diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 538dc5925..12820d699 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -290,6 +290,7 @@ bool expr_is_pure(Expr *expr) case EXPR_TYPEID: case EXPR_CT_ARG: case EXPR_OPERATOR_CHARS: + case EXPR_CT_CHECKS: return true; case EXPR_VASPLAT: return true; diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 1eab3a948..a52d4a520 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -284,6 +284,7 @@ Expr *copy_expr(CopyStruct *c, Expr *source_expr) case EXPR_GROUP: case EXPR_STRINGIFY: case EXPR_CT_EVAL: + case EXPR_CT_CHECKS: MACRO_COPY_EXPR(expr->inner_expr); return expr; case EXPR_TYPEID_INFO: diff --git a/src/compiler/enums.h b/src/compiler/enums.h index c3bf2e653..a1342ba7f 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -211,6 +211,7 @@ typedef enum EXPR_CATCH_UNWRAP, EXPR_COMPOUND_LITERAL, EXPR_CONST, + EXPR_CT_CHECKS, EXPR_CT_CALL, EXPR_CT_CONV, EXPR_CT_IDENT, @@ -343,6 +344,7 @@ typedef enum typedef enum { SCOPE_NONE = 0, + SCOPE_CHECKS = 1 << 0, SCOPE_EXPR_BLOCK = 1 << 5, SCOPE_MACRO = 1 << 6, } ScopeFlags; @@ -544,6 +546,7 @@ typedef enum TOKEN_CT_ALIGNOF, // $alignof TOKEN_CT_ASSERT, // $assert TOKEN_CT_CASE, // $case + TOKEN_CT_CHECKS, // $checks TOKEN_CT_DEFAULT, // $default TOKEN_CT_DEFINED, // $defined TOKEN_CT_FOR, // $for diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index eeeaabc9a..c676ae1b3 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -5545,6 +5545,7 @@ void llvm_emit_expr(GenContext *c, BEValue *value, Expr *expr) case EXPR_CT_ARG: case EXPR_ASM: case EXPR_VASPLAT: + case EXPR_CT_CHECKS: UNREACHABLE case EXPR_BUILTIN_ACCESS: llvm_emit_builtin_access(c, value, expr); diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 1b581d228..e62a9ef37 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -886,6 +886,18 @@ static Expr *parse_ct_sizeof(ParseContext *c, Expr *left) return access; } +static Expr *parse_ct_checks(ParseContext *c, Expr *left) +{ + assert(!left && "Unexpected left hand side"); + Expr *checks = expr_new(EXPR_CT_CHECKS, c->span); + advance_and_verify(c, TOKEN_CT_CHECKS); + CONSUME_OR_RET(TOKEN_LPAREN, poisoned_expr); + ASSIGN_EXPR_OR_RET(checks->inner_expr, parse_expression_list(c, true), poisoned_expr); + CONSUME_OR_RET(TOKEN_RPAREN, poisoned_expr); + RANGE_EXTEND_PREV(checks); + return checks; +} + static Expr *parse_ct_call(ParseContext *c, Expr *left) { assert(!left && "Unexpected left hand side"); @@ -1762,6 +1774,7 @@ ParseRule rules[TOKEN_EOF + 1] = { [TOKEN_CT_SIZEOF] = { parse_ct_sizeof, NULL, PREC_NONE }, [TOKEN_CT_ALIGNOF] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_DEFINED] = { parse_ct_call, NULL, PREC_NONE }, + [TOKEN_CT_CHECKS] = { parse_ct_checks, NULL, PREC_NONE }, [TOKEN_CT_EVAL] = { parse_ct_eval, NULL, PREC_NONE }, [TOKEN_CT_EXTNAMEOF] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_OFFSETOF] = { parse_ct_call, NULL, PREC_NONE }, diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index c66f25206..7cd36539b 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -1204,6 +1204,7 @@ Ast *parse_stmt(ParseContext *c) case TOKEN_CT_QNAMEOF: case TOKEN_CT_NAMEOF: case TOKEN_CT_DEFINED: + case TOKEN_CT_CHECKS: case TOKEN_CT_STRINGIFY: case TOKEN_CT_EVAL: case TOKEN_TRY: diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index b9b9ce9e1..6b8b37c50 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -847,6 +847,7 @@ Expr *recursive_may_narrow_float(Expr *expr, Type *type) case EXPR_ASM: case EXPR_VASPLAT: case EXPR_OPERATOR_CHARS: + case EXPR_CT_CHECKS: UNREACHABLE case EXPR_BUILTIN_ACCESS: @@ -1019,6 +1020,7 @@ Expr *recursive_may_narrow_int(Expr *expr, Type *type) case EXPR_ASM: case EXPR_VASPLAT: case EXPR_OPERATOR_CHARS: + case EXPR_CT_CHECKS: UNREACHABLE case EXPR_POST_UNARY: return recursive_may_narrow_int(expr->unary_expr.expr, type); diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 9d8c6d32a..9c674ae60 100644 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -575,19 +575,14 @@ static bool sema_analyse_bitstruct(SemaContext *context, Decl *decl) return false; } Decl **members = decl->bitstruct.members; - bool success = true; - SCOPE_START - VECEACH(members, i) + VECEACH(members, i) + { + if (!sema_analyse_bitstruct_member(context, decl, i, decl->bitstruct.overlap)) { - if (!sema_analyse_bitstruct_member(context, decl, i, decl->bitstruct.overlap)) - { - success = false; - break; - } + return decl_poison(decl); } - SCOPE_END; - if (!success) return decl_poison(decl); - return decl_ok(decl); + } + return true; } diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 3c614c968..443f95198 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -405,6 +405,7 @@ bool expr_is_constant_eval(Expr *expr, ConstantEvalKind eval_kind) case EXPR_CONST: case EXPR_OPERATOR_CHARS: case EXPR_STRINGIFY: + case EXPR_CT_CHECKS: return true; case EXPR_COND: return expr_list_is_constant_eval(expr->cond_expr, eval_kind); @@ -725,6 +726,7 @@ static bool sema_check_expr_lvalue(Expr *top_expr, Expr *expr) case EXPR_ASM: case EXPR_VASPLAT: case EXPR_OPERATOR_CHARS: + case EXPR_CT_CHECKS: goto ERR; } UNREACHABLE @@ -827,6 +829,7 @@ bool expr_may_addr(Expr *expr) case EXPR_ASM: case EXPR_VASPLAT: case EXPR_OPERATOR_CHARS: + case EXPR_CT_CHECKS: return false; } UNREACHABLE @@ -7115,6 +7118,12 @@ static inline bool sema_expr_analyse_ct_incdec(SemaContext *context, Expr *expr, return false; } + if (var->var.scope_depth < context->active_scope.depth) + { + SEMA_ERROR(expr, "Cannot modify '%s' inside of a runtime scope.", var->name); + return false; + } + Expr *end_value = expr_copy(start_value); // Make the change. @@ -8022,6 +8031,33 @@ RETRY: } UNREACHABLE } + +static inline Expr *sema_check_exprlist(SemaContext *context, Expr *exprlist) +{ + assert(exprlist->expr_kind == EXPR_EXPRESSION_LIST); + Expr *failed = NULL; + bool suppress_error = global_context.suppress_errors; + global_context.suppress_errors = true; + SCOPE_START_WITH_FLAGS(SCOPE_CHECKS); + FOREACH_BEGIN(Expr *expr, exprlist->expression_list) + if (!sema_analyse_cond_expr(context, expr)) + { + failed = expr; + break; + } + FOREACH_END(); + SCOPE_END; + global_context.suppress_errors = suppress_error; + return failed; +} + +static inline bool sema_expr_analyse_ct_checks(SemaContext *context, Expr *expr) +{ + Expr *err = sema_check_exprlist(context, expr->inner_expr); + expr_rewrite_const_bool(expr, type_bool, err == NULL); + return true; +} + static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr) { if (expr->resolve_status == RESOLVE_DONE) return expr_ok(expr); @@ -8426,6 +8462,8 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr) case EXPR_VASPLAT: SEMA_ERROR(expr, "'$vasplat' can only be used inside of macros."); return false; + case EXPR_CT_CHECKS: + return sema_expr_analyse_ct_checks(context, expr); case EXPR_CT_ARG: return sema_expr_analyse_ct_arg(context, expr); case EXPR_VARIANT: diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 58e1e76ea..20a9ab116 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -2515,11 +2515,10 @@ static bool sema_analyse_checked(SemaContext *context, Ast *directive, AstId **a bool success = true; bool suppress_error = global_context.suppress_errors; global_context.suppress_errors = true; - SCOPE_START + SCOPE_START_WITH_FLAGS(SCOPE_CHECKS) VECEACH(declexpr->cond_expr, j) { Expr *expr = declexpr->cond_expr[j]; - const char *comment = directive->doc_stmt.contract.comment; global_context.suppress_errors = comment != NULL; if (!sema_analyse_cond_expr(context, expr)) diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index fdb7d5818..b52c73485 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -330,6 +330,8 @@ const char *token_type_to_string(TokenType type) return "$assert"; case TOKEN_CT_CASE: return "$case"; + case TOKEN_CT_CHECKS: + return "$checks"; case TOKEN_CT_DEFAULT: return "$default"; case TOKEN_CT_DEFINED: diff --git a/src/version.h b/src/version.h index 7e069e1cb..72f04e659 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.3.52" \ No newline at end of file +#define COMPILER_VERSION "0.3.53" \ No newline at end of file diff --git a/test/test_suite/compile_time/ct_checks.c3t b/test/test_suite/compile_time/ct_checks.c3t new file mode 100644 index 000000000..e571238e5 --- /dev/null +++ b/test/test_suite/compile_time/ct_checks.c3t @@ -0,0 +1,20 @@ +// #target: macos-x64 +module test; +import std::io; + +fn void main() +{ + int a; + bool b = $checks(a = 12.0); + var $y = 23; + bool c = $checks(int z = 23, $y += 23, &c); + bool d = $checks(&c, $y, int yy = 23); + io::printfln("%s %s %s", b, $y, c); +} + +/* #expect: test.ll + + store i32 0 + store i8 0 + store i8 0 + store i8 1 diff --git a/test/test_suite2/compile_time/ct_checks.c3t b/test/test_suite2/compile_time/ct_checks.c3t new file mode 100644 index 000000000..e571238e5 --- /dev/null +++ b/test/test_suite2/compile_time/ct_checks.c3t @@ -0,0 +1,20 @@ +// #target: macos-x64 +module test; +import std::io; + +fn void main() +{ + int a; + bool b = $checks(a = 12.0); + var $y = 23; + bool c = $checks(int z = 23, $y += 23, &c); + bool d = $checks(&c, $y, int yy = 23); + io::printfln("%s %s %s", b, $y, c); +} + +/* #expect: test.ll + + store i32 0 + store i8 0 + store i8 0 + store i8 1