From dd1d0781d4b335867f4dfcb0912e0e6b451e7464 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 05:06:46 +0300 Subject: [PATCH] comptime: allow only explicit auto-expand of []string in compile-time $for loops (fixes #9278) --- vlib/v/checker/comptime.v | 34 +++++++++++++++++++ ...call_method_implicit_string_expand_err.out | 7 ++++ ..._call_method_implicit_string_expand_err.vv | 12 +++++++ vlib/v/checker/used_features.v | 14 ++++++-- vlib/v/gen/c/comptime.v | 31 ++++++++++++----- .../comptime/comptime_call_slice_arg_test.v | 4 +-- .../comptime_for_method_call_with_args_test.v | 2 +- vlib/vweb/vweb.v | 8 ++--- 8 files changed, 94 insertions(+), 18 deletions(-) create mode 100644 vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.out create mode 100644 vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.vv diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 778fc0302..e6f78502c 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -35,6 +35,37 @@ fn comptime_power_value(left ast.ComptTimeConstValue, right ast.ComptTimeConstVa return none } +fn (mut c Checker) is_string_array_type(typ ast.Type) bool { + final_typ := c.table.unaliased_type(c.unwrap_generic(typ)) + sym := c.table.final_sym(final_typ) + if sym.info is ast.Array { + return c.table.unaliased_type(sym.info.elem_type) == ast.string_type + } + return false +} + +fn (mut c Checker) check_comptime_method_string_auto_expand(mut node ast.ComptimeCall) bool { + if c.comptime.comptime_for_method == unsafe { nil } || node.args.len == 0 + || node.args.any(it.expr is ast.ArrayDecompose) { + return false + } + method := c.comptime.comptime_for_method + if method.params.len - 1 < node.args.len { + return false + } + last_arg := node.args.last() + if !c.is_string_array_type(last_arg.typ) { + return false + } + next_param := method.params[node.args.len].typ + if c.is_string_array_type(next_param) { + return false + } + c.error('to auto-expand `[]string` arguments in comptime method calls, use `...${last_arg.expr}`', + last_arg.pos) + return true +} + fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { if node.left !is ast.EmptyExpr { node.left_type = c.expr(mut node.left) @@ -232,6 +263,9 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { // check each arg expression node.args[i].typ = c.expr(mut arg.expr) } + if c.check_comptime_method_string_auto_expand(mut node) { + return ast.void_type + } c.markused_comptimecall(mut node) c.stmts_ending_with_expression(mut node.or_block.stmts, c.expected_or_type) return c.type_resolver.get_type(node) diff --git a/vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.out b/vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.out new file mode 100644 index 000000000..9c339ce13 --- /dev/null +++ b/vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.out @@ -0,0 +1,7 @@ +vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.vv:10:19: error: to auto-expand `[]string` arguments in comptime method calls, use `...args` + 8 | args := ['5'] + 9 | $for method in Dummy.methods { + 10 | Dummy{}.$method(args) + | ~~~~ + 11 | } + 12 | } diff --git a/vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.vv b/vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.vv new file mode 100644 index 000000000..ce9b20d65 --- /dev/null +++ b/vlib/v/checker/tests/comptime_call_method_implicit_string_expand_err.vv @@ -0,0 +1,12 @@ +module main + +struct Dummy {} + +fn (_ Dummy) sample(_ int) {} + +fn main() { + args := ['5'] + $for method in Dummy.methods { + Dummy{}.$method(args) + } +} diff --git a/vlib/v/checker/used_features.v b/vlib/v/checker/used_features.v index c1c77ec40..50a7249be 100644 --- a/vlib/v/checker/used_features.v +++ b/vlib/v/checker/used_features.v @@ -11,6 +11,14 @@ fn (mut c Checker) markused_comptime_call(check bool, key string) { } } +fn comptime_call_last_arg_type(arg ast.CallArg) ast.Type { + return if arg.expr is ast.ArrayDecompose { + arg.expr.expr_type + } else { + arg.typ + } +} + fn (mut c Checker) markused_assertstmt_auto_str(mut node ast.AssertStmt) { if !c.table.used_features.auto_str && !c.is_builtin_mod && mut node.expr is ast.InfixExpr { if !c.table.sym(c.unwrap_generic(node.expr.left_type)).has_method('str') { @@ -73,8 +81,9 @@ fn (mut c Checker) markused_comptimecall(mut node ast.ComptimeCall) { c.table.used_features.comptime_calls['${int(c.unwrap_generic(m.receiver_type))}.${m.name}'] = true if node.args.len > 0 && m.params.len > 0 { last_param := m.params.last().typ + last_arg_type := comptime_call_last_arg_type(node.args.last()) if (last_param.is_int() || last_param.is_bool()) - && c.table.final_sym(node.args.last().typ).kind == .array { + && c.table.final_sym(last_arg_type).kind == .array { c.table.used_features.comptime_calls['${ast.string_type_idx}.${c.table.type_to_str(m.params.last().typ)}'] = true } } @@ -83,8 +92,9 @@ fn (mut c Checker) markused_comptimecall(mut node ast.ComptimeCall) { m := c.comptime.comptime_for_method if node.args.len > 0 && m.params.len > 0 { last_param := m.params.last().typ + last_arg_type := comptime_call_last_arg_type(node.args.last()) if (last_param.is_int() || last_param.is_bool()) - && c.table.final_sym(node.args.last().typ).kind == .array { + && c.table.final_sym(last_arg_type).kind == .array { c.table.used_features.comptime_calls['${ast.string_type_idx}.${c.table.type_to_str(m.params.last().typ)}'] = true } } diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 60f172485..64cb4b0f9 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -29,6 +29,26 @@ fn (g &Gen) veb_context_html_arg() string { return ctx_param.name } +fn (mut g Gen) is_string_array_type(typ ast.Type) bool { + final_typ := g.table.unaliased_type(g.unwrap_generic(typ)) + sym := g.table.final_sym(final_typ) + if sym.info is ast.Array { + return g.table.unaliased_type(sym.info.elem_type) == ast.string_type + } + return false +} + +fn (mut g Gen) comptime_call_expands_string_args(m &ast.Fn, node ast.ComptimeCall) bool { + if node.args.len == 0 || node.args.last().expr !is ast.ArrayDecompose { + return false + } + array_decompose := node.args.last().expr as ast.ArrayDecompose + if !g.is_string_array_type(array_decompose.expr_type) || m.params.len - 1 < node.args.len { + return false + } + return !g.is_string_array_type(m.params[node.args.len].typ) +} + fn (mut g Gen) comptime_selector(node ast.ComptimeSelector) { left_type := g.resolved_expr_type(node.left, node.left_type) if node.is_method && g.comptime.comptime_for_method != unsafe { nil } { @@ -226,16 +246,9 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { if g.inside_call && m.return_type == ast.void_type { g.error('method `${m.name}()` (no value) used as value', node.pos) } - expand_strs := if node.args.len > 0 && m.params.len - 1 >= node.args.len { - arg := node.args.last() - param := m.params[node.args.len] - - arg.expr in [ast.IndexExpr, ast.Ident] && g.table.type_to_str(arg.typ) == '[]string' - && g.table.type_to_str(param.typ) != '[]string' - } else { - false - } + expand_strs := g.comptime_call_expands_string_args(m, node) mut has_decompose := !m.is_variadic && node.args.any(it.expr is ast.ArrayDecompose) + && !expand_strs // check argument length and types if m.params.len - 1 != node.args.len && !expand_strs { if g.inside_call { diff --git a/vlib/v/tests/comptime/comptime_call_slice_arg_test.v b/vlib/v/tests/comptime/comptime_call_slice_arg_test.v index e0b7e26ba..e670c616e 100644 --- a/vlib/v/tests/comptime/comptime_call_slice_arg_test.v +++ b/vlib/v/tests/comptime/comptime_call_slice_arg_test.v @@ -8,10 +8,10 @@ fn test_main() { args := ['0', '5'] $for method in Dummy.methods { if args.len > 1 { - assert Dummy{}.$method(args[1 ..]) == 6 + assert Dummy{}.$method(...args[1 ..]) == 6 tmp := args[1..] - assert Dummy{}.$method(tmp) == 6 + assert Dummy{}.$method(...tmp) == 6 } } } diff --git a/vlib/v/tests/comptime/comptime_for_method_call_with_args_test.v b/vlib/v/tests/comptime/comptime_for_method_call_with_args_test.v index a53ae4d73..4d2e00deb 100644 --- a/vlib/v/tests/comptime/comptime_for_method_call_with_args_test.v +++ b/vlib/v/tests/comptime/comptime_for_method_call_with_args_test.v @@ -10,7 +10,7 @@ fn test_comptime_for_method_call_with_args() { $for method in Dummy.methods { if os.args.len > 1 { d := Dummy{} - d.$method(os.args) + d.$method(...os.args) } } assert true diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index 90b0c61f0..6502975b0 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -830,11 +830,11 @@ fn handle_route[T](mut app T, url urllib.URL, host string, routes &map[string]Ro } if route.middleware == '' { - app.$method(args) + app.$method(...args) } else if validate_app_middleware(mut app, route.middleware, method.name) { - app.$method(args) + app.$method(...args) } } else { if route.middleware == '' { @@ -874,9 +874,9 @@ fn handle_route[T](mut app T, url urllib.URL, host string, routes &map[string]Ro } } if route.middleware == '' { - app.$method(method_args) + app.$method(...method_args) } else if validate_app_middleware(mut app, route.middleware, method.name) { - app.$method(method_args) + app.$method(...method_args) } return } -- 2.39.5