From 463c6957fc271c8ffafb430199aba921f6e2730c Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Wed, 26 Nov 2025 23:54:18 +0100 Subject: [PATCH] - Support `int $foo...` arguments. #2601 --- releasenotes.md | 1 + src/compiler/parse_global.c | 31 +++++++------- src/compiler/sema_decls.c | 2 +- src/compiler/sema_expr.c | 40 ++++++++++++++++++- .../functions/const_vaarg_untyped.c3 | 6 +++ .../functions/expr_vaarg_untyped.c3 | 4 ++ test/test_suite/functions/returning_void.c3t | 1 + .../functions/splat_const_vaarg.c3t | 35 ++++++++++++++++ .../functions/splat_const_vaarg_fail.c3 | 17 ++++++++ 9 files changed, 120 insertions(+), 17 deletions(-) create mode 100644 test/test_suite/functions/const_vaarg_untyped.c3 create mode 100644 test/test_suite/functions/expr_vaarg_untyped.c3 create mode 100644 test/test_suite/functions/splat_const_vaarg.c3t create mode 100644 test/test_suite/functions/splat_const_vaarg_fail.c3 diff --git a/releasenotes.md b/releasenotes.md index df076e919..156d82841 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -17,6 +17,7 @@ - Deprecate `--test-nocapture` in favour of `--test-show-output` #2588. - Xtensa target no longer enabled by default on LLVM 22, Compile with `-DXTENSA_ENABLE` to enable it instead - Add `float[<3>] x = { .xy = 1.2, .z = 3.3 }` swizzle initialization for vectors. #2599 +- Support `int $foo...` arguments. #2601 ### Fixes - `Foo.is_eq` would return false if the type was a `typedef` and had an overload, but the underlying type was not comparable. diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index b49ca7946..26afabb59 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -1583,12 +1583,20 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, i // We reserve upper case constants for globals. PRINT_ERROR_HERE("Parameter names may not be all uppercase."); return false; + case TOKEN_CT_IDENT: + // ct_var $foo + name = symstr(c); + advance_and_verify(c, TOKEN_CT_IDENT); + param_kind = VARDECL_PARAM_CT; + goto CHECK_ELLIPSIS; + break; case TOKEN_IDENT: // normal "foo" name = symstr(c); param_kind = VARDECL_PARAM; advance_and_verify(c, TOKEN_IDENT); // Check for "foo..." which defines an implicit "any" vararg +CHECK_ELLIPSIS: if (try_consume(c, TOKEN_ELLIPSIS)) { // Did we get Foo... foo... @@ -1606,7 +1614,12 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, i // Did we get Foo foo...? If so then that's an error. if (type) { - PRINT_ERROR_LAST("For typed varargs '...', needs to appear after the type."); + PRINT_ERROR_LAST("For typed vaargs '...', needs to appear after the type."); + return false; + } + else if (param_kind == VARDECL_PARAM_CT) + { + PRINT_ERROR_LAST("Untyped constant vaargs are not supported. Use raw macro vaargs instead."); return false; } // This is "foo..." @@ -1615,18 +1628,6 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, i type = type_info_new_base(type_any, c->span); } break; - case TOKEN_CT_IDENT: - // ct_var $foo - name = symstr(c); - advance_and_verify(c, TOKEN_CT_IDENT); - // This will catch Type... $foo and $foo..., neither is allowed. - if (ellipsis || tok_is(c, TOKEN_ELLIPSIS)) - { - PRINT_ERROR_LAST("Compile time parameters may not be varargs, use untyped macro varargs '...' instead."); - return false; - } - param_kind = VARDECL_PARAM_CT; - break; case TOKEN_AMP: // reference &foo advance_and_verify(c, TOKEN_AMP); @@ -1659,7 +1660,7 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, i advance_and_verify(c, TOKEN_HASH_IDENT); if (ellipsis || tok_is(c, TOKEN_ELLIPSIS)) { - PRINT_ERROR_LAST("Expression parameters may not be varargs, use untyped macro varargs '...' instead."); + PRINT_ERROR_LAST("Expression parameters may not be vaargs, use untyped macro vaargs '...' instead."); return false; } param_kind = VARDECL_PARAM_EXPR; @@ -1670,7 +1671,7 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Variadic *variadic, i advance_and_verify(c, TOKEN_CT_TYPE_IDENT); if (ellipsis || tok_is(c, TOKEN_ELLIPSIS)) { - PRINT_ERROR_LAST("Expression parameters may not be varargs, use untyped macro varargs '...' instead."); + PRINT_ERROR_LAST("Expression parameters may not be vaargs, use untyped macro vaargs '...' instead."); return false; } param_kind = VARDECL_PARAM_CT_TYPE; diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index d3dad8cc0..a4787ff5a 100755 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -1411,7 +1411,7 @@ static inline bool sema_analyse_signature(SemaContext *context, Signature *sig, } if (param->var.vararg) { - if (var_kind != VARDECL_PARAM) + if (var_kind != VARDECL_PARAM && var_kind != VARDECL_PARAM_CT) { SEMA_ERROR(param, "Only regular parameters may be vararg."); return decl_poison(param); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 7d9d95d0f..51baa78bf 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -2025,7 +2025,18 @@ INLINE bool sema_call_evaluate_arguments(SemaContext *context, CalledDecl *calle if (type_is_arraylike(inner->type)) { inner_new = expr_copy(inner); - expr_insert_addr(inner_new); + if (sema_cast_const(inner_new) && expr_is_const_initializer(inner_new)) + { + ConstInitializer *initializer = inner_new->const_expr.initializer; + inner_new->const_expr.const_kind = CONST_SLICE; + inner_new->type = type_get_slice(inner_new->type->array.base); + inner_new->const_expr.slice_init = initializer; + initializer->type = inner_new->type; + } + else + { + expr_insert_addr(inner_new); + } } if (!cast_implicit_silent(context, inner_new, variadic_slot_type, false)) goto SPLAT_NORMAL; if (inner != inner_new) expr_replace(inner, inner_new); @@ -2218,6 +2229,33 @@ SPLAT_NORMAL:; if (!sema_analyse_parameter(context, arg, params[i], callee->definition, optional, no_match_ref, callee->macro, callee->struct_var && i == 0)) return false; actual_args[i] = arg; } + if (call->call_expr.varargs && variadic != VARIADIC_RAW) + { + Decl *param = params[vaarg_index]; + if (param->var.kind == VARDECL_PARAM_CT) + { + if (call->call_expr.va_is_splat) + { + if (!expr_is_runtime_const(call->call_expr.vasplat)) + { + SEMA_ERROR(call->call_expr.vasplat, "The splat must be a compile time value."); + RETURN_NOTE_FUNC_DEFINITION; + } + } + else + { + FOREACH(Expr *, ct_param, call->call_expr.varargs) + { + if (!expr_is_runtime_const(ct_param)) + { + SEMA_ERROR(ct_param, "All vaargs must be contant values."); + RETURN_NOTE_FUNC_DEFINITION; + } + + } + } + } + } if (num_args) last = args[num_args - 1]; call->call_expr.arguments = args; // 17. Set default values. diff --git a/test/test_suite/functions/const_vaarg_untyped.c3 b/test/test_suite/functions/const_vaarg_untyped.c3 new file mode 100644 index 000000000..e93a3f718 --- /dev/null +++ b/test/test_suite/functions/const_vaarg_untyped.c3 @@ -0,0 +1,6 @@ +macro foo2(int $a, $i...) // #error: Untyped constant vaargs are not supported. Use raw macro vaargs instead +{ + $foreach $ab : $i: + $echo $ab; + $endforeach +} diff --git a/test/test_suite/functions/expr_vaarg_untyped.c3 b/test/test_suite/functions/expr_vaarg_untyped.c3 new file mode 100644 index 000000000..eeda233f1 --- /dev/null +++ b/test/test_suite/functions/expr_vaarg_untyped.c3 @@ -0,0 +1,4 @@ + +macro @foo3(int $a, int #i...) // #error: Expression parameters may not be vaargs, use untyped macro vaargs '...' instead +{ +} diff --git a/test/test_suite/functions/returning_void.c3t b/test/test_suite/functions/returning_void.c3t index 7e1a7732d..d08ed82c3 100644 --- a/test/test_suite/functions/returning_void.c3t +++ b/test/test_suite/functions/returning_void.c3t @@ -1,3 +1,4 @@ +// #target: macos-x64 fn void test1() { int x; diff --git a/test/test_suite/functions/splat_const_vaarg.c3t b/test/test_suite/functions/splat_const_vaarg.c3t new file mode 100644 index 000000000..f7ad9c2d4 --- /dev/null +++ b/test/test_suite/functions/splat_const_vaarg.c3t @@ -0,0 +1,35 @@ +// #target: macos-x64 +module test; +macro foo(int $a, int... $i) +{ + $foreach $ab : $i: + { + int x = $ab; + } + $endforeach +} +fn int main() +{ + foo(3, 1, 2); + int[3] $x = { 1, 2, 3 }; + foo(3, ...$x); + return 0; +} + + +/* #expect: test.ll + +define i32 @main() #0 { +entry: + %x = alloca i32, align 4 + %x1 = alloca i32, align 4 + %x2 = alloca i32, align 4 + %x3 = alloca i32, align 4 + %x4 = alloca i32, align 4 + store i32 1, ptr %x, align 4 + store i32 2, ptr %x1, align 4 + store i32 1, ptr %x2, align 4 + store i32 2, ptr %x3, align 4 + store i32 3, ptr %x4, align 4 + ret i32 0 +} diff --git a/test/test_suite/functions/splat_const_vaarg_fail.c3 b/test/test_suite/functions/splat_const_vaarg_fail.c3 new file mode 100644 index 000000000..73e76ec66 --- /dev/null +++ b/test/test_suite/functions/splat_const_vaarg_fail.c3 @@ -0,0 +1,17 @@ +module test; + +macro foo(int $a, int... $i) +{ + $foreach $ab : $i: + $echo $ab; + $endforeach +} +fn int main() +{ + int a; + foo(3, 1, 2, a); // #error: All vaargs must be contant values + int[3] x = { 1, 2, 3 }; + foo(3, ...x); // #error: The splat must be a compile time value + return 0; +} +