From 28b9be64ee58fd6d16579c28197f11aa60fd2cc3 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 27 Aug 2025 12:37:01 +0200 Subject: [PATCH] Update error message for missing body after if/for/etc #2289. --- releasenotes.md | 3 ++- src/compiler/parse_stmt.c | 21 ++++++++++++++++++++- test/test_suite/statements/missing_body.c3 | 4 ++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 test/test_suite/statements/missing_body.c3 diff --git a/releasenotes.md b/releasenotes.md index fd60499c1..ce687913c 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -26,7 +26,8 @@ - `@assignable_to` is deprecated in favour of `$define` - Add `linklib-dir` to c3l-libraries to place their linked libraries in. Defaults to `linked-libs` - If the `os-arch` linked library doesn't exist, try with `os` for c3l libs. -- A file with an inferred module may not contain additional other modules. +- A file with an inferred module may not contain additional other modules. +- Update error message for missing body after if/for/etc #2289. ### Fixes - List.remove_at would incorrectly trigger ASAN. diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 2c9279eeb..c1fd10a99 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -9,6 +9,16 @@ static inline Expr *parse_asm_expr(ParseContext *c); +INLINE bool next_is_end_parens(ParseContext *c) +{ + TokenType tok = c->tok; + return tok == TOKEN_RBRACE || tok == TOKEN_RPAREN; +} +#define CHECK_HAS_BODY(text_) \ + do { if (next_is_end_parens(c)) { \ + print_error_after(c->prev_span, "The body of %s statement was expected here.", text_); \ + return poisoned_ast; } } while (0) + static Ast *parse_decl_stmt_after_type(ParseContext *c, TypeInfo *type) { Ast *ast = ast_calloc(); @@ -489,6 +499,7 @@ static inline Ast* parse_do_stmt(ParseContext *c) do_ast->flow.skip_first = true; ASSIGN_DECLID_OR_RET(do_ast->for_stmt.flow.label, parse_optional_label(c, do_ast), poisoned_ast); + CHECK_HAS_BODY("a do"); ASSIGN_ASTID_OR_RET(do_ast->for_stmt.body, parse_stmt(c), poisoned_ast); if (try_consume(c, TOKEN_EOS)) @@ -526,6 +537,7 @@ static inline Ast *parse_case_stmts(ParseContext *c, TokenType case_type, TokenT AstId *next = &compound->compound_stmt.first_stmt; while (!token_type_ends_case(c->tok, case_type, default_type)) { + CHECK_HAS_BODY("a case"); ASSIGN_AST_OR_RET(Ast *stmt, parse_stmt(c), poisoned_ast); ast_append(&next, stmt); } @@ -562,11 +574,13 @@ static inline Ast* parse_defer_stmt(ParseContext *c) first->declare_stmt = decl; advance_and_verify(c, TOKEN_IDENT); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); + CHECK_HAS_BODY("a defer-catch"); ASSIGN_ASTID_OR_RET(first->next, parse_stmt(c), poisoned_ast); compound->compound_stmt.first_stmt = astid(first); defer_stmt->defer_stmt.body = astid(compound); return defer_stmt; } + CHECK_HAS_BODY("a defer"); ASSIGN_ASTID_OR_RET(defer_stmt->defer_stmt.body, parse_stmt(c), poisoned_ast); return defer_stmt; } @@ -586,6 +600,7 @@ static inline Ast* parse_while_stmt(ParseContext *c) ASSIGN_EXPRID_OR_RET(while_ast->for_stmt.cond, parse_cond(c), poisoned_ast); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); unsigned row = c->prev_span.row; + CHECK_HAS_BODY("a while"); ASSIGN_AST_OR_RET(Ast *body, parse_stmt(c), poisoned_ast); if (body->ast_kind != AST_COMPOUND_STMT && row != body->span.row) { @@ -625,6 +640,7 @@ static inline Ast* parse_if_stmt(ParseContext *c) CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); unsigned next_row = c->span.row; + CHECK_HAS_BODY("an if"); ASSIGN_ASTID_OR_RET(if_ast->if_stmt.then_body, parse_stmt(c), poisoned_ast); if (row != next_row && astptr(if_ast->if_stmt.then_body)->ast_kind != AST_COMPOUND_STMT) { @@ -633,6 +649,7 @@ static inline Ast* parse_if_stmt(ParseContext *c) } if (try_consume(c, TOKEN_ELSE)) { + CHECK_HAS_BODY("an else"); ASSIGN_ASTID_OR_RET(if_ast->if_stmt.else_body, parse_stmt(c), poisoned_ast); } return if_ast; @@ -813,6 +830,7 @@ static inline Ast* parse_for_stmt(ParseContext *c) // Ast range does not include the body RANGE_EXTEND_PREV(ast); unsigned row = c->prev_span.row; + CHECK_HAS_BODY("a for"); ASSIGN_AST_OR_RET(Ast *body, parse_stmt(c), poisoned_ast); if (body->ast_kind != AST_COMPOUND_STMT && row != body->span.row) { @@ -889,6 +907,7 @@ static inline Ast* parse_foreach_stmt(ParseContext *c) CONSUME_OR_RET(TOKEN_RPAREN, poisoned_ast); RANGE_EXTEND_PREV(ast); + CHECK_HAS_BODY("a foreach"); ASSIGN_ASTID_OR_RET(ast->foreach_stmt.body, parse_stmt(c), poisoned_ast); return ast; } @@ -1470,7 +1489,7 @@ Ast *parse_stmt(ParseContext *c) case TOKEN_UNDERSCORE: case TOKEN_UNION: PRINT_ERROR_HERE("Unexpected '%s' found when expecting a statement.", - token_type_to_string(c->tok)); + token_type_to_string(c->tok)); advance(c); return poisoned_ast; case TOKEN_RPAREN: diff --git a/test/test_suite/statements/missing_body.c3 b/test/test_suite/statements/missing_body.c3 new file mode 100644 index 000000000..5634ab029 --- /dev/null +++ b/test/test_suite/statements/missing_body.c3 @@ -0,0 +1,4 @@ +fn void main() +{ + if (true) // #error: The body of an if statement +}