From a2fbfe64cd4092f59b2d8844a7fc5267b30fc2b0 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 06:28:43 +0300 Subject: [PATCH] cgen: fix v allocating too much memory for generic method returning option (fixes #20276) --- vlib/v/gen/c/fn.v | 47 +++++++++++------- .../generics_method_returning_option_test.v | 48 +++++++++++++++++++ 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 92775a6c1..9bdb5e3ac 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -2772,7 +2772,11 @@ fn (mut g Gen) resolve_return_type(node ast.CallExpr) ast.Type { } } if func.generic_names.len > 0 { - mut concrete_types := if node.raw_concrete_types.len > 0 { + mut concrete_types := if node.concrete_types.len == func.generic_names.len + && node.concrete_types.all(it != 0 && !it.has_flag(.generic) + && !g.type_has_unresolved_generic_parts(it)) { + node.concrete_types.map(g.unwrap_generic(it)) + } else if node.raw_concrete_types.len > 0 { node.raw_concrete_types.map(g.unwrap_generic(it)) } else { node.concrete_types.map(g.unwrap_generic(it)) @@ -2834,7 +2838,7 @@ fn (mut g Gen) resolve_return_type(node ast.CallExpr) ast.Type { // may be stale (from a different instantiation). Re-resolve from // g.cur_concrete_types and override if different. if arg.expr is ast.Ident && arg.expr.obj is ast.Var - && (arg.expr.obj as ast.Var).ct_type_var == .generic_param { + && (arg.expr.obj as ast.Var).ct_type_var == .generic_param && g.table.sym(param.typ).name in func.generic_names { mut resolved := g.resolve_current_fn_generic_param_type(arg.expr.name) if (arg.is_mut || (arg.expr.obj as ast.Var).is_mut) @@ -2892,22 +2896,31 @@ fn (mut g Gen) resolve_return_type(node ast.CallExpr) ast.Type { } } } - return_type_generic := if node.return_type_generic != ast.void_type - && node.return_type_generic != 0 { - node.return_type_generic - } else if parent_method.params.len > 0 { - parent_method.return_type - } else { - func.return_type + // Prefer the resolved method declaration over the cached call metadata here. + // Generic rechecks can leave `node.return_type_generic` stale even when the + // method's concrete type arguments have since been corrected. + mut return_type_candidates := []ast.Type{} + if func.return_type != 0 { + return_type_candidates << func.return_type } - if gen_type := g.table.convert_generic_type(return_type_generic, func.generic_names, - concrete_types) - { - if !gen_type.has_flag(.generic) { - return if node.or_block.kind == .absent { - gen_type - } else { - gen_type.clear_option_and_result() + if node.return_type_generic != ast.void_type && node.return_type_generic != 0 + && node.return_type_generic !in return_type_candidates { + return_type_candidates << node.return_type_generic + } + if parent_method.params.len > 0 && parent_method.return_type != 0 + && parent_method.return_type !in return_type_candidates { + return_type_candidates << parent_method.return_type + } + for return_type_generic in return_type_candidates { + if gen_type := g.table.convert_generic_type(return_type_generic, + func.generic_names, concrete_types) + { + if !gen_type.has_flag(.generic) { + return if node.or_block.kind == .absent { + gen_type + } else { + gen_type.clear_option_and_result() + } } } } diff --git a/vlib/v/tests/generics/generics_method_returning_option_test.v b/vlib/v/tests/generics/generics_method_returning_option_test.v index f83a80905..dfe10fced 100644 --- a/vlib/v/tests/generics/generics_method_returning_option_test.v +++ b/vlib/v/tests/generics/generics_method_returning_option_test.v @@ -28,3 +28,51 @@ fn test_generic_method_returning_option() { a.diagnostics()! assert true } + +interface Component { + is_component() +} + +struct ActionRow { + components []Component +} + +fn (_ ActionRow) is_component() {} + +fn (ar ActionRow) walk[T](f fn (T) bool) ?T { + for c in ar.components { + if c is T { + if f(c) { + return c + } + } else if c is ActionRow { + if d := c.walk(f) { + return d + } + } + } + return none +} + +struct TextInput { + custom_id string + value ?string +} + +fn (_ TextInput) is_component() {} + +fn test_recursive_interface_method_inference() { + ar := ActionRow{ + components: [ + TextInput{ + custom_id: 'foo' + value: 'bar' + }, + ] + } + d := ar.walk(fn (ti TextInput) bool { + return ti.custom_id == 'foo' + }) or { panic('expected TextInput') } + assert d.custom_id == 'foo' + assert d.value or { '' } == 'bar' +} -- 2.39.5