From 397b9896374e42792a9249f6cbc61b8584857b96 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 16:31:21 +0300 Subject: [PATCH] cgen: keep fn calling order if subsequent expression needs to use a temporary variable (fixes #26134) --- vlib/v/gen/c/array.v | 76 +++++++++++++++++-- vlib/v/gen/c/assert.v | 5 ++ vlib/v/gen/c/cgen.v | 13 ++-- vlib/v/gen/c/ctempvars.v | 13 ++++ ...sert_should_evaluate_args_just_once_test.v | 20 +++++ .../array_init_call_order_test.v | 31 ++++++++ 6 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 vlib/v/tests/builtin_arrays/array_init_call_order_test.v diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index 0b4f7180a..d6c7e63d5 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -5,6 +5,61 @@ module c import strings import v.ast +fn array_init_orig_expr(expr ast.Expr) ast.Expr { + return match expr { + ast.CTempVar { expr.orig } + else { expr } + } +} + +fn (g &Gen) can_keep_array_init_expr_inline(expr ast.Expr) bool { + return match expr { + ast.BoolLiteral, ast.CharLiteral, ast.EnumVal, ast.FloatLiteral, ast.IntegerLiteral, + ast.None, ast.OffsetOf, ast.SizeOf, ast.StringLiteral, ast.TypeNode { + true + } + ast.Ident { + expr.or_expr.kind == .absent + } + ast.ParExpr { + g.can_keep_array_init_expr_inline(expr.expr) + } + ast.SelectorExpr { + expr.or_block.kind == .absent && g.can_keep_array_init_expr_inline(expr.expr) + } + else { + false + } + } +} + +fn (mut g Gen) prepare_array_init_exprs(exprs []ast.Expr, expr_types []ast.Type, default_type ast.Type) []ast.Expr { + mut needs_order_preserved := false + for expr in exprs { + if g.need_tmp_var_in_expr(expr) { + needs_order_preserved = true + break + } + } + if !needs_order_preserved { + return exprs + } + mut prepared := []ast.Expr{cap: exprs.len} + for i, expr in exprs { + if g.can_keep_array_init_expr_inline(expr) { + prepared << expr + continue + } + expr_type := if expr_types.len > i && expr_types[i] != 0 { + expr_types[i] + } else { + default_type + } + prepared << ast.Expr(g.expr_to_ctemp_before_stmt(expr, expr_type)) + } + return prepared +} + fn (mut g Gen) array_init(node ast.ArrayInit, var_name string) { array_type := g.unwrap(node.typ) mut array_styp := '' @@ -49,6 +104,7 @@ fn (mut g Gen) array_init(node ast.ArrayInit, var_name string) { // `[1, 2, 3]` elem_styp := g.styp(elem_type.typ) noscan := g.check_noscan(elem_type.typ) + prepared_exprs := g.prepare_array_init_exprs(node.exprs, node.expr_types, node.elem_type) if elem_type.unaliased_sym.kind == .function { g.write('builtin__new_array_from_c_array(${len}, ${len}, sizeof(voidptr), _MOV((voidptr[${len}]){') } else { @@ -59,10 +115,11 @@ fn (mut g Gen) array_init(node ast.ArrayInit, var_name string) { g.write('\t\t') } is_iface_or_sumtype := elem_sym.kind in [.sum_type, .interface] - for i, expr in node.exprs { + for i, expr in prepared_exprs { + actual_expr := array_init_orig_expr(expr) expr_type := if node.expr_types.len > i { node.expr_types[i] } else { node.elem_type } if expr_type == ast.string_type - && expr !in [ast.IndexExpr, ast.CallExpr, ast.StringLiteral, ast.StringInterLiteral, ast.InfixExpr] { + && actual_expr !in [ast.IndexExpr, ast.CallExpr, ast.StringLiteral, ast.StringInterLiteral, ast.InfixExpr] { if is_iface_or_sumtype { g.expr_with_cast(expr, expr_type, node.elem_type) } else { @@ -73,8 +130,8 @@ fn (mut g Gen) array_init(node ast.ArrayInit, var_name string) { } else { if node.elem_type.has_flag(.option) { g.expr_with_opt(expr, expr_type, node.elem_type) - } else if elem_type.unaliased_sym.kind == .array_fixed - && expr in [ast.Ident, ast.SelectorExpr, ast.CallExpr] { + } else if elem_type.unaliased_sym.kind == .array_fixed && (expr is ast.CTempVar + || actual_expr in [ast.Ident, ast.SelectorExpr, ast.CallExpr]) { info := elem_type.unaliased_sym.info as ast.ArrayFixed g.fixed_array_var_init(g.expr_string(expr), expr.is_auto_deref_var(), info.elem_type, info.size) @@ -172,13 +229,16 @@ fn (mut g Gen) fixed_array_init(node ast.ArrayInit, array_type Type, var_name st g.inside_array_item = tmp_inside_array } nelen := node.exprs.len - for i, expr in node.exprs { - if elem_sym.kind == .array_fixed && expr in [ast.Ident, ast.SelectorExpr] { + prepared_exprs := g.prepare_array_init_exprs(node.exprs, node.expr_types, node.elem_type) + for i, expr in prepared_exprs { + actual_expr := array_init_orig_expr(expr) + if elem_sym.kind == .array_fixed + && (expr is ast.CTempVar || actual_expr in [ast.Ident, ast.SelectorExpr]) { elem_info := elem_sym.array_fixed_info() g.fixed_array_var_init(g.expr_string(expr), expr.is_auto_deref_var(), elem_info.elem_type, elem_info.size) - } else if elem_sym.kind == .array_fixed && expr is ast.CallExpr - && g.table.final_sym(expr.return_type).kind == .array_fixed { + } else if elem_sym.kind == .array_fixed && actual_expr is ast.CallExpr + && g.table.final_sym(actual_expr.return_type).kind == .array_fixed { elem_info := elem_sym.array_fixed_info() tmp_var := g.expr_with_var(expr, node.expr_types[i], false) g.fixed_array_var_init(tmp_var, false, elem_info.elem_type, elem_info.size) diff --git a/vlib/v/gen/c/assert.v b/vlib/v/gen/c/assert.v index 712de60f5..2bdda8a70 100644 --- a/vlib/v/gen/c/assert.v +++ b/vlib/v/gen/c/assert.v @@ -108,6 +108,11 @@ fn (mut g Gen) assert_subexpression_to_ctemp(expr ast.Expr, expr_type ast.Type) ast.LockExpr { return g.new_ctemp_var_then_gen(expr, expr_type) } + ast.ArrayInit { + if expr.has_callexpr || g.need_tmp_var_in_expr(expr) { + return g.new_ctemp_var_then_gen(expr, expr_type) + } + } else {} } return unsupported_ctemp_assert_transform diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c4362ecd7..c61763fce 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3293,12 +3293,15 @@ fn (mut g Gen) expr_with_fixed_array(expr ast.Expr, got_type_raw ast.Type, expec val_styp := g.styp(val_typ) val_sym := g.table.final_sym(val_typ) prefix := if val_sym.kind !in [.array, .array_fixed] { '&' } else { '' } - for i, item_expr in expr.exprs { + prepared_exprs := g.prepare_array_init_exprs(expr.exprs, expr.expr_types, val_typ) + for i, item_expr in prepared_exprs { + actual_item_expr := array_init_orig_expr(item_expr) g.write('memcpy(${prefix}${tmp_var}[${i}], ') - needs_addr := (item_expr is ast.CallExpr && !item_expr.return_type.is_ptr() - && g.table.final_sym(item_expr.return_type).kind !in [.array, .array_fixed]) - || (item_expr is ast.InfixExpr && !item_expr.promoted_type.is_ptr()) - || item_expr is ast.StructInit + needs_addr := + (actual_item_expr is ast.CallExpr && !actual_item_expr.return_type.is_ptr() + && g.table.final_sym(actual_item_expr.return_type).kind !in [.array, .array_fixed]) + || (actual_item_expr is ast.InfixExpr && !actual_item_expr.promoted_type.is_ptr()) + || actual_item_expr is ast.StructInit if needs_addr { g.write('ADDR(${val_styp}, ') } diff --git a/vlib/v/gen/c/ctempvars.v b/vlib/v/gen/c/ctempvars.v index 8be04e9dd..6fe07443c 100644 --- a/vlib/v/gen/c/ctempvars.v +++ b/vlib/v/gen/c/ctempvars.v @@ -17,6 +17,19 @@ fn (mut g Gen) new_ctemp_var_then_gen(expr ast.Expr, expr_type ast.Type) ast.CTe return x } +fn (mut g Gen) expr_to_ctemp_before_stmt(expr ast.Expr, expr_type ast.Type) ast.CTempVar { + stmt_str := if g.inside_ternary > 0 { + g.go_before_ternary().trim_space() + } else { + g.go_before_last_stmt().trim_space() + } + g.empty_line = true + mut x := g.new_ctemp_var(expr, expr_type) + g.gen_ctemp_var(mut x) + g.write(stmt_str) + return x +} + fn (mut g Gen) gen_ctemp_var(mut tvar ast.CTempVar) { styp := g.styp(tvar.typ) final_sym := g.table.final_sym(tvar.typ) diff --git a/vlib/v/tests/assign/assert_should_evaluate_args_just_once_test.v b/vlib/v/tests/assign/assert_should_evaluate_args_just_once_test.v index 83c28f298..8e03e9022 100644 --- a/vlib/v/tests/assign/assert_should_evaluate_args_just_once_test.v +++ b/vlib/v/tests/assign/assert_should_evaluate_args_just_once_test.v @@ -2,6 +2,11 @@ struct Abc { ncalls int } +struct AssertCounter { +mut: + value int +} + @[unsafe] fn fn_that_should_be_called_just_once_array() []int { mut static ncalls := 0 @@ -26,6 +31,11 @@ fn fn_that_should_be_called_just_once_struct() Abc { } } +fn (mut c AssertCounter) next() int { + c.value++ + return c.value +} + fn test_assert_calls_a_function_returning_an_array_just_once() { unsafe { assert fn_that_should_be_called_just_once_array().len == 1 @@ -37,3 +47,13 @@ fn test_assert_calls_a_function_returning_a_struct_just_once() { assert fn_that_should_be_called_just_once_struct().ncalls == 1 } } + +fn test_assert_array_literal_keeps_call_order_when_if_expr_needs_tmp() { + mut c := AssertCounter{} + assert [c.next(), if c.value > 0 { + _ := 1 + c.next() + } else { + 0 + }] == [1, 2] +} diff --git a/vlib/v/tests/builtin_arrays/array_init_call_order_test.v b/vlib/v/tests/builtin_arrays/array_init_call_order_test.v new file mode 100644 index 000000000..aea1b61cf --- /dev/null +++ b/vlib/v/tests/builtin_arrays/array_init_call_order_test.v @@ -0,0 +1,31 @@ +struct Counter { +mut: + value int +} + +fn (mut c Counter) next() int { + c.value++ + return c.value +} + +fn test_array_init_keeps_call_order_when_if_expr_needs_tmp() { + mut c := Counter{} + actual := [c.next(), if c.value > 0 { + _ := 1 + c.next() + } else { + 0 + }] + assert actual == [1, 2] +} + +fn test_fixed_array_init_keeps_call_order_when_if_expr_needs_tmp() { + mut c := Counter{} + actual := [c.next(), if c.value > 0 { + _ := 1 + c.next() + } else { + 0 + }]! + assert actual == [1, 2]! +} -- 2.39.5