From 380c1c8ab49ee918e42848763b9075c83a43a179 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Thu, 30 Jul 2020 17:43:12 +0200 Subject: [PATCH] Support for $assert. Also change order of checking global declarations from parsing to semantic analysis. --- resources/testfragments/super_simple.c3 | 12 +++++++ src/compiler/ast.c | 5 +++ src/compiler/compiler.c | 16 ++++++++- src/compiler/compiler_internal.h | 11 +++++++ src/compiler/enums.h | 2 ++ src/compiler/llvm_codegen_stmt.c | 1 + src/compiler/parse_stmt.c | 18 ++++++++++ src/compiler/parser.c | 15 +++++++-- src/compiler/parser_internal.h | 1 + src/compiler/sema_expr.c | 5 ++- src/compiler/sema_passes.c | 20 +++++++++++ src/compiler/sema_stmts.c | 33 +++++++++++++++++++ src/compiler/tokens.c | 2 ++ .../global_static_assert_not_constant.c3 | 3 ++ .../local_static_assert_not_constant.c3 | 20 +++++++++++ test/test_suite/assert/static_assert.c3 | 8 +++++ test/test_suite/globals/global_init.c3 | 7 ---- test/test_suite/subarrays/sub_array_init.c3 | 7 ++++ test/test_suite/visibility/ambiguous_var.c3t | 27 +++++++++++++++ 19 files changed, 201 insertions(+), 12 deletions(-) create mode 100644 test/test_suite/assert/global_static_assert_not_constant.c3 create mode 100644 test/test_suite/assert/local_static_assert_not_constant.c3 create mode 100644 test/test_suite/assert/static_assert.c3 create mode 100644 test/test_suite/subarrays/sub_array_init.c3 create mode 100644 test/test_suite/visibility/ambiguous_var.c3t diff --git a/resources/testfragments/super_simple.c3 b/resources/testfragments/super_simple.c3 index 0f73da979..43bf5d243 100644 --- a/resources/testfragments/super_simple.c3 +++ b/resources/testfragments/super_simple.c3 @@ -1215,6 +1215,18 @@ public func int! decode(char[] infile, byte[] out) return j; } */ + +int xx = 0; +const int FOOP = 2; + +$assert(xx == 0); + +func void test22() +{ + $assert(FOOP == 2, "Bad"); + $assert(FOOP == 0, "Good"); +} + func int main(int x) { diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 9b0e73684..45971d973 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -925,6 +925,11 @@ static void fprint_ast_recursive(Context *context, FILE *file, Ast *ast, int ind if (!ast) return; switch (ast->ast_kind) { + case AST_CT_ASSERT: + DUMP("($assert"); + DUMPEXPR(ast->ct_assert_stmt.expr); + DUMPEXPR(ast->ct_assert_stmt.message); + DUMPEND(); case AST_TRY_STMT: DUMP("(try"); DUMPEXPR(ast->try_stmt.decl_expr); diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 8992237f6..d82ef48ff 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -71,12 +71,12 @@ void compiler_parse(BuildTarget *target) void compiler_compile(BuildTarget *target) { Context **contexts = NULL; + diag_reset(); VECEACH(target->sources, i) { bool loaded = false; File *file = source_file_load(target->sources[i], &loaded); if (loaded) continue; - diag_reset(); Context *context = context_create(file, target); vec_add(contexts, context); parse_file(context); @@ -102,6 +102,12 @@ void compiler_compile(BuildTarget *target) } if (diagnostics.errors > 0) exit(EXIT_FAILURE); + VECEACH(contexts, i) + { + sema_analysis_pass_register_globals(contexts[i]); + } + if (diagnostics.errors > 0) exit(EXIT_FAILURE); + VECEACH(contexts, i) { sema_analysis_pass_conditional_compilation(contexts[i]); @@ -112,6 +118,14 @@ void compiler_compile(BuildTarget *target) { sema_analysis_pass_decls(contexts[i]); } + if (diagnostics.errors > 0) exit(EXIT_FAILURE); + + VECEACH(contexts, i) + { + sema_analysis_pass_ct_assert(contexts[i]); + } + if (diagnostics.errors > 0) exit(EXIT_FAILURE); + VECEACH(contexts, i) { sema_analysis_pass_functions(contexts[i]); diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index a244e160a..975fcfc87 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -915,6 +915,11 @@ typedef struct TokenId **instructions; } AstAsmStmt; +typedef struct +{ + Expr *message; + Expr *expr; +} AstCtAssertStmt; typedef struct _Ast { @@ -947,6 +952,7 @@ typedef struct _Ast Ast *ct_else_stmt; // 8 AstCtForStmt ct_for_stmt; // 64 AstScopedStmt scoped_stmt; // 16 + AstCtAssertStmt ct_assert_stmt; }; } Ast; @@ -1024,6 +1030,7 @@ typedef struct _Context Decl** imports; Module *module; STable local_symbols; + Decl **global_decls; Decl **enums; Decl **types; Decl **functions; @@ -1031,6 +1038,7 @@ typedef struct _Context Decl **vars; Decl **incr_array; Decl **ct_ifs; + Ast **ct_asserts; Ast **defers; Decl *active_function_for_analysis; Token *comments; @@ -1345,8 +1353,10 @@ const char *resolve_status_to_string(ResolveStatus status); #define SEMA_PREV(_node, ...) sema_prev_at_range3((_node)->span, __VA_ARGS__) void sema_analysis_pass_process_imports(Context *context); +void sema_analysis_pass_register_globals(Context *context); void sema_analysis_pass_conditional_compilation(Context *context); void sema_analysis_pass_decls(Context *context); +void sema_analysis_pass_ct_assert(Context *context); void sema_analysis_pass_functions(Context *context); bool sema_add_member(Context *context, Decl *decl); @@ -1354,6 +1364,7 @@ bool sema_add_local(Context *context, Decl *decl); bool sema_unwrap_var(Context *context, Decl *decl); bool sema_rewrap_var(Context *context, Decl *decl); +bool sema_analyse_ct_assert_stmt(Context *context, Ast *statement); bool sema_analyse_statement(Context *context, Ast *statement); Decl *sema_resolve_symbol_in_current_dynamic_scope(Context *context, const char *symbol); Decl *sema_resolve_symbol(Context *context, const char *symbol, Path *path, Decl **ambiguous_other_decl, Decl **private_decl); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 4a7368af1..ecfd71b47 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -57,6 +57,7 @@ typedef enum AST_COMPOUND_STMT, AST_CONTINUE_STMT, AST_DEFINE_STMT, + AST_CT_ASSERT, AST_CT_IF_STMT, AST_CT_ELIF_STMT, AST_CT_ELSE_STMT, @@ -415,6 +416,7 @@ typedef enum TOKEN_WHILE, TOKEN_TYPEOF, + TOKEN_CT_ASSERT, // $assert TOKEN_CT_CASE, // $case TOKEN_CT_DEFAULT, // $default TOKEN_CT_FOR, // $for diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index 8f228ba76..0a24c86fa 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -938,6 +938,7 @@ void gencontext_emit_stmt(GenContext *context, Ast *ast) break; case AST_ASM_STMT: TODO + case AST_CT_ASSERT: case AST_CT_IF_STMT: case AST_CT_ELIF_STMT: case AST_CT_ELSE_STMT: diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 37c0d0da4..3ff676f57 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -767,8 +767,24 @@ static inline Ast* parse_ct_switch_stmt(Context *context) return ast; } + #pragma mark --- External functions +Ast *parse_ct_assert_stmt(Context *context) +{ + Ast *ast = AST_NEW_TOKEN(AST_CT_ASSERT, context->tok); + advance_and_verify(context, TOKEN_CT_ASSERT); + TRY_CONSUME_OR(TOKEN_LPAREN, "'$assert' needs a '(' here, did you forget it?", poisoned_ast); + ast->ct_assert_stmt.expr = TRY_EXPR_OR(parse_expr(context), poisoned_ast); + if (try_consume(context, TOKEN_COMMA)) + { + ast->ct_assert_stmt.message = TRY_EXPR_OR(parse_expr(context), poisoned_ast); + } + TRY_CONSUME_OR(TOKEN_RPAREN, "The ending ')' was expected here.", poisoned_ast); + TRY_CONSUME_EOS(); + return ast; +} + Ast *parse_stmt(Context *context) { switch (context->tok.type) @@ -873,6 +889,8 @@ Ast *parse_stmt(Context *context) SEMA_TOKEN_ERROR(context->tok, "'default' was found outside of 'switch', did you mismatch a '{ }' pair?"); advance(context); return poisoned_ast; + case TOKEN_CT_ASSERT: + return parse_ct_assert_stmt(context); case TOKEN_CT_IF: return parse_ct_if_stmt(context); case TOKEN_CT_SWITCH: diff --git a/src/compiler/parser.c b/src/compiler/parser.c index f5fead99f..a96154942 100644 --- a/src/compiler/parser.c +++ b/src/compiler/parser.c @@ -1627,6 +1627,7 @@ static inline bool check_no_visibility_before(Context *context, Visibility visib */ static inline Decl *parse_top_level(Context *context) { + Visibility visibility = VISIBLE_MODULE; switch (context->tok.type) { @@ -1653,8 +1654,15 @@ static inline Decl *parse_top_level(Context *context) return parse_attribute_declaration(context, visibility); case TOKEN_FUNC: return parse_func_definition(context, visibility, false); + case TOKEN_CT_ASSERT: + if (!check_no_visibility_before(context, visibility)) return poisoned_decl; + { + Ast *ast = TRY_AST_OR(parse_ct_assert_stmt(context), false); + vec_add(context->ct_asserts, ast); + return NULL; + } case TOKEN_CT_IF: - if (!check_no_visibility_before(context, visibility)) return false; + if (!check_no_visibility_before(context, visibility)) return poisoned_decl; return parse_ct_if_top_level(context); case TOKEN_CONST: return parse_const_declaration(context, visibility); @@ -1676,7 +1684,7 @@ static inline Decl *parse_top_level(Context *context) // All of these start type return parse_global_declaration(context, visibility); case TOKEN_IDENT: - if (!check_no_visibility_before(context, visibility)) return false; + if (!check_no_visibility_before(context, visibility)) return poisoned_decl; return parse_incremental_array(context); case TOKEN_EOF: assert(visibility != VISIBLE_MODULE); @@ -1924,9 +1932,10 @@ static inline void parse_current(Context *context) while (!TOKEN_IS(TOKEN_EOF)) { Decl *decl = parse_top_level(context); + if (!decl) continue; if (decl_ok(decl)) { - context_register_global_decl(context, decl); + vec_add(context->global_decls, decl); } else { diff --git a/src/compiler/parser_internal.h b/src/compiler/parser_internal.h index fefcc56f4..62755fdf8 100644 --- a/src/compiler/parser_internal.h +++ b/src/compiler/parser_internal.h @@ -34,6 +34,7 @@ typedef enum DECL_PARSE_NORMAL, DECL_PARSE_UNWRAP } DeclParse; +Ast *parse_ct_assert_stmt(Context *context); Ast *parse_stmt(Context *context); Path *parse_path_prefix(Context *context, bool *had_error); Expr *parse_type_expression_with_path(Context *context, Path *path); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 96ae835ee..9c9ded972 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -87,7 +87,7 @@ int sema_check_comp_time_bool(Context *context, Expr *expr) if (!sema_analyse_expr_of_required_type(context, type_bool, expr, false)) return -1; if (expr->expr_kind != EXPR_CONST) { - SEMA_ERROR(expr, "$if requires a compile time constant value."); + SEMA_ERROR(expr, "Compile time evaluation requires a compile time constant value."); return -1; } return expr->const_expr.b; @@ -3163,6 +3163,9 @@ static Ast *ast_copy_from_macro(Context *context, Ast *source) return ast; case AST_ASM_STMT: TODO + case AST_CT_ASSERT: + MACRO_COPY_EXPR(ast->ct_assert_stmt.expr); + return ast; case AST_BREAK_STMT: return ast; case AST_TRY_STMT: diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index ae263c9a9..3431af8d7 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -36,6 +36,16 @@ void sema_analysis_pass_process_imports(Context *context) DEBUG_LOG("Pass finished with %d error(s).", diagnostics.errors); } +void sema_analysis_pass_register_globals(Context *context) +{ + DEBUG_LOG("Pass: Register globals for %s", context->file->name); + VECEACH(context->global_decls, i) + { + context_register_global_decl(context, context->global_decls[i]); + } + vec_resize(context->global_decls, 0); + DEBUG_LOG("Pass finished with %d error(s).", diagnostics.errors); +} static inline void sema_append_decls(Context *context, Decl **decls) { @@ -92,6 +102,16 @@ void sema_analysis_pass_conditional_compilation(Context *context) DEBUG_LOG("Pass finished with %d error(s).", diagnostics.errors); } +void sema_analysis_pass_ct_assert(Context *context) +{ + DEBUG_LOG("Pass: $assert checks %s", context->file->name); + VECEACH(context->ct_asserts, i) + { + sema_analyse_ct_assert_stmt(context, context->ct_asserts[i]); + } + DEBUG_LOG("Pass finished with %d error(s).", diagnostics.errors); +} + static inline bool analyse_func_body(Context *context, Decl *decl) { if (!decl->func.body) return true; diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index ed2d80761..844bd0d82 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -1221,6 +1221,37 @@ static bool sema_analyse_volatile_stmt(Context *context, Ast *statement) return result; } +bool sema_analyse_ct_assert_stmt(Context *context, Ast *statement) +{ + Expr *expr = statement->ct_assert_stmt.expr; + Expr *message = statement->ct_assert_stmt.message; + if (message) + { + if (!sema_analyse_expr(context, type_string, message)) return false; + if (message->type->type_kind != TYPE_STRING) + { + SEMA_ERROR(message, "Expected a string as the error message."); + } + } + int res = sema_check_comp_time_bool(context, expr); + + if (res == -1) return false; + if (!res) + { + if (message) + { + SEMA_ERROR(expr, "Compile time assert - %.*s", message->const_expr.string.len, message->const_expr.string.chars); + } + else + { + SEMA_ERROR(expr, "Compile time assert failed."); + } + return false; + } + statement->ast_kind = AST_NOP_STMT; + return true; +} + static bool sema_analyse_compound_stmt(Context *context, Ast *statement) { context_push_scope(context); @@ -1248,6 +1279,8 @@ static inline bool sema_analyse_statement_inner(Context *context, Ast *statement case AST_POISONED: case AST_SCOPED_STMT: UNREACHABLE + case AST_CT_ASSERT: + return sema_analyse_ct_assert_stmt(context, statement); case AST_DEFINE_STMT: return sema_analyse_define_stmt(context, statement); case AST_ASM_STMT: diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index df7cc6f6d..06ff095a2 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -326,6 +326,8 @@ const char *token_type_to_string(TokenType type) case TOKEN_DOCS_LINE: return "DOCS_LINE"; + case TOKEN_CT_ASSERT: + return "$assert"; case TOKEN_CT_CASE: return "$case"; case TOKEN_CT_DEFAULT: diff --git a/test/test_suite/assert/global_static_assert_not_constant.c3 b/test/test_suite/assert/global_static_assert_not_constant.c3 new file mode 100644 index 000000000..9d1c3496d --- /dev/null +++ b/test/test_suite/assert/global_static_assert_not_constant.c3 @@ -0,0 +1,3 @@ +int x = 3; + +$assert(x == 3); // #error: Compile time evaluation requires a compile time constant value. \ No newline at end of file diff --git a/test/test_suite/assert/local_static_assert_not_constant.c3 b/test/test_suite/assert/local_static_assert_not_constant.c3 new file mode 100644 index 000000000..0f13c8202 --- /dev/null +++ b/test/test_suite/assert/local_static_assert_not_constant.c3 @@ -0,0 +1,20 @@ +int x = 3; + +func void test() +{ + $assert(x == 3); // #error: Compile time evaluation requires a compile time constant value. +} + +func void test2() +{ + int i = 0; + $assert(1); + $assert(i == 0); // #error: Compile time evaluation requires a compile time constant value. +} + +func int foo(); +func void test3() +{ + int i = 0; + $assert(foo() == 0); // #error: Compile time evaluation requires a compile time constant value. +} diff --git a/test/test_suite/assert/static_assert.c3 b/test/test_suite/assert/static_assert.c3 new file mode 100644 index 000000000..2a139c833 --- /dev/null +++ b/test/test_suite/assert/static_assert.c3 @@ -0,0 +1,8 @@ + +const int FOO = 2; + +func void test() +{ + $assert(FOO == 2, "Bad"); + $assert(FOO == 0, "Good"); // #error: Compile time assert - Good +} \ No newline at end of file diff --git a/test/test_suite/globals/global_init.c3 b/test/test_suite/globals/global_init.c3 index ea87d0c13..e18c83e73 100644 --- a/test/test_suite/globals/global_init.c3 +++ b/test/test_suite/globals/global_init.c3 @@ -3,13 +3,6 @@ //char[] str3 = "hello"; -func void test2() -{ - int[2] a = { 1, 2 }; - - int[2] b = 30; // #error: Cannot implicitly cast 'compint' to 'int[2]' - int[2] c = a; -} int[2] a1 = { 1, 2 }; diff --git a/test/test_suite/subarrays/sub_array_init.c3 b/test/test_suite/subarrays/sub_array_init.c3 new file mode 100644 index 000000000..a8ec1120b --- /dev/null +++ b/test/test_suite/subarrays/sub_array_init.c3 @@ -0,0 +1,7 @@ +func void test2() +{ + int[2] a = { 1, 2 }; + + int[2] b = 30; // #error: Cannot implicitly cast 'compint' to 'int[2]' + int[2] c = a; +} diff --git a/test/test_suite/visibility/ambiguous_var.c3t b/test/test_suite/visibility/ambiguous_var.c3t new file mode 100644 index 000000000..1d689d516 --- /dev/null +++ b/test/test_suite/visibility/ambiguous_var.c3t @@ -0,0 +1,27 @@ +// #file: file1.c3 +module foo; + +public int a; +public int b; + +// #file: file2.c3 +module bar; +public int a; +public int b; + +// #file: file3.c3 + +module baz; +import foo; +import bar; + +public int a; + +func void test2() +{ + int c = a; // This is fine. + c = foo::b; + c = bar::b; + c = foo::a; + c = b; // #error: Ambiguous symbol 'b' – both defined in foo and bar, please add the module name to resolve the ambiguity +}