| author | |
| committer | |
| log | 1ed926c3216016d73e83377a295869d8579e2fde |
| tree | dfc72c0a73844cf440a83e8b67d6c16683c63c2c |
| parent | c6a9ab107bb383759f3627ed828ea2a779844f2b |
9 files changed, 68 insertions(+), 100 deletions(-)
README.md+1-5| ... | ... | @@ -104,11 +104,7 @@ Type : token(Symbol) | PointerType | token(Unreachable) |
| 104 | 104 | |
| 105 | 105 | PointerType : token(Star) token(Const) Type | token(Star) token(Mut) Type |
| 106 | 106 | |
| 107 | Block : token(LBrace) many(Statement) token(RBrace) | |
| 108 | ||
| 109 | Statement : ExpressionStatement | |
| 110 | ||
| 111 | ExpressionStatement : Expression token(Semicolon) | |
| 107 | Block : token(LBrace) list(option(Expression), token(Semicolon)) token(RBrace) | |
| 112 | 108 | |
| 113 | 109 | Expression : BoolOrExpression | ReturnExpression |
| 114 | 110 |
src/analyze.cpp+16-58| ... | ... | @@ -270,6 +270,7 @@ static void preview_function_declarations(CodeGen *g, ImportTableEntry *import, |
| 270 | 270 | case NodeTypeNumberLiteral: |
| 271 | 271 | case NodeTypeStringLiteral: |
| 272 | 272 | case NodeTypeUnreachable: |
| 273 | case NodeTypeVoid: | |
| 273 | 274 | case NodeTypeSymbol: |
| 274 | 275 | case NodeTypeCastExpr: |
| 275 | 276 | case NodeTypePrefixOpExpr: |
| ... | ... | @@ -311,8 +312,12 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 311 | 312 | for (int i = 0; i < node->data.block.statements.length; i += 1) { |
| 312 | 313 | AstNode *child = node->data.block.statements.at(i); |
| 313 | 314 | if (return_type == g->builtin_types.entry_unreachable) { |
| 314 | add_node_error(g, child, | |
| 315 | buf_sprintf("unreachable code")); | |
| 315 | if (child->type == NodeTypeVoid) { | |
| 316 | // {unreachable;void;void} is allowed. | |
| 317 | // ignore void statements once we enter unreachable land. | |
| 318 | continue; | |
| 319 | } | |
| 320 | add_node_error(g, child, buf_sprintf("unreachable code")); | |
| 316 | 321 | break; |
| 317 | 322 | } |
| 318 | 323 | return_type = analyze_expression(g, import, context, nullptr, child); |
| ... | ... | @@ -415,6 +420,10 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 415 | 420 | return_type = g->builtin_types.entry_unreachable; |
| 416 | 421 | break; |
| 417 | 422 | |
| 423 | case NodeTypeVoid: | |
| 424 | return_type = g->builtin_types.entry_void; | |
| 425 | break; | |
| 426 | ||
| 418 | 427 | case NodeTypeSymbol: |
| 419 | 428 | // look up symbol in symbol table |
| 420 | 429 | zig_panic("TODO"); |
| ... | ... | @@ -439,59 +448,6 @@ static TypeTableEntry * analyze_expression(CodeGen *g, ImportTableEntry *import, |
| 439 | 448 | return return_type; |
| 440 | 449 | } |
| 441 | 450 | |
| 442 | static void check_fn_def_control_flow(CodeGen *g, AstNode *node) { | |
| 443 | // Follow the execution flow and make sure the code returns appropriately. | |
| 444 | // * A `return` statement in an unreachable type function should be an error. | |
| 445 | // * Control flow should not be able to reach the end of an unreachable type function. | |
| 446 | // * Functions that have a type other than void should not return without a value. | |
| 447 | // * void functions without explicit return statements at the end need the | |
| 448 | // add_implicit_return flag set on the codegen node. | |
| 449 | assert(node->type == NodeTypeFnDef); | |
| 450 | AstNode *proto_node = node->data.fn_def.fn_proto; | |
| 451 | assert(proto_node->type == NodeTypeFnProto); | |
| 452 | AstNode *return_type_node = proto_node->data.fn_proto.return_type; | |
| 453 | assert(return_type_node->type == NodeTypeType); | |
| 454 | ||
| 455 | node->codegen_node = allocate<CodeGenNode>(1); | |
| 456 | FnDefNode *codegen_fn_def = &node->codegen_node->data.fn_def_node; | |
| 457 | ||
| 458 | assert(return_type_node->codegen_node); | |
| 459 | TypeTableEntry *type_entry = return_type_node->codegen_node->data.type_node.entry; | |
| 460 | assert(type_entry); | |
| 461 | ||
| 462 | AstNode *body_node = node->data.fn_def.body; | |
| 463 | assert(body_node->type == NodeTypeBlock); | |
| 464 | ||
| 465 | // TODO once we understand types, do this pass after type checking, and | |
| 466 | // if an expression has an unreachable value then stop looking at statements after | |
| 467 | // it. then we can remove the check to `unreachable` in the end of this function. | |
| 468 | bool prev_statement_return = false; | |
| 469 | for (int i = 0; i < body_node->data.block.statements.length; i += 1) { | |
| 470 | AstNode *statement_node = body_node->data.block.statements.at(i); | |
| 471 | if (statement_node->type == NodeTypeReturnExpr) { | |
| 472 | if (type_entry == g->builtin_types.entry_unreachable) { | |
| 473 | add_node_error(g, statement_node, | |
| 474 | buf_sprintf("return statement in function with unreachable return type")); | |
| 475 | return; | |
| 476 | } else { | |
| 477 | prev_statement_return = true; | |
| 478 | } | |
| 479 | } else if (prev_statement_return) { | |
| 480 | add_node_error(g, statement_node, | |
| 481 | buf_sprintf("unreachable code")); | |
| 482 | } | |
| 483 | } | |
| 484 | ||
| 485 | if (!prev_statement_return) { | |
| 486 | if (type_entry == g->builtin_types.entry_void) { | |
| 487 | codegen_fn_def->add_implicit_return = true; | |
| 488 | } else if (type_entry != g->builtin_types.entry_unreachable) { | |
| 489 | add_node_error(g, node, | |
| 490 | buf_sprintf("control reaches end of non-void function")); | |
| 491 | } | |
| 492 | } | |
| 493 | } | |
| 494 | ||
| 495 | 451 | static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, AstNode *node) { |
| 496 | 452 | switch (node->type) { |
| 497 | 453 | case NodeTypeFnDef: |
| ... | ... | @@ -512,14 +468,15 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, |
| 512 | 468 | // TODO: define local variables for parameters |
| 513 | 469 | } |
| 514 | 470 | |
| 515 | check_fn_def_control_flow(g, node); | |
| 516 | ||
| 517 | 471 | BlockContext context; |
| 518 | 472 | context.node = node; |
| 519 | 473 | context.root = &context; |
| 520 | 474 | context.parent = nullptr; |
| 521 | 475 | TypeTableEntry *expected_type = fn_proto->return_type->codegen_node->data.type_node.entry; |
| 522 | analyze_expression(g, import, &context, expected_type, node->data.fn_def.body); | |
| 476 | TypeTableEntry *block_return_type = analyze_expression(g, import, &context, expected_type, node->data.fn_def.body); | |
| 477 | ||
| 478 | node->codegen_node = allocate<CodeGenNode>(1); | |
| 479 | node->codegen_node->data.fn_def_node.implicit_return_type = block_return_type; | |
| 523 | 480 | } |
| 524 | 481 | break; |
| 525 | 482 | |
| ... | ... | @@ -548,6 +505,7 @@ static void analyze_top_level_declaration(CodeGen *g, ImportTableEntry *import, |
| 548 | 505 | case NodeTypeNumberLiteral: |
| 549 | 506 | case NodeTypeStringLiteral: |
| 550 | 507 | case NodeTypeUnreachable: |
| 508 | case NodeTypeVoid: | |
| 551 | 509 | case NodeTypeSymbol: |
| 552 | 510 | case NodeTypeCastExpr: |
| 553 | 511 | case NodeTypePrefixOpExpr: |
src/codegen.cpp+10-5| ... | ... | @@ -401,6 +401,8 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
| 401 | 401 | case NodeTypeUnreachable: |
| 402 | 402 | add_debug_source_node(g, node); |
| 403 | 403 | return LLVMBuildUnreachable(g->builder); |
| 404 | case NodeTypeVoid: | |
| 405 | return nullptr; | |
| 404 | 406 | case NodeTypeNumberLiteral: |
| 405 | 407 | { |
| 406 | 408 | Buf *number_str = &node->data.number; |
| ... | ... | @@ -441,7 +443,7 @@ static LLVMValueRef gen_expr(CodeGen *g, AstNode *node) { |
| 441 | 443 | zig_unreachable(); |
| 442 | 444 | } |
| 443 | 445 | |
| 444 | static void gen_block(CodeGen *g, ImportTableEntry *import, AstNode *block_node, bool add_implicit_return) { | |
| 446 | static void gen_block(CodeGen *g, ImportTableEntry *import, AstNode *block_node, TypeTableEntry *implicit_return_type) { | |
| 445 | 447 | assert(block_node->type == NodeTypeBlock); |
| 446 | 448 | |
| 447 | 449 | LLVMZigDILexicalBlock *di_block = LLVMZigCreateLexicalBlock(g->dbuilder, g->block_scopes.last(), |
| ... | ... | @@ -450,13 +452,16 @@ static void gen_block(CodeGen *g, ImportTableEntry *import, AstNode *block_node, |
| 450 | 452 | |
| 451 | 453 | add_debug_source_node(g, block_node); |
| 452 | 454 | |
| 455 | LLVMValueRef return_value; | |
| 453 | 456 | for (int i = 0; i < block_node->data.block.statements.length; i += 1) { |
| 454 | 457 | AstNode *statement_node = block_node->data.block.statements.at(i); |
| 455 | gen_expr(g, statement_node); | |
| 458 | return_value = gen_expr(g, statement_node); | |
| 456 | 459 | } |
| 457 | 460 | |
| 458 | if (add_implicit_return) { | |
| 461 | if (implicit_return_type == g->builtin_types.entry_void) { | |
| 459 | 462 | LLVMBuildRetVoid(g->builder); |
| 463 | } else if (implicit_return_type != g->builtin_types.entry_unreachable) { | |
| 464 | LLVMBuildRet(g->builder, return_value); | |
| 460 | 465 | } |
| 461 | 466 | |
| 462 | 467 | g->block_scopes.pop(); |
| ... | ... | @@ -552,8 +557,8 @@ static void do_code_gen(CodeGen *g) { |
| 552 | 557 | codegen_fn_def->params = allocate<LLVMValueRef>(LLVMCountParams(fn)); |
| 553 | 558 | LLVMGetParams(fn, codegen_fn_def->params); |
| 554 | 559 | |
| 555 | bool add_implicit_return = codegen_fn_def->add_implicit_return; | |
| 556 | gen_block(g, import, fn_def_node->data.fn_def.body, add_implicit_return); | |
| 560 | TypeTableEntry *implicit_return_type = codegen_fn_def->implicit_return_type; | |
| 561 | gen_block(g, import, fn_def_node->data.fn_def.body, implicit_return_type); | |
| 557 | 562 | |
| 558 | 563 | g->block_scopes.pop(); |
| 559 | 564 | } |
src/parser.cpp+33-29| ... | ... | @@ -89,6 +89,8 @@ const char *node_type_str(NodeType node_type) { |
| 89 | 89 | return "PrefixOpExpr"; |
| 90 | 90 | case NodeTypeUse: |
| 91 | 91 | return "Use"; |
| 92 | case NodeTypeVoid: | |
| 93 | return "Void"; | |
| 92 | 94 | } |
| 93 | 95 | zig_unreachable(); |
| 94 | 96 | } |
| ... | ... | @@ -233,6 +235,9 @@ void ast_print(AstNode *node, int indent) { |
| 233 | 235 | case NodeTypeUse: |
| 234 | 236 | fprintf(stderr, "%s '%s'\n", node_type_str(node->type), buf_ptr(&node->data.use.path)); |
| 235 | 237 | break; |
| 238 | case NodeTypeVoid: | |
| 239 | fprintf(stderr, "Void\n"); | |
| 240 | break; | |
| 236 | 241 | } |
| 237 | 242 | } |
| 238 | 243 | |
| ... | ... | @@ -416,6 +421,9 @@ static AstNode *ast_parse_type(ParseContext *pc, int token_index, int *new_token |
| 416 | 421 | if (token->id == TokenIdKeywordUnreachable) { |
| 417 | 422 | node->data.type.type = AstNodeTypeTypePrimitive; |
| 418 | 423 | buf_init_from_str(&node->data.type.primitive_name, "unreachable"); |
| 424 | } else if (token->id == TokenIdKeywordVoid) { | |
| 425 | node->data.type.type = AstNodeTypeTypePrimitive; | |
| 426 | buf_init_from_str(&node->data.type.primitive_name, "void"); | |
| 419 | 427 | } else if (token->id == TokenIdSymbol) { |
| 420 | 428 | node->data.type.type = AstNodeTypeTypePrimitive; |
| 421 | 429 | ast_buf_from_token(pc, token, &node->data.type.primitive_name); |
| ... | ... | @@ -569,6 +577,10 @@ static AstNode *ast_parse_primary_expr(ParseContext *pc, int *token_index, bool |
| 569 | 577 | AstNode *node = ast_create_node(pc, NodeTypeUnreachable, token); |
| 570 | 578 | *token_index += 1; |
| 571 | 579 | return node; |
| 580 | } else if (token->id == TokenIdKeywordVoid) { | |
| 581 | AstNode *node = ast_create_node(pc, NodeTypeVoid, token); | |
| 582 | *token_index += 1; | |
| 583 | return node; | |
| 572 | 584 | } else if (token->id == TokenIdSymbol) { |
| 573 | 585 | AstNode *node = ast_create_node(pc, NodeTypeSymbol, token); |
| 574 | 586 | ast_buf_from_token(pc, token, &node->data.symbol); |
| ... | ... | @@ -1024,50 +1036,42 @@ static AstNode *ast_parse_expression(ParseContext *pc, int *token_index, bool ma |
| 1024 | 1036 | } |
| 1025 | 1037 | |
| 1026 | 1038 | /* |
| 1027 | ExpressionStatement : Expression token(Semicolon) | |
| 1028 | */ | |
| 1029 | static AstNode *ast_parse_expression_statement(ParseContext *pc, int *token_index) { | |
| 1030 | AstNode *expr_node = ast_parse_expression(pc, token_index, true); | |
| 1031 | ||
| 1032 | Token *semicolon = &pc->tokens->at(*token_index); | |
| 1033 | *token_index += 1; | |
| 1034 | ast_expect_token(pc, semicolon, TokenIdSemicolon); | |
| 1035 | ||
| 1036 | return expr_node; | |
| 1037 | } | |
| 1038 | ||
| 1039 | /* | |
| 1040 | Statement : ExpressionStatement | |
| 1041 | */ | |
| 1042 | static AstNode *ast_parse_statement(ParseContext *pc, int *token_index) { | |
| 1043 | return ast_parse_expression_statement(pc, token_index); | |
| 1044 | } | |
| 1045 | ||
| 1046 | /* | |
| 1047 | Block : token(LBrace) many(Statement) token(RBrace); | |
| 1039 | Block : token(LBrace) list(option(Expression), token(Semicolon)) token(RBrace) | |
| 1048 | 1040 | */ |
| 1049 | 1041 | static AstNode *ast_parse_block(ParseContext *pc, int *token_index, bool mandatory) { |
| 1050 | Token *l_brace = &pc->tokens->at(*token_index); | |
| 1042 | Token *last_token = &pc->tokens->at(*token_index); | |
| 1051 | 1043 | |
| 1052 | if (l_brace->id != TokenIdLBrace) { | |
| 1044 | if (last_token->id != TokenIdLBrace) { | |
| 1053 | 1045 | if (mandatory) { |
| 1054 | ast_invalid_token_error(pc, l_brace); | |
| 1046 | ast_invalid_token_error(pc, last_token); | |
| 1055 | 1047 | } else { |
| 1056 | 1048 | return nullptr; |
| 1057 | 1049 | } |
| 1058 | 1050 | } |
| 1059 | 1051 | *token_index += 1; |
| 1060 | 1052 | |
| 1061 | AstNode *node = ast_create_node(pc, NodeTypeBlock, l_brace); | |
| 1053 | AstNode *node = ast_create_node(pc, NodeTypeBlock, last_token); | |
| 1062 | 1054 | |
| 1055 | // {} -> {void} | |
| 1056 | // {;} -> {void;void} | |
| 1057 | // {2} -> {2} | |
| 1058 | // {2;} -> {2;void} | |
| 1059 | // {;2} -> {void;2} | |
| 1063 | 1060 | for (;;) { |
| 1064 | Token *token = &pc->tokens->at(*token_index); | |
| 1065 | if (token->id == TokenIdRBrace) { | |
| 1061 | AstNode *expression_node = ast_parse_expression(pc, token_index, false); | |
| 1062 | if (!expression_node) { | |
| 1063 | expression_node = ast_create_node(pc, NodeTypeVoid, last_token); | |
| 1064 | } | |
| 1065 | node->data.block.statements.append(expression_node); | |
| 1066 | ||
| 1067 | last_token = &pc->tokens->at(*token_index); | |
| 1068 | if (last_token->id == TokenIdRBrace) { | |
| 1066 | 1069 | *token_index += 1; |
| 1067 | 1070 | return node; |
| 1071 | } else if (last_token->id == TokenIdSemicolon) { | |
| 1072 | *token_index += 1; | |
| 1068 | 1073 | } else { |
| 1069 | AstNode *statement_node = ast_parse_statement(pc, token_index); | |
| 1070 | node->data.block.statements.append(statement_node); | |
| 1074 | ast_invalid_token_error(pc, last_token); | |
| 1071 | 1075 | } |
| 1072 | 1076 | } |
| 1073 | 1077 | zig_unreachable(); |
src/parser.hpp+1| ... | ... | @@ -38,6 +38,7 @@ enum NodeType { |
| 38 | 38 | NodeTypePrefixOpExpr, |
| 39 | 39 | NodeTypeFnCallExpr, |
| 40 | 40 | NodeTypeUse, |
| 41 | NodeTypeVoid, | |
| 41 | 42 | }; |
| 42 | 43 | |
| 43 | 44 | struct AstNodeRoot { |
src/semantic_info.hpp+1-1| ... | ... | @@ -106,7 +106,7 @@ struct TypeNode { |
| 106 | 106 | }; |
| 107 | 107 | |
| 108 | 108 | struct FnDefNode { |
| 109 | bool add_implicit_return; | |
| 109 | TypeTableEntry *implicit_return_type; | |
| 110 | 110 | bool skip; |
| 111 | 111 | LLVMValueRef *params; |
| 112 | 112 | }; |
src/tokenizer.cpp+3| ... | ... | @@ -181,6 +181,8 @@ static void end_token(Tokenize *t) { |
| 181 | 181 | t->cur_tok->id = TokenIdKeywordAs; |
| 182 | 182 | } else if (mem_eql_str(token_mem, token_len, "use")) { |
| 183 | 183 | t->cur_tok->id = TokenIdKeywordUse; |
| 184 | } else if (mem_eql_str(token_mem, token_len, "void")) { | |
| 185 | t->cur_tok->id = TokenIdKeywordVoid; | |
| 184 | 186 | } |
| 185 | 187 | |
| 186 | 188 | t->cur_tok = nullptr; |
| ... | ... | @@ -574,6 +576,7 @@ static const char * token_name(Token *token) { |
| 574 | 576 | case TokenIdKeywordExport: return "Export"; |
| 575 | 577 | case TokenIdKeywordAs: return "As"; |
| 576 | 578 | case TokenIdKeywordUse: return "Use"; |
| 579 | case TokenIdKeywordVoid: return "Void"; | |
| 577 | 580 | case TokenIdLParen: return "LParen"; |
| 578 | 581 | case TokenIdRParen: return "RParen"; |
| 579 | 582 | case TokenIdComma: return "Comma"; |
src/tokenizer.hpp+1| ... | ... | @@ -23,6 +23,7 @@ enum TokenId { |
| 23 | 23 | TokenIdKeywordExport, |
| 24 | 24 | TokenIdKeywordAs, |
| 25 | 25 | TokenIdKeywordUse, |
| 26 | TokenIdKeywordVoid, | |
| 26 | 27 | TokenIdLParen, |
| 27 | 28 | TokenIdRParen, |
| 28 | 29 | TokenIdComma, |
test/run_tests.cpp+2-2| ... | ... | @@ -209,11 +209,11 @@ fn a() {} |
| 209 | 209 | |
| 210 | 210 | add_compile_fail_case("unreachable with return", R"SOURCE( |
| 211 | 211 | fn a() -> unreachable {return;} |
| 212 | )SOURCE", 1, ".tmp_source.zig:2:24: error: return statement in function with unreachable return type"); | |
| 212 | )SOURCE", 1, ".tmp_source.zig:2:24: error: type mismatch. expected unreachable. got void"); | |
| 213 | 213 | |
| 214 | 214 | add_compile_fail_case("control reaches end of non-void function", R"SOURCE( |
| 215 | 215 | fn a() -> i32 {} |
| 216 | )SOURCE", 1, ".tmp_source.zig:2:1: error: control reaches end of non-void function"); | |
| 216 | )SOURCE", 1, ".tmp_source.zig:2:15: error: type mismatch. expected i32. got void"); | |
| 217 | 217 | |
| 218 | 218 | add_compile_fail_case("undefined function call", R"SOURCE( |
| 219 | 219 | fn a() { |