diff --git a/README.md b/README.md index 0a2a0722d8..808a1dbb30 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ compromises backward compatibility. provide a tag or sha1). * Include documentation generator. * Shebang line OK so language can be used for "scripting" as well. + * No null pointer. Convenient syntax for dealing with a maybe type so that + null pointer is not missed. ### Current Status diff --git a/src/analyze.cpp b/src/analyze.cpp index 5e6b0a3558..cba9f955ae 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -551,22 +551,127 @@ static TypeTableEntry *get_return_type(BlockContext *context) { return return_type_node->codegen_node->data.type_node.entry; } -static void check_type_compatibility(CodeGen *g, AstNode *node, +static TypeTableEntry *determine_peer_type_compatibility(CodeGen *g, AstNode *node, + TypeTableEntry *type1, TypeTableEntry *type2) +{ + if (type1->id == TypeTableEntryIdInvalid || + type2->id == TypeTableEntryIdInvalid) + { + return type1; + } else if (type1->id == TypeTableEntryIdUnreachable) { + return type2; + } else if (type2->id == TypeTableEntryIdUnreachable) { + return type1; + } else if (type1->id == TypeTableEntryIdInt && + type2->id == TypeTableEntryIdInt && + type1->data.integral.is_signed == type2->data.integral.is_signed) + { + return (type1->size_in_bits > type2->size_in_bits) ? type1 : type2; + } else if (type1->id == TypeTableEntryIdFloat && + type2->id == TypeTableEntryIdFloat) + { + return (type1->size_in_bits > type2->size_in_bits) ? type1 : type2; + } else if (type1->id == TypeTableEntryIdArray && + type2->id == TypeTableEntryIdArray && + type1 == type2) + { + return type1; + } else if (type1 == type2) { + return type1; + } + + add_node_error(g, node, + buf_sprintf("ambiguous expression type: '%s' vs '%s'", + buf_ptr(&type1->name), buf_ptr(&type2->name))); + + return g->builtin_types.entry_invalid; +} + +static bool num_lit_fits_in_other_type(CodeGen *g, TypeTableEntry *literal_type, TypeTableEntry *other_type) { + NumLit num_lit = literal_type->data.num_lit.kind; + uint64_t lit_size_in_bits = num_lit_bit_count(num_lit); + + switch (other_type->id) { + case TypeTableEntryIdInvalid: + case TypeTableEntryIdNumberLiteral: + zig_unreachable(); + case TypeTableEntryIdVoid: + case TypeTableEntryIdBool: + case TypeTableEntryIdUnreachable: + case TypeTableEntryIdPointer: + case TypeTableEntryIdArray: + case TypeTableEntryIdStruct: + return false; + case TypeTableEntryIdInt: + if (is_num_lit_unsigned(num_lit)) { + return lit_size_in_bits <= other_type->size_in_bits; + } else { + return false; + } + case TypeTableEntryIdFloat: + if (is_num_lit_float(num_lit)) { + return lit_size_in_bits <= other_type->size_in_bits; + } else { + return false; + } + } + zig_unreachable(); +} + +static TypeTableEntry *resolve_type_compatibility(CodeGen *g, AstNode *node, TypeTableEntry *expected_type, TypeTableEntry *actual_type) { if (expected_type == nullptr) - return; // anything will do + return actual_type; // anything will do if (expected_type == actual_type) - return; // match + return expected_type; // match if (expected_type->id == TypeTableEntryIdInvalid || actual_type->id == TypeTableEntryIdInvalid) - return; // already complained + return expected_type; // already complained if (actual_type->id == TypeTableEntryIdUnreachable) - return; // sorry toots; gotta run. good luck with that expected type. + return actual_type; // sorry toots; gotta run. good luck with that expected type. + + if (actual_type->id == TypeTableEntryIdNumberLiteral && + num_lit_fits_in_other_type(g, actual_type, expected_type)) + { + return expected_type; + } + + // implicit widening conversion + if (expected_type->id == TypeTableEntryIdInt && + actual_type->id == TypeTableEntryIdInt && + expected_type->data.integral.is_signed == actual_type->data.integral.is_signed && + expected_type->size_in_bits > actual_type->size_in_bits) + { + node->codegen_node->expr_node.cast_type = expected_type; + node->codegen_node->expr_node.implicit_cast.op = CastOpIntWidenOrShorten; + return expected_type; + } add_node_error(g, node, buf_sprintf("expected type '%s', got '%s'", buf_ptr(&expected_type->name), buf_ptr(&actual_type->name))); + + return g->builtin_types.entry_invalid; +} + +static TypeTableEntry *resolve_peer_type_compatibility(CodeGen *g, AstNode *parent_node, + AstNode *child1, AstNode *child2, + TypeTableEntry *type1, TypeTableEntry *type2) +{ + assert(type1); + assert(type2); + + TypeTableEntry *parent_type = determine_peer_type_compatibility(g, parent_node, type1, type2); + + if (parent_type->id == TypeTableEntryIdInvalid) { + return parent_type; + } + + resolve_type_compatibility(g, child1, parent_type, type1); + resolve_type_compatibility(g, child2, parent_type, type2); + + return parent_type; } BlockContext *new_block_context(AstNode *node, BlockContext *parent) { @@ -624,39 +729,6 @@ static void get_struct_field(TypeTableEntry *struct_type, Buf *name, TypeStructF *out_i = -1; } -static bool num_lit_fits_in_other_type(CodeGen *g, TypeTableEntry *literal_type, TypeTableEntry *other_type) { - NumLit num_lit = literal_type->data.num_lit.kind; - uint64_t lit_size_in_bits = num_lit_bit_count(num_lit); - - switch (other_type->id) { - case TypeTableEntryIdInvalid: - case TypeTableEntryIdNumberLiteral: - zig_unreachable(); - case TypeTableEntryIdVoid: - case TypeTableEntryIdBool: - case TypeTableEntryIdUnreachable: - case TypeTableEntryIdPointer: - case TypeTableEntryIdArray: - case TypeTableEntryIdStruct: - return false; - case TypeTableEntryIdInt: - if (is_num_lit_unsigned(num_lit)) { - - return lit_size_in_bits <= other_type->size_in_bits; - } else { - return false; - } - case TypeTableEntryIdFloat: - if (is_num_lit_float(num_lit)) { - return lit_size_in_bits <= other_type->size_in_bits; - } else { - return false; - } - } - zig_unreachable(); -} - - static TypeTableEntry *analyze_field_access_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, AstNode *node) { @@ -1125,19 +1197,10 @@ static TypeTableEntry *analyze_number_literal_expr(CodeGen *g, ImportTableEntry buf_sprintf("number literal too large to be represented in any type")); return g->builtin_types.entry_invalid; } else if (expected_type) { - if (expected_type->id == TypeTableEntryIdInvalid) { - return g->builtin_types.entry_invalid; - } else if (num_lit_fits_in_other_type(g, num_lit_type, expected_type)) { - NumberLiteralNode *codegen_num_lit = &node->codegen_node->data.num_lit_node; - assert(!codegen_num_lit->resolved_type); - codegen_num_lit->resolved_type = expected_type; - - return expected_type; - } else { - add_node_error(g, node, buf_sprintf("expected type '%s', got '%s'", - buf_ptr(&expected_type->name), buf_ptr(&num_lit_type->name))); - return g->builtin_types.entry_invalid; - } + NumberLiteralNode *codegen_num_lit = &node->codegen_node->data.num_lit_node; + assert(!codegen_num_lit->resolved_type); + codegen_num_lit->resolved_type = resolve_type_compatibility(g, node, expected_type, num_lit_type); + return codegen_num_lit->resolved_type; } else { return num_lit_type; } @@ -1154,6 +1217,7 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, BlockContext *child_context = new_block_context(node, context); node->codegen_node->data.block_node.block_context = child_context; return_type = g->builtin_types.entry_void; + for (int i = 0; i < node->data.block.statements.length; i += 1) { AstNode *child = node->data.block.statements.at(i); if (child->type == NodeTypeLabel) { @@ -1172,7 +1236,9 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, add_node_error(g, first_executing_node(child), buf_sprintf("unreachable code")); break; } - return_type = analyze_expression(g, import, child_context, nullptr, child); + bool is_last = (i == node->data.block.statements.length - 1); + TypeTableEntry *passed_expected_type = is_last ? expected_type : nullptr; + return_type = analyze_expression(g, import, child_context, passed_expected_type, child); } break; } @@ -1194,7 +1260,7 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, actual_return_type = g->builtin_types.entry_invalid; } - check_type_compatibility(g, node, expected_return_type, actual_return_type); + resolve_type_compatibility(g, node, expected_return_type, actual_return_type); } else { add_node_error(g, node, buf_sprintf("return expression outside function definition")); } @@ -1387,22 +1453,17 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, else_type = analyze_expression(g, import, context, expected_type, node->data.if_expr.else_node); } else { else_type = g->builtin_types.entry_void; + else_type = resolve_type_compatibility(g, node, expected_type, else_type); } - TypeTableEntry *primary_type; - TypeTableEntry *other_type; - if (then_type->id == TypeTableEntryIdUnreachable) { - primary_type = else_type; - other_type = then_type; + if (expected_type) { + return_type = (then_type->id == TypeTableEntryIdUnreachable) ? else_type : then_type; } else { - primary_type = then_type; - other_type = else_type; + return_type = resolve_peer_type_compatibility(g, node, + node->data.if_expr.then_block, node->data.if_expr.else_node, + then_type, else_type); } - - check_type_compatibility(g, node, primary_type, other_type); - check_type_compatibility(g, node, expected_type, other_type); - return_type = primary_type; break; } case NodeTypeDirective: @@ -1421,7 +1482,7 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, zig_unreachable(); } assert(return_type); - check_type_compatibility(g, node, expected_type, return_type); + resolve_type_compatibility(g, node, expected_type, return_type); node->codegen_node->expr_node.type_entry = return_type; node->codegen_node->expr_node.block_context = context; diff --git a/src/analyze.hpp b/src/analyze.hpp index b898b3b497..71fdc3bfb5 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -145,6 +145,7 @@ struct CodeGen { TypeTableEntry *entry_bool; TypeTableEntry *entry_u8; TypeTableEntry *entry_u64; + TypeTableEntry *entry_i8; TypeTableEntry *entry_i32; TypeTableEntry *entry_i64; TypeTableEntry *entry_isize; @@ -230,12 +231,6 @@ struct FnDefNode { bool skip; }; -struct ExprNode { - TypeTableEntry *type_entry; - // the context in which this expression is evaluated. - // for blocks, this points to the containing scope, not the block's own scope for its children. - BlockContext *block_context; -}; struct AssignNode { VariableTableEntry *var_entry; @@ -268,6 +263,17 @@ struct CastNode { LLVMValueRef ptr; }; +struct ExprNode { + TypeTableEntry *type_entry; + // the context in which this expression is evaluated. + // for blocks, this points to the containing scope, not the block's own scope for its children. + BlockContext *block_context; + + // may be null for no cast + TypeTableEntry *cast_type; + CastNode implicit_cast; +}; + struct NumberLiteralNode { TypeTableEntry *resolved_type; }; diff --git a/src/codegen.cpp b/src/codegen.cpp index b0a7eae2c8..f81d07faa6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -274,16 +274,9 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) { zig_unreachable(); } -static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) { - assert(node->type == NodeTypeCastExpr); - - LLVMValueRef expr_val = gen_expr(g, node->data.cast_expr.expr); - - TypeTableEntry *actual_type = get_expr_type(node->data.cast_expr.expr); - TypeTableEntry *wanted_type = get_expr_type(node); - - CastNode *cast_node = &node->codegen_node->data.cast_node; - +static LLVMValueRef gen_bare_cast(CodeGen *g, AstNode *node, LLVMValueRef expr_val, + TypeTableEntry *actual_type, TypeTableEntry *wanted_type, CastNode *cast_node) +{ switch (cast_node->op) { case CastOpNothing: return expr_val; @@ -327,6 +320,20 @@ static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) { zig_unreachable(); } +static LLVMValueRef gen_cast_expr(CodeGen *g, AstNode *node) { + assert(node->type == NodeTypeCastExpr); + + LLVMValueRef expr_val = gen_expr(g, node->data.cast_expr.expr); + + TypeTableEntry *actual_type = get_expr_type(node->data.cast_expr.expr); + TypeTableEntry *wanted_type = get_expr_type(node); + + CastNode *cast_node = &node->codegen_node->data.cast_node; + + return gen_bare_cast(g, node, expr_val, actual_type, wanted_type, cast_node); + +} + static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, LLVMValueRef val1, LLVMValueRef val2, TypeTableEntry *op1_type, TypeTableEntry *op2_type, @@ -871,7 +878,7 @@ static LLVMValueRef gen_asm_expr(CodeGen *g, AstNode *node) { return LLVMBuildCall(g->builder, asm_fn, param_values, input_and_output_count, ""); } -static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { +static LLVMValueRef gen_expr_no_cast(CodeGen *g, AstNode *node) { switch (node->type) { case NodeTypeBinOpExpr: return gen_bin_op_expr(g, node); @@ -1022,6 +1029,22 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { zig_unreachable(); } +static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { + LLVMValueRef val = gen_expr_no_cast(g, node); + + if (node->type == NodeTypeVoid) { + return val; + } + + assert(node->codegen_node); + + TypeTableEntry *actual_type = node->codegen_node->expr_node.type_entry; + TypeTableEntry *cast_type = node->codegen_node->expr_node.cast_type; + + return cast_type ? gen_bare_cast(g, node, val, actual_type, cast_type, + &node->codegen_node->expr_node.implicit_cast) : val; +} + static void build_label_blocks(CodeGen *g, AstNode *block_node) { assert(block_node->type == NodeTypeBlock); for (int i = 0; i < block_node->data.block.statements.length; i += 1) { @@ -1322,6 +1345,19 @@ static void define_builtin_types(CodeGen *g) { g->builtin_types.entry_u64 = entry; } g->builtin_types.entry_c_string_literal = get_pointer_to_type(g, g->builtin_types.entry_u8, true); + { + TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt); + entry->type_ref = LLVMInt8Type(); + buf_init_from_str(&entry->name, "i8"); + entry->size_in_bits = 8; + entry->align_in_bits = 8; + entry->data.integral.is_signed = true; + entry->di_type = LLVMZigCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), + entry->size_in_bits, entry->align_in_bits, + LLVMZigEncoding_DW_ATE_signed()); + g->type_table.put(&entry->name, entry); + g->builtin_types.entry_i8 = entry; + } { TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdInt); entry->type_ref = LLVMInt32Type(); @@ -1951,6 +1987,8 @@ void codegen_link(CodeGen *g, const char *out_file) { fprintf(stderr, "ld failed with return code %d\n", return_code); fprintf(stderr, "%s\n", buf_ptr(&ld_stderr)); exit(1); + } else if (buf_len(&ld_stderr)) { + fprintf(stderr, "%s\n", buf_ptr(&ld_stderr)); } if (g->out_type == OutTypeLib) { diff --git a/test/run_tests.cpp b/test/run_tests.cpp index f6d4274bda..2a7746c4d0 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -56,7 +56,7 @@ static TestCase *add_simple_case(const char *case_name, const char *source, cons test_case->compiler_args.append(tmp_exe_path); test_case->compiler_args.append("--release"); test_case->compiler_args.append("--strip"); - test_case->compiler_args.append("--verbose"); + //test_case->compiler_args.append("--verbose"); test_case->compiler_args.append("--color"); test_case->compiler_args.append("on"); @@ -86,7 +86,7 @@ static TestCase *add_compile_fail_case(const char *case_name, const char *source test_case->compiler_args.append(tmp_exe_path); test_case->compiler_args.append("--release"); test_case->compiler_args.append("--strip"); - test_case->compiler_args.append("--verbose"); + //test_case->compiler_args.append("--verbose"); test_cases.append(test_case); @@ -788,6 +788,15 @@ fn f() { x = 1; } )SOURCE", 1, ".tmp_source.zig:4:5: error: cannot assign to constant variable"); + + + add_compile_fail_case("missing else clause", R"SOURCE( +fn f() { + const x : i32 = if true { 1 }; + const y = if true { 1 as i32 }; +} + )SOURCE", 2, ".tmp_source.zig:3:21: error: expected type 'i32', got 'void'", + ".tmp_source.zig:4:15: error: ambiguous expression type: 'i32' vs 'void'"); } static void print_compiler_invocation(TestCase *test_case) {