From 06142bdeaa602c4c65b687348ce6c64d2a3b7185 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 00:46:47 +0300 Subject: [PATCH] cgen: fix c transpilation type bug (fixes #10802) --- vlib/v/checker/checker.v | 16 ++++++++++++ vlib/v/gen/c/fn.v | 8 +++++- vlib/v/gen/c/utils.v | 24 ++++++++++++++++- ..._mut_receiver_local_copy_regression_test.v | 26 +++++++++++++++++++ 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 vlib/v/tests/generics/generics_mut_receiver_local_copy_regression_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 7177098ce..e1f82d19a 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -334,6 +334,22 @@ fn (mut c Checker) refresh_generic_scope_var_type_for_use(mut v ast.Var, use_pos refreshed_type = c.expr(mut expr) } if refreshed_type != 0 && refreshed_type != ast.void_type { + mut expr_is_auto_deref_ident := false + if expr is ast.Ident { + ident := expr as ast.Ident + expr_is_auto_deref_ident = ident.obj is ast.Var && ident.obj.is_auto_deref + if !expr_is_auto_deref_ident { + if source_var := ident.scope.find_var(ident.name) { + expr_is_auto_deref_ident = source_var.is_auto_deref + } + } + } + // Keep `mut x := param` as a value copy during generic rechecks too. + // `c.expr(param)` returns the wrapped pointer type for auto-deref vars, + // but the declaration itself already inferred the dereferenced value type. + if expr_is_auto_deref_ident && refreshed_type.is_ptr() { + refreshed_type = refreshed_type.deref() + } $if trace_ci_fixes ? { if c.file.path.contains('/datatypes/linked_list.v') { eprintln('refresh_var expr fn=${c.table.cur_fn.name} var=${v.name} refreshed=${c.table.type_to_str(refreshed_type)} expr=${expr}') diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 42d271039..99fa15423 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -3609,8 +3609,14 @@ fn (mut g Gen) refresh_current_generic_local_scope_vars(scope &ast.Scope) { || (var.expr is ast.StructInit && var.expr.typ_str.len > 0 && g.current_fn_generic_names().index(var.expr.typ_str.all_after_last('.')) >= 0) if should_resolve_expr_type && !(var.expr is ast.Ident && var.expr.name == name) { - resolved_type := g.resolved_expr_type(var.expr, var.typ) + mut resolved_type := g.resolved_expr_type(var.expr, var.typ) if resolved_type != 0 { + // Mirror the checker's `:=` behavior for auto-deref vars. + // Generic cgen refreshes re-evaluate initializer expressions, + // so `mut x := param` must stay a value copy here as well. + if g.is_auto_deref_source_ident(var.expr) && resolved_type.is_ptr() { + resolved_type = resolved_type.deref() + } var.typ = g.unwrap_generic(g.recheck_concrete_type(resolved_type)) } } else if var.typ != 0 { diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index 4bca60ae3..53c117dc7 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -224,6 +224,19 @@ fn (g Gen) expr_has_or_block(expr ast.Expr) bool { } } +fn (g &Gen) is_auto_deref_source_ident(expr ast.Expr) bool { + if expr is ast.Ident { + ident := expr as ast.Ident + if ident.obj is ast.Var && ident.obj.is_auto_deref { + return true + } + if source_var := ident.scope.find_var(ident.name) { + return source_var.is_auto_deref + } + } + return false +} + fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { mut scope := if expr.scope != unsafe { nil } { expr.scope.innermost(expr.pos.pos) @@ -266,6 +279,10 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { refreshed_expr_type = call_like_type } } + // Keep `mut x := param` as a value copy when re-resolving locals. + if g.is_auto_deref_source_ident(v.expr) && refreshed_expr_type.is_ptr() { + refreshed_expr_type = refreshed_expr_type.deref() + } // If the variable was initialized with an `or {}` block that // unwraps the option/result, clear the flag from the resolved type if refreshed_expr_type.has_option_or_result() && g.expr_has_or_block(v.expr) { @@ -316,7 +333,7 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { || g.type_has_unresolved_generic_parts(parent_v.typ)) { resolved_parent_expr_type := g.resolved_expr_type(parent_v.expr, parent_v.typ) if resolved_parent_expr_type != 0 { - parent_v.typ = + mut refreshed_parent_type := g.unwrap_generic(g.recheck_concrete_type(resolved_parent_expr_type)) if g.type_has_unresolved_generic_parts(parent_v.typ) { call_like_type := g.resolved_call_like_expr_type(parent_v.expr) @@ -325,6 +342,11 @@ fn (mut g Gen) resolved_scope_var_type(expr ast.Ident) ast.Type { parent_v.typ = call_like_type } } + if g.is_auto_deref_source_ident(parent_v.expr) + && refreshed_parent_type.is_ptr() { + refreshed_parent_type = refreshed_parent_type.deref() + } + parent_v.typ = refreshed_parent_type } } if v.is_unwrapped { diff --git a/vlib/v/tests/generics/generics_mut_receiver_local_copy_regression_test.v b/vlib/v/tests/generics/generics_mut_receiver_local_copy_regression_test.v new file mode 100644 index 000000000..3ad76bf66 --- /dev/null +++ b/vlib/v/tests/generics/generics_mut_receiver_local_copy_regression_test.v @@ -0,0 +1,26 @@ +module main + +struct Node[T] { +mut: + data T + next &Node[T] = unsafe { nil } +} + +fn (mut node Node[T]) tail_data() T { + mut current_node := node + for current_node.next != unsafe { nil } { + current_node = current_node.next + } + return current_node.data +} + +fn test_generic_mut_receiver_local_copy_can_follow_recursive_next() { + mut node2 := Node[int]{ + data: 2 + } + mut node1 := Node[int]{ + data: 1 + next: &node2 + } + assert node1.tail_data() == 2 +} -- 2.39.5