From c629ae348c266474c413968d102d81f47e7b781d Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 23 Apr 2026 21:35:29 +0300 Subject: [PATCH] cgen: fix C compiler error with arrays.filter_indexed (fixes #26959) --- vlib/v/checker/fn.v | 20 +++++++ vlib/v/gen/c/str.v | 23 +++++--- vlib/v/gen/c/str_intp.v | 66 ++++++++++++---------- vlib/v/gen/c/utils.v | 97 ++++++++++++++++++++++----------- vlib/v/tests/fns/closure_test.v | 22 +++++++- 5 files changed, 159 insertions(+), 69 deletions(-) diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index f9f17f0d6..1ecd47b32 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1180,6 +1180,7 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { parent_var := node.decl.scope.parent.find_var(var.name) or { panic('unexpected checker error: cannot find parent of inherited variable `${var.name}`') } + captures_auto_deref_by_value := parent_var.is_auto_deref && !var.is_mut mut declared_parent_typ := parent_var.typ if keep_fn != unsafe { nil } { if keep_fn.is_method && keep_fn.receiver.name == var.name { @@ -1232,6 +1233,9 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { } else { var.typ = ptyp } + if captures_auto_deref_by_value && var.typ.is_ptr() { + var.typ = var.typ.deref() + } if c.is_nocopy_struct(var.typ) { c.error('cannot capture @[nocopy] struct by value: use a reference instead', var.pos) } @@ -1239,6 +1243,22 @@ fn (mut c Checker) anon_fn(mut node ast.AnonFn) ast.Type { has_generic = true } node.decl.scope.update_var_type(var.name, var.typ) + if captures_auto_deref_by_value { + if mut captured_var := node.decl.scope.find_var(var.name) { + captured_var.is_auto_deref = false + captured_var.typ = var.typ + if captured_var.orig_type.is_ptr() { + captured_var.orig_type = captured_var.orig_type.deref() + } + if captured_var.smartcasts.len > 0 { + captured_var.smartcasts = captured_var.smartcasts.map(if it.is_ptr() { + it.deref() + } else { + it + }) + } + } + } } c.anon_fn_generic_names = []string{} c.anon_fn_concrete_types = []ast.Type{} diff --git a/vlib/v/gen/c/str.v b/vlib/v/gen/c/str.v index e655912be..8075f5660 100644 --- a/vlib/v/gen/c/str.v +++ b/vlib/v/gen/c/str.v @@ -100,8 +100,15 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { g.inside_opt_or_res = old_inside_opt_or_res g.expected_fixed_arr = false } - is_shared := etype.has_flag(.shared_f) - mut typ := etype + mut expr_type := etype + if expr is ast.Ident && g.resolved_ident_is_by_value_auto_deref_capture(expr) { + resolved_scope_type := g.resolved_scope_var_type(expr) + if resolved_scope_type != 0 { + expr_type = resolved_scope_type + } + } + is_shared := expr_type.has_flag(.shared_f) + mut typ := expr_type if is_shared { typ = typ.clear_flag(.shared_f).set_nr_muls(0) } @@ -131,7 +138,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { g.write(')') return } - if mut_arg_option_type == 0 && expr is ast.Ident && expr.is_auto_deref_var() + if mut_arg_option_type == 0 && expr is ast.Ident && g.expr_is_auto_deref_var(expr) && typ.has_flag(.option) { g.write('${g.get_str_fn(typ)}(*') g.expr(expr) @@ -143,7 +150,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { is_ptr := typ.is_ptr() || (typ.has_flag(.option_mut_param_t) && !typ.has_flag(.option)) mut sym := g.table.sym(typ) // when type is non-option alias and doesn't has `str()`, print the aliased value - if mut sym.info is ast.Alias && !sym.has_method('str') && !etype.has_flag(.option) { + if mut sym.info is ast.Alias && !sym.has_method('str') && !expr_type.has_flag(.option) { parent_sym := g.table.sym(sym.info.parent_type) if parent_sym.has_method('str') { typ = sym.info.parent_type @@ -170,7 +177,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { g.expr(expr) g.write(')') } else if typ == ast.string_type { - if etype.is_ptr() { + if expr_type.is_ptr() { g.write('*') } g.expr(expr) @@ -223,7 +230,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { typ = exp_typ } is_dump_expr := expr is ast.DumpExpr - is_var_mut := expr.is_auto_deref_var() && !typ.has_flag(.option) + is_var_mut := g.expr_is_auto_deref_var(expr) && !typ.has_flag(.option) str_fn_name := if mut_arg_option_type != 0 { g.get_str_fn(mut_arg_option_type) } else { @@ -303,7 +310,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { if sym.is_c_struct() { g.write(c_struct_ptr(sym, typ, str_method_expects_ptr)) } else { - g.write('*'.repeat(etype.nr_muls())) + g.write('*'.repeat(expr_type.nr_muls())) } } else if !str_method_expects_ptr && is_interface_smartcast_to_nonptr { g.write('*') @@ -341,7 +348,7 @@ fn (mut g Gen) gen_expr_to_string(expr ast.Expr, etype ast.Type) { g.write('}, 0, 0, 0}}))') } } else { - is_var_mut := expr.is_auto_deref_var() && !typ.has_flag(.option) + is_var_mut := g.expr_is_auto_deref_var(expr) && !typ.has_flag(.option) str_fn_name := g.get_str_fn(typ) g.write('${str_fn_name}(') if sym.kind != .function { diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index b3d5b0d3b..29bd6bdf3 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -75,11 +75,11 @@ fn (g Gen) should_clear_option_flag(expr ast.Expr) bool { return false } -fn int_ref_interpolates_as_value(expr ast.Expr, typ ast.Type, fmt u8) bool { +fn (g &Gen) int_ref_interpolates_as_value(expr ast.Expr, typ ast.Type, fmt u8) bool { if fmt == `p` || !(typ.is_int_valptr() || typ.is_float_valptr()) { return false } - if expr.is_auto_deref_var() { + if g.expr_is_auto_deref_var(expr) { return true } return match expr { @@ -171,32 +171,40 @@ fn (mut g Gen) str_format(node ast.StringInterLiteral, i int, fmts []u8) (u64, s } if g.is_type_name_string_expr(expr) { typ = ast.string_type - } else if expr is ast.Ident && expr.obj is ast.Var { - if expr.obj.smartcasts.len > 0 { - if expr.obj.orig_type != 0 && g.table.sym(expr.obj.orig_type).kind == .interface - && i < node.expr_types.len && node.expr_types[i] != ast.void_type { - typ = g.unwrap_generic(node.expr_types[i]) - } else { - typ = g.unwrap_generic(expr.obj.smartcasts.last()) - cast_sym := *g.table.sym(typ) - smartcast_variant_typ := cast_sym.aggregate_variant_type(g.aggregate_type_idx) - if smartcast_variant_typ != 0 { - typ = smartcast_variant_typ - } else if expr.obj.ct_type_var == .smartcast { - typ = g.unwrap_generic(g.type_resolver.get_type(expr)) - } + } else if expr is ast.Ident { + if g.resolved_ident_is_by_value_auto_deref_capture(expr) { + resolved_scope_type := g.resolved_scope_var_type(expr) + if resolved_scope_type != 0 { + typ = g.unwrap_generic(resolved_scope_type) } - } else if expr.obj.ct_type_var == .smartcast { - resolved_typ := g.unwrap_generic(g.type_resolver.get_type(expr)) - if resolved_typ != ast.void_type { - typ = resolved_typ + } + if expr.obj is ast.Var { + if expr.obj.smartcasts.len > 0 { + if expr.obj.orig_type != 0 && g.table.sym(expr.obj.orig_type).kind == .interface + && i < node.expr_types.len && node.expr_types[i] != ast.void_type { + typ = g.unwrap_generic(node.expr_types[i]) + } else { + typ = g.unwrap_generic(expr.obj.smartcasts.last()) + cast_sym := *g.table.sym(typ) + smartcast_variant_typ := cast_sym.aggregate_variant_type(g.aggregate_type_idx) + if smartcast_variant_typ != 0 { + typ = smartcast_variant_typ + } else if expr.obj.ct_type_var == .smartcast { + typ = g.unwrap_generic(g.type_resolver.get_type(expr)) + } + } + } else if expr.obj.ct_type_var == .smartcast { + resolved_typ := g.unwrap_generic(g.type_resolver.get_type(expr)) + if resolved_typ != ast.void_type { + typ = resolved_typ + } } } } - if node.exprs[i].is_auto_deref_var() && typ.nr_muls() > 0 { + if g.expr_is_auto_deref_var(node.exprs[i]) && typ.nr_muls() > 0 { typ = typ.deref() } - if int_ref_interpolates_as_value(expr, typ, fmts[i]) && typ.is_ptr() { + if g.int_ref_interpolates_as_value(expr, typ, fmts[i]) && typ.is_ptr() { typ = typ.deref() } typ = g.table.final_type(typ) @@ -367,7 +375,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { if orig_variant_typ != 0 { orig_typ = orig_variant_typ } - is_int_valptr := int_ref_interpolates_as_value(expr, orig_typ, fmt) + is_int_valptr := g.int_ref_interpolates_as_value(expr, orig_typ, fmt) typ := if is_int_valptr { orig_typ.deref() } else { orig_typ } typ_sym := g.table.sym(typ) if g.is_type_name_string_expr(expr) { @@ -377,7 +385,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { if typ == ast.string_type && g.comptime.comptime_for_method == unsafe { nil } { if g.inside_veb_tmpl { g.write('${g.veb_filter_fn_name}(') - if expr.is_auto_deref_var() && fmt != `p` { + if g.expr_is_auto_deref_var(expr) && fmt != `p` { g.write('*') } g.expr(expr) @@ -400,7 +408,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { pos: tmp_pos }) pos_before := g.out.len - if expr.is_auto_deref_var() && fmt != `p` { + if g.expr_is_auto_deref_var(expr) && fmt != `p` { g.write('*') } g.expr(expr) @@ -409,7 +417,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { g.write(tmp) return } - if expr.is_auto_deref_var() && fmt != `p` { + if g.expr_is_auto_deref_var(expr) && fmt != `p` { g.write('*') } g.expr(expr) @@ -477,7 +485,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { } else { g.write('(u64)(') } - if expr.is_auto_deref_var() || is_int_valptr { + if g.expr_is_auto_deref_var(expr) || is_int_valptr { g.write('*') } g.expr(expr) @@ -486,7 +494,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { } g.write(')') } else { - if (expr.is_auto_deref_var() || is_int_valptr) && fmt != `p` { + if (g.expr_is_auto_deref_var(expr) || is_int_valptr) && fmt != `p` { g.write('*') } g.expr(expr) @@ -495,7 +503,7 @@ fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { } } } else { - if expr.is_auto_deref_var() && fmt != `p` { + if g.expr_is_auto_deref_var(expr) && fmt != `p` { g.write('*') } g.expr(expr) diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index 10a63ffea..749d2b268 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -444,42 +444,46 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { } if v.is_inherited && scope.parent != unsafe { nil } { if mut parent_v := scope.parent.find_var(expr.name) { - if parent_v.generic_typ != 0 { - refreshed_parent_type := - g.unwrap_generic(g.recheck_concrete_type(parent_v.generic_typ)) - if refreshed_parent_type != 0 { - parent_v.typ = refreshed_parent_type + by_value_auto_deref_capture := !v.is_auto_deref && parent_v.is_auto_deref + && parent_v.typ.is_ptr() + if !by_value_auto_deref_capture { + if parent_v.generic_typ != 0 { + refreshed_parent_type := + g.unwrap_generic(g.recheck_concrete_type(parent_v.generic_typ)) + if refreshed_parent_type != 0 { + parent_v.typ = refreshed_parent_type + } } - } - if parent_v.smartcasts.len > 0 { - smartcast_type := if parent_v.ct_type_var == .smartcast { - g.type_resolver.get_type(expr) - } else { - g.exposed_smartcast_type(parent_v.orig_type, parent_v.smartcasts.last(), - parent_v.is_mut) + if parent_v.smartcasts.len > 0 { + smartcast_type := if parent_v.ct_type_var == .smartcast { + g.type_resolver.get_type(expr) + } else { + g.exposed_smartcast_type(parent_v.orig_type, + parent_v.smartcasts.last(), parent_v.is_mut) + } + return g.unwrap_generic(g.recheck_concrete_type(smartcast_type)) } - return g.unwrap_generic(g.recheck_concrete_type(smartcast_type)) - } - if parent_v.expr !is ast.EmptyExpr - && ((g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0) - || parent_v.typ.has_flag(.generic) - || g.type_has_unresolved_generic_parts(parent_v.typ)) { - resolved_parent_type := g.resolved_expr_type(parent_v.expr, parent_v.typ) - if resolved_parent_type != 0 { - resolved_parent := - g.unwrap_generic(g.recheck_concrete_type(resolved_parent_type)) - if g.type_has_unresolved_generic_parts(resolved_parent) { - call_like_type := g.resolved_call_like_expr_type(parent_v.expr) - if call_like_type != 0 && !call_like_type.has_flag(.generic) - && !g.type_has_unresolved_generic_parts(call_like_type) { - return call_like_type + if parent_v.expr !is ast.EmptyExpr + && ((g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0) + || parent_v.typ.has_flag(.generic) + || g.type_has_unresolved_generic_parts(parent_v.typ)) { + resolved_parent_type := g.resolved_expr_type(parent_v.expr, parent_v.typ) + if resolved_parent_type != 0 { + resolved_parent := + g.unwrap_generic(g.recheck_concrete_type(resolved_parent_type)) + if g.type_has_unresolved_generic_parts(resolved_parent) { + call_like_type := g.resolved_call_like_expr_type(parent_v.expr) + if call_like_type != 0 && !call_like_type.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(call_like_type) { + return call_like_type + } } + return resolved_parent } - return resolved_parent } - } - if parent_v.typ != 0 { - return g.unwrap_generic(g.recheck_concrete_type(parent_v.typ)) + if parent_v.typ != 0 { + return g.unwrap_generic(g.recheck_concrete_type(parent_v.typ)) + } } } } @@ -554,6 +558,37 @@ fn (mut g Gen) resolved_ident_is_auto_heap(expr ast.Ident) bool { return false } +fn (g &Gen) resolved_ident_is_auto_deref(expr ast.Ident) bool { + if expr.scope != unsafe { nil } { + if v := expr.scope.find_var(expr.name) { + return v.is_auto_deref + } + } + if expr.obj is ast.Var { + return expr.obj.is_auto_deref + } + return false +} + +fn (g &Gen) resolved_ident_is_by_value_auto_deref_capture(expr ast.Ident) bool { + if expr.scope == unsafe { nil } || expr.scope.parent == unsafe { nil } { + return false + } + scope_var := expr.scope.find_var(expr.name) or { return false } + if !scope_var.is_inherited || scope_var.is_auto_deref { + return false + } + parent_var := expr.scope.parent.find_var(expr.name) or { return false } + return parent_var.is_auto_deref && parent_var.typ.is_ptr() +} + +fn (g &Gen) expr_is_auto_deref_var(expr ast.Expr) bool { + return match expr { + ast.Ident { g.resolved_ident_is_auto_deref(expr) } + else { expr.is_auto_deref_var() } + } +} + // scope_ident_is_auto_heap reports whether `expr`'s scope variable has // is_auto_heap set. Unlike `resolved_ident_is_auto_heap`, this ignores // `expr.obj.is_auto_heap` because a use-site Ident's obj copy may have been diff --git a/vlib/v/tests/fns/closure_test.v b/vlib/v/tests/fns/closure_test.v index 894a6e753..79ff785f4 100644 --- a/vlib/v/tests/fns/closure_test.v +++ b/vlib/v/tests/fns/closure_test.v @@ -1,4 +1,4 @@ -import arrays { flat_map, flat_map_indexed, fold, map_indexed, max } +import arrays { filter_indexed, flat_map, flat_map_indexed, fold, map_indexed, max } fn test_decl_assignment() { my_var := 12 @@ -211,6 +211,26 @@ fn test_closure_in_for_in_loop() { } } +struct ClosureFilterProduct { +mut: + cross_sell_skus []string +} + +fn test_filter_indexed_closure_capture_of_for_mut_value() { + mut products := [ClosureFilterProduct{ + cross_sell_skus: ['sku-a', 'sku-b'] + }] + for mut product in products { + product.cross_sell_skus = filter_indexed[string](product.cross_sell_skus, fn [products, product] (idx int, css string) bool { + product_snapshot := '${product}' + products_snapshot := '${products}' + return idx == 0 && css == 'sku-a' && product_snapshot.len > 0 + && products_snapshot.len > 0 + }) + } + assert products[0].cross_sell_skus == ['sku-a'] +} + fn ret_two() (int, int) { return 2, 5 } -- 2.39.5