From 988549599d2f93f291f40008b671f86d6bd4f971 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Thu, 10 Jul 2025 18:31:38 +0200 Subject: [PATCH] `$is_const` is deprecated in favour of `@is_const` based on `$defined`. `$foo` variables could be assigned non-compile time values. `$foo[0] = ...` was incorrectly requiring that the assigned values were compile time constants. --- lib/std/core/values.c3 | 6 ++++++ releasenotes.md | 4 ++++ src/compiler/parse_expr.c | 19 +++++++++--------- src/compiler/sema_expr.c | 17 +++++++++------- .../test_suite/compile_time/var_must_be_ct.c3 | 20 +++++++++++++++++++ 5 files changed, 50 insertions(+), 16 deletions(-) create mode 100644 test/test_suite/compile_time/var_must_be_ct.c3 diff --git a/lib/std/core/values.c3 b/lib/std/core/values.c3 index 5faf24095..6e3ecb360 100644 --- a/lib/std/core/values.c3 +++ b/lib/std/core/values.c3 @@ -20,6 +20,12 @@ macro bool @is_vector(#value) @const => types::is_vector($typeof(#value)); macro bool @is_same_vector_type(#value1, #value2) @const => types::is_same_vector_type($typeof(#value1), $typeof(#value2)); macro bool @assign_to(#value1, #value2) @const => $assignable(#value1, $typeof(#value2)); macro bool @is_lvalue(#value) => $defined(#value = #value); +macro bool @assignable_to(#foo, $Type) @const @builtin => $defined(*&&($Type){} = #foo); +macro bool @is_const(#foo) @const @builtin +{ + var $v; + return $defined($v = #foo); +} macro promote_int(x) { diff --git a/releasenotes.md b/releasenotes.md index e916cb1ee..7a4500fe8 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -15,6 +15,8 @@ - Formatting option "%h" now supports pointers. - Improve error on unsigned implicit conversion to signed. - Update error message for struct initialization #2286 +- `$is_const` is deprecated in favour of `@is_const` based on `$defined`. + ### Fixes - mkdir/rmdir would not work properly with substring paths on non-windows platforms. - Hex string formatter check incorrectly rejected slices. @@ -41,6 +43,8 @@ - Function pointers are now compile time constants. - Splat 8 arguments can sometimes cause incorrect behaviour in the compiler. #2283 - Correctly poison the analysis after a failed $assert or $error. #2284 +- `$foo` variables could be assigned non-compile time values. +- `$foo[0] = ...` was incorrectly requiring that the assigned values were compile time constants. ### Stdlib changes - Improve contract for readline. #2280 diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 563a7f0fd..534909a27 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -1097,7 +1097,7 @@ static Expr *parse_access_expr(ParseContext *c, Expr *left, SourceSpan lhs_start return access_expr; } -static Expr *parse_ct_ident(ParseContext *c, Expr *left, SourceSpan lhs_span) +static Expr *parse_ct_ident(ParseContext *c, Expr *left, SourceSpan lhs_span UNUSED) { ASSERT(!left && "Unexpected left hand side"); if (try_consume(c, TOKEN_CT_CONST_IDENT)) @@ -1112,7 +1112,7 @@ static Expr *parse_ct_ident(ParseContext *c, Expr *left, SourceSpan lhs_span) } -static Expr *parse_hash_ident(ParseContext *c, Expr *left, SourceSpan lhs_span) +static Expr *parse_hash_ident(ParseContext *c, Expr *left, SourceSpan lhs_span UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *expr = EXPR_NEW_TOKEN(EXPR_HASH_IDENT); @@ -1125,7 +1125,7 @@ static Expr *parse_hash_ident(ParseContext *c, Expr *left, SourceSpan lhs_span) /** * ct_eval ::= CT_EVAL '(' expr ')' */ -static Expr *parse_ct_eval(ParseContext *c, Expr *left, SourceSpan lhs_start) +static Expr *parse_ct_eval(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *expr = EXPR_NEW_TOKEN(EXPR_CT_EVAL); @@ -1138,7 +1138,7 @@ static Expr *parse_ct_eval(ParseContext *c, Expr *left, SourceSpan lhs_start) } -static Expr *parse_ct_defined(ParseContext *c, Expr *left, SourceSpan lhs_start) +static Expr *parse_ct_defined(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *defined = expr_new(EXPR_CT_DEFINED, c->span); @@ -1153,7 +1153,7 @@ static Expr *parse_ct_defined(ParseContext *c, Expr *left, SourceSpan lhs_start) * * Note that this is tranformed to $typeof(expr).sizeof. */ -static Expr *parse_ct_sizeof(ParseContext *c, Expr *left, SourceSpan lhs_start) +static Expr *parse_ct_sizeof(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *access = expr_new(EXPR_ACCESS_UNRESOLVED, c->span); @@ -1178,7 +1178,7 @@ static Expr *parse_ct_sizeof(ParseContext *c, Expr *left, SourceSpan lhs_start) /** * ct_is_const ::= CT_IS_CONST '(' expr ')' */ -static Expr *parse_ct_is_const(ParseContext *c, Expr *left, SourceSpan lhs_start) +static Expr *parse_ct_is_const(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *checks = expr_new(EXPR_CT_IS_CONST, c->span); @@ -1187,13 +1187,14 @@ static Expr *parse_ct_is_const(ParseContext *c, Expr *left, SourceSpan lhs_start ASSIGN_EXPR_OR_RET(checks->inner_expr, parse_expr(c), poisoned_expr); CONSUME_OR_RET(TOKEN_RPAREN, poisoned_expr); RANGE_EXTEND_PREV(checks); + SEMA_DEPRECATED(checks, "The $is_const macro is deprecated. Use @is_const(...) instead."); return checks; } /** * ct_checks ::= CT_EMBED '(' constant_expr (',' constant_expr)? ')' */ -static Expr *parse_ct_embed(ParseContext *c, Expr *left, SourceSpan lhs_start) +static Expr *parse_ct_embed(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *embed = expr_new(EXPR_EMBED, c->span); @@ -1213,7 +1214,7 @@ static Expr *parse_ct_embed(ParseContext *c, Expr *left, SourceSpan lhs_start) * ct_call ::= (CT_ALIGNOF | CT_FEATURE | CT_EXTNAMEOF | CT_OFFSETOF | CT_NAMEOF | CT_QNAMEOF) '(' flat_path ')' * flat_path ::= expr ('.' primary) | '[' expr ']')* */ -static Expr *parse_ct_call(ParseContext *c, Expr *left, SourceSpan lhs_start) +static Expr *parse_ct_call(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *expr = EXPR_NEW_TOKEN(EXPR_CT_CALL); @@ -1230,7 +1231,7 @@ static Expr *parse_ct_call(ParseContext *c, Expr *left, SourceSpan lhs_start) return expr; } -static Expr *parse_ct_assignable(ParseContext *c, Expr *left, SourceSpan lhs_start) +static Expr *parse_ct_assignable(ParseContext *c, Expr *left, SourceSpan lhs_start UNUSED) { ASSERT(!left && "Unexpected left hand side"); Expr *expr = EXPR_NEW_TOKEN(EXPR_CT_ASSIGNABLE); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index f21451caf..14c57b19a 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -92,7 +92,7 @@ static bool sema_expr_check_shift_rhs(SemaContext *context, Expr *expr, Expr *le is_assign); static bool sema_expr_analyse_and_or(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref); static bool sema_expr_analyse_slice_assign(SemaContext *context, Expr *expr, Type *left_type, Expr *right, bool *failed_ref); -static bool sema_expr_analyse_ct_identifier_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right); +static bool sema_expr_analyse_ct_identifier_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref); static bool sema_expr_analyse_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref); static bool sema_expr_analyse_comp(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref); static bool sema_expr_analyse_op_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, BinaryOp operator); @@ -6538,17 +6538,20 @@ bool sema_expr_analyse_assign_right_side(SemaContext *context, Expr *expr, Type return true; } -static bool sema_expr_analyse_ct_identifier_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right) +static bool sema_expr_analyse_ct_identifier_assign(SemaContext *context, Expr *expr, Expr *left, Expr *right, bool *failed_ref) { - // Do regular lvalue evaluation of the identifier - if (!sema_analyse_expr_lvalue(context, left, NULL)) return false; + ASSERT_SPAN(left, left->resolve_status == RESOLVE_DONE); // Evaluate right side to using inference from last type. if (!sema_analyse_inferred_expr(context, left->type, right)) return false; + if (!expr_is_runtime_const(right)) + { + if (failed_ref) return *failed_ref = true, false; + RETURN_SEMA_ERROR(right, "You can only assign constants to a compile time variable."); + } Decl *ident = left->ct_ident_expr.decl; - ident->var.init_expr = right; expr_replace(expr, right); ident->type = right->type; @@ -6565,7 +6568,7 @@ static bool sema_expr_analyse_ct_subscript_rhs(SemaContext *context, Decl *ct_va { if (!sema_analyse_expr_rhs(context, type_get_indexed_type(ct_var->type), right, false, NULL, false)) return false; } - if (!sema_cast_const(right)) + if (!expr_is_runtime_const(right)) { RETURN_SEMA_ERROR(right, "The argument must be a constant value."); } @@ -6654,7 +6657,7 @@ static bool sema_expr_analyse_assign(SemaContext *context, Expr *expr, Expr *lef { case EXPR_CT_IDENT: // $foo = ... - return sema_expr_analyse_ct_identifier_assign(context, expr, left, right); + return sema_expr_analyse_ct_identifier_assign(context, expr, left, right, failed_ref); case EXPR_CT_SUBSCRIPT: return sema_expr_analyse_ct_subscript_assign(context, expr, left, right); default: diff --git a/test/test_suite/compile_time/var_must_be_ct.c3 b/test/test_suite/compile_time/var_must_be_ct.c3 new file mode 100644 index 000000000..4bcd94409 --- /dev/null +++ b/test/test_suite/compile_time/var_must_be_ct.c3 @@ -0,0 +1,20 @@ +fn void test1() +{ + int a; + var $x; + $x = a; // #error: only assign constants to a compile time +} +fn void test2() +{ + int a; + int[2] $x = { 1, 2 }; + $x[0] = a; // #error: argument must be a constant value +} + +int g; +fn void test3() +{ + int*[2] $x = { &g + 1, null }; + int* $y = &g + 1; + $x[0] = &g + 1; +}