From 5e934d589db08febfd9c80d6351e3b0f5cb626c2 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 27 Jun 2026 21:34:53 +0300 Subject: [PATCH] checker: register nested generic str deps (#27442) --- vlib/v/checker/fn.v | 1 + vlib/v/checker/str.v | 2 +- vlib/v/checker/used_features.v | 325 +++++++++++++++++- vlib/v/gen/c/auto_str_methods.v | 129 ++++++- .../generic_alias_str_auto_field.c.must_have | 2 + .../testdata/generic_alias_str_auto_field.vv | 31 ++ vlib/v/markused/walker.v | 51 ++- ...neric_auto_str_nested_generic_field_test.v | 224 ++++++++++++ .../tests/generics/generic_sumtype_str_test.v | 22 ++ 9 files changed, 770 insertions(+), 17 deletions(-) create mode 100644 vlib/v/gen/c/testdata/generic_alias_str_auto_field.c.must_have create mode 100644 vlib/v/gen/c/testdata/generic_alias_str_auto_field.vv create mode 100644 vlib/v/tests/generics/generic_auto_str_nested_generic_field_test.v diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 13db671f5..9404040cf 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -3292,6 +3292,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) c.fail_if_unreadable(node.left, left_type, 'receiver') if !c.is_builtin_mod { c.table.used_features.auto_str = true + c.markused_auto_str_dependencies(left_type) } return ast.string_type } else if node.kind == .free { diff --git a/vlib/v/checker/str.v b/vlib/v/checker/str.v index 5e12d5ec2..491f8553a 100644 --- a/vlib/v/checker/str.v +++ b/vlib/v/checker/str.v @@ -96,7 +96,6 @@ fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Type { if ftyp == 0 { return ast.void_type } - c.markused_string_inter_lit(mut node, ftyp) c.fail_if_unreadable(expr, ftyp, 'interpolation object') node.expr_types << ftyp if i < node.fwidth_exprs.len { @@ -179,6 +178,7 @@ fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Type { && c.table.cur_fn.name == 'str' && c.table.cur_fn.receiver.name == '${expr}' { c.error('cannot call `str()` method recursively', expr.pos()) } + c.markused_string_inter_lit(mut node, ftyp, fmt) } c.inside_interface_deref = inside_interface_deref_save if c.pref.warn_about_allocs { diff --git a/vlib/v/checker/used_features.v b/vlib/v/checker/used_features.v index 22ef2304d..5e8f969f6 100644 --- a/vlib/v/checker/used_features.v +++ b/vlib/v/checker/used_features.v @@ -20,12 +20,16 @@ fn comptime_call_last_arg_type(arg ast.CallArg) ast.Type { } 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') { + if !c.is_builtin_mod && mut node.expr is ast.InfixExpr { + left_type := c.unwrap_generic(node.expr.left_type) + right_type := c.unwrap_generic(node.expr.right_type) + c.markused_auto_str_dependencies(left_type) + c.markused_auto_str_dependencies(right_type) + if !c.table.used_features.auto_str && !c.table.sym(left_type).has_method('str') { c.table.used_features.auto_str = true return } - if !c.table.sym(c.unwrap_generic(node.expr.right_type)).has_method('str') { + if !c.table.used_features.auto_str && !c.table.sym(right_type).has_method('str') { c.table.used_features.auto_str = true } } @@ -41,6 +45,7 @@ fn (mut c Checker) markused_dumpexpr(mut node ast.DumpExpr) { } if !c.table.sym(unwrapped_type).has_method('str') { c.table.used_features.auto_str = true + c.markused_auto_str_dependencies(unwrapped_type) if node.expr_type.is_ptr() { c.table.used_features.auto_str_ptr = true } @@ -48,6 +53,7 @@ fn (mut c Checker) markused_dumpexpr(mut node ast.DumpExpr) { c.table.used_features.auto_str_arr = c.table.final_sym(unwrapped_type).kind == .array } } else { + c.markused_generic_str_method(unwrapped_type, c.table.sym(unwrapped_type)) c.table.used_features.print_types[node.expr_type.idx()] = true } c.table.used_features.print_types[ast.int_type_idx] = true @@ -130,6 +136,7 @@ fn (mut c Checker) markused_print_call(mut node ast.CallExpr) { if arg_typ == 0 { return } + c.markused_auto_str_dependencies(arg_typ) if (node.args[0].expr is ast.CallExpr && node.args[0].expr.is_method && node.args[0].expr.name == 'str') || !c.table.sym(arg_typ).has_method('str') { @@ -206,15 +213,26 @@ fn (mut c Checker) markused_method_call(mut node ast.CallExpr, mut left_expr ast } } -fn (mut c Checker) markused_string_inter_lit(mut _ ast.StringInterLiteral, ftyp ast.Type) { +fn string_inter_lit_format_uses_str(fmt u8) bool { + return fmt == `s` +} + +fn (mut c Checker) markused_string_inter_lit(mut _ ast.StringInterLiteral, ftyp ast.Type, fmt u8) { if c.is_builtin_mod { return } + uses_str_format := string_inter_lit_format_uses_str(fmt) if !c.table.sym(ftyp).has_method('str') { - c.table.used_features.auto_str = true + if uses_str_format { + c.table.used_features.auto_str = true + c.markused_auto_str_dependencies(ftyp) + } } else { - c.mark_type_str_method_as_referenced(ftyp) - c.table.used_features.print_types[ftyp.idx()] = true + if uses_str_format { + c.markused_generic_str_method(ftyp, c.table.sym(ftyp)) + c.mark_type_str_method_as_referenced(ftyp) + c.table.used_features.print_types[ftyp.idx()] = true + } } if ftyp.is_ptr() { c.table.used_features.auto_str_ptr = true @@ -227,6 +245,299 @@ fn (mut c Checker) markused_string_inter_lit(mut _ ast.StringInterLiteral, ftyp } } +fn (mut c Checker) markused_auto_str_dependencies(typ ast.Type) { + mut visited := map[ast.Type]bool{} + c.markused_auto_str_dependencies_for_type(typ, mut visited) +} + +fn (mut c Checker) markused_auto_str_dependencies_for_type(typ ast.Type, mut visited map[ast.Type]bool) { + base_typ := c.unwrap_generic(typ.clear_option_and_result().clear_flags(.variadic, .shared_f, + .atomic_f).clear_ref()) + if base_typ == 0 || visited[base_typ] { + return + } + visited[base_typ] = true + mut sym := c.table.sym(base_typ) + has_parent_str := sym.has_method_with_generic_parent('str') + has_exact_str := sym.has_method('str') + if has_parent_str || has_exact_str { + c.markused_generic_str_method(base_typ, sym) + return + } + if sym.info is ast.Alias { + c.markused_auto_str_dependencies_for_type(sym.info.parent_type.derive(base_typ), mut + visited) + return + } + sym = c.table.final_sym(base_typ) + if sym.has_method_with_generic_parent('str') || sym.has_method('str') { + c.markused_generic_str_method(ast.idx_to_type(sym.idx), sym) + return + } + match sym.info { + ast.Array { + c.markused_auto_str_dependencies_for_type(sym.info.elem_type, mut visited) + } + ast.ArrayFixed { + c.markused_auto_str_dependencies_for_type(sym.info.elem_type, mut visited) + } + ast.Chan { + c.markused_auto_str_dependencies_for_type(sym.info.elem_type, mut visited) + } + ast.Interface { + for concrete_type in sym.info.types { + c.markused_auto_str_dependencies_for_type(concrete_type, mut visited) + } + } + ast.Map { + c.markused_auto_str_dependencies_for_type(sym.info.key_type, mut visited) + c.markused_auto_str_dependencies_for_type(sym.info.value_type, mut visited) + } + ast.MultiReturn { + for ret_type in sym.info.types { + c.markused_auto_str_dependencies_for_type(ret_type, mut visited) + } + } + ast.Struct { + for field in sym.info.fields { + if attr := field.attrs.find_first('str') { + if attr.arg == 'skip' { + continue + } + } + c.markused_auto_str_dependencies_for_type(field.typ, mut visited) + } + } + ast.SumType { + for variant in sym.info.variants { + c.markused_auto_str_dependencies_for_type(variant, mut visited) + } + } + ast.Thread { + c.markused_auto_str_dependencies_for_type(sym.info.return_type, mut visited) + } + else {} + } +} + +fn (mut c Checker) markused_generic_str_method(typ ast.Type, sym &ast.TypeSymbol) { + mut method := ast.Fn{} + mut concrete_types := []ast.Type{} + if structured_method := c.table.find_structured_receiver_method_with_types(typ, 'str') { + if exact_method := sym.find_method('str') { + method = exact_method + } else if exact_method := c.table.find_alias_parent_exact_method(typ, 'str') { + method = exact_method + } else { + method = structured_method.method + concrete_types = structured_method.concrete_types.map(c.unwrap_generic(it)) + } + } else { + method = sym.find_method_with_generic_parent('str') or { + sym.find_method('str') or { return } + } + } + if concrete_types.len == 0 { + concrete_types = c.concrete_types_for_generic_str_receiver(typ, sym, method) + } + if method.generic_names.len != concrete_types.len || concrete_types.len == 0 + || concrete_types.any(it.has_flag(.generic) || c.type_has_unresolved_generic_parts(it)) { + return + } + mut registered := c.register_auto_str_fn_concrete_types(method.fkey(), concrete_types) + decl_fkey := method_decl_fkey(method) + if decl_fkey != method.fkey() { + registered = c.register_auto_str_fn_concrete_types(decl_fkey, concrete_types) || registered + } + for resolved_decl_fkey in c.str_method_decl_fkeys_for_type(typ, method) { + if resolved_decl_fkey != method.fkey() && resolved_decl_fkey != decl_fkey { + registered = c.register_auto_str_fn_concrete_types(resolved_decl_fkey, concrete_types) + || registered + } + } + if registered { + c.need_recheck_generic_fns = true + } +} + +fn (mut c Checker) register_auto_str_fn_concrete_types(fkey string, concrete_types []ast.Type) bool { + if fkey == '' { + return false + } + if _ := c.table.fn_generic_types[fkey] { + // The generic function key is already registered. + } else { + c.table.register_fn_generic_types(fkey) + } + return c.table.register_fn_concrete_types(fkey, concrete_types) +} + +fn method_decl_fkey(method ast.Fn) string { + if method.source_fn != unsafe { nil } { + fndecl := unsafe { &ast.FnDecl(method.source_fn) } + return fndecl.fkey() + } + return method.fkey() +} + +fn (c &Checker) generic_parent_str_method_fkey(sym &ast.TypeSymbol) string { + match sym.info { + ast.Struct, ast.Interface, ast.SumType { + if sym.info.parent_type.has_flag(.generic) { + parent_sym := c.table.sym(sym.info.parent_type) + if method := parent_sym.find_method('str') { + return method_decl_fkey(method) + } + } + } + ast.GenericInst { + if sym.info.parent_idx > 0 { + parent_sym := c.table.sym(ast.idx_to_type(sym.info.parent_idx)) + if method := parent_sym.find_method('str') { + return method_decl_fkey(method) + } + } + } + else {} + } + + return '' +} + +fn add_str_method_decl_fkey(mut fkeys []string, fkey string) { + if fkey != '' && fkey !in fkeys { + fkeys << fkey + } +} + +fn add_matching_str_method_decl_fkeys(mut fkeys []string, candidate_fkeys []string, resolved_decl_fkey string) { + if resolved_decl_fkey in candidate_fkeys { + for fkey in candidate_fkeys { + add_str_method_decl_fkey(mut fkeys, fkey) + } + } +} + +fn (c &Checker) str_method_decl_fkeys_for_type(typ ast.Type, method ast.Fn) []string { + mut fkeys := []string{} + if typ == 0 { + return fkeys + } + resolved_decl_fkey := method_decl_fkey(method) + for fkey in c.str_method_decl_fkeys_for_candidate(typ) { + add_str_method_decl_fkey(mut fkeys, fkey) + } + unaliased := c.table.unaliased_type(typ) + if unaliased != 0 && unaliased != typ { + add_matching_str_method_decl_fkeys(mut fkeys, + c.str_method_decl_fkeys_for_candidate(unaliased), resolved_decl_fkey) + } + if !typ.is_ptr() { + ref_typ := typ.ref() + if ref_typ != 0 { + add_matching_str_method_decl_fkeys(mut fkeys, + c.str_method_decl_fkeys_for_candidate(ref_typ), resolved_decl_fkey) + } + } else { + deref_typ := typ.deref() + if deref_typ != 0 { + add_matching_str_method_decl_fkeys(mut fkeys, + c.str_method_decl_fkeys_for_candidate(deref_typ), resolved_decl_fkey) + } + } + return fkeys +} + +fn (c &Checker) str_method_decl_fkeys_for_candidate(candidate ast.Type) []string { + mut fkeys := []string{} + sym := c.table.sym(candidate) + parent_fkey := c.generic_parent_str_method_fkey(sym) + if parent_fkey != '' { + add_str_method_decl_fkey(mut fkeys, parent_fkey) + } + if method := sym.find_method_with_generic_parent('str') { + add_str_method_decl_fkey(mut fkeys, method_decl_fkey(method)) + } + if method := c.table.find_method(sym, 'str') { + add_str_method_decl_fkey(mut fkeys, method_decl_fkey(method)) + } + method, embed_types := c.table.find_method_from_embeds(c.table.final_sym(candidate), 'str') or { + ast.Fn{}, []ast.Type{} + } + if embed_types.len != 0 { + add_str_method_decl_fkey(mut fkeys, method_decl_fkey(method)) + } + return fkeys +} + +fn (mut c Checker) concrete_types_for_generic_str_receiver(typ ast.Type, sym &ast.TypeSymbol, method ast.Fn) []ast.Type { + mut concrete_types := c.concrete_types_for_type_symbol(sym) + if concrete_types.len == 0 && sym.info is ast.Alias { + concrete_types = c.alias_parent_concrete_types(sym.info) + } + if method.generic_names.len == 0 + || (concrete_types.len > 0 && concrete_types.len == method.generic_names.len) { + return concrete_types + } + return c.infer_method_receiver_concrete_types(method, typ) +} + +fn (c &Checker) alias_parent_concrete_types(info ast.Alias) []ast.Type { + parent_sym := c.table.sym(info.parent_type) + match parent_sym.info { + ast.Struct, ast.SumType, ast.Interface { + mut concrete_types := parent_sym.info.concrete_types.clone() + if concrete_types.len == 0 + && parent_sym.generic_types.len == parent_sym.info.generic_types.len + && parent_sym.generic_types != parent_sym.info.generic_types { + concrete_types = parent_sym.generic_types.clone() + } + return concrete_types + } + ast.GenericInst { + return parent_sym.info.concrete_types.clone() + } + ast.Array { + return [parent_sym.info.elem_type] + } + ast.ArrayFixed { + return [parent_sym.info.elem_type] + } + ast.Chan { + return [parent_sym.info.elem_type] + } + ast.Map { + return [parent_sym.info.key_type, parent_sym.info.value_type] + } + ast.Alias { + return c.alias_parent_concrete_types(parent_sym.info) + } + else {} + } + + return []ast.Type{} +} + +fn (mut c Checker) infer_method_receiver_concrete_types(method ast.Fn, typ ast.Type) []ast.Type { + mut concrete_types := []ast.Type{cap: method.generic_names.len} + for generic_name in method.generic_names { + mut concrete_type := c.infer_composite_generic_type(generic_name, method.receiver_type, typ) + if concrete_type == ast.void_type { + unaliased_receiver_type := c.table.fully_unaliased_type(method.receiver_type) + unaliased_typ := c.table.fully_unaliased_type(typ) + if unaliased_receiver_type != method.receiver_type || unaliased_typ != typ { + concrete_type = c.infer_composite_generic_type(generic_name, + unaliased_receiver_type, unaliased_typ) + } + } + if concrete_type == ast.void_type { + return []ast.Type{} + } + concrete_types << c.unwrap_generic(concrete_type) + } + return concrete_types +} + fn (mut c Checker) markused_array_method(check bool, method_name string) { if !check { return diff --git a/vlib/v/gen/c/auto_str_methods.v b/vlib/v/gen/c/auto_str_methods.v index d0d6bf4c9..dab65ea60 100644 --- a/vlib/v/gen/c/auto_str_methods.v +++ b/vlib/v/gen/c/auto_str_methods.v @@ -90,12 +90,17 @@ fn (mut g Gen) get_str_fn(typ ast.Type) string { if sym.is_builtin() && !str_fn_name.starts_with('builtin__') { str_fn_name = 'builtin__${str_fn_name}' } - if sym.has_method_with_generic_parent('str') && !g.pref.new_generic_solver { - match mut sym.info { - ast.Struct, ast.SumType, ast.Interface { - str_fn_name = g.generic_fn_name(sym.info.concrete_types, str_fn_name) + if !g.pref.new_generic_solver { + if str_method := sym.find_method_with_generic_parent('str') { + if method_has_generic_source(str_method) { + match mut sym.info { + ast.Struct, ast.SumType, ast.Interface, ast.Alias, ast.GenericInst, ast.FnType { + str_fn_name = g.generic_fn_name(g.str_method_concrete_types(unwrapped, sym), + str_fn_name) + } + else {} + } } - else {} } } if sym.language == .c && !typ.has_flag(.option) && sym.has_method('str') { @@ -670,6 +675,102 @@ fn styp_to_str_fn_name(styp string) string { return styp.replace_each(['*', '', '.', '__', ' ', '__']) + '_str' } +fn method_has_generic_source(method ast.Fn) bool { + if method.generic_names.len > 0 { + return true + } + if method.source_fn != unsafe { nil } { + fndecl := unsafe { &ast.FnDecl(method.source_fn) } + return fndecl.generic_names.len > 0 + } + return false +} + +fn (mut g Gen) str_method_concrete_types(typ ast.Type, sym &ast.TypeSymbol) []ast.Type { + if _ := g.receiver_exact_method_for_type(typ, 'str') { + match sym.info { + ast.Struct, ast.SumType, ast.Interface { + return sym.info.concrete_types.clone() + } + ast.GenericInst { + return sym.info.concrete_types.clone() + } + ast.FnType { + return g.concrete_types_for_fn_type_symbol(sym) + } + ast.Alias { + return g.alias_parent_concrete_types(sym.info) + } + else {} + } + } + if structured_method := g.table.find_structured_receiver_method_with_types(typ, 'str') { + return structured_method.concrete_types.map(g.unwrap_generic(it)) + } + match sym.info { + ast.Struct, ast.SumType, ast.Interface { + return sym.info.concrete_types.clone() + } + ast.GenericInst { + return sym.info.concrete_types.clone() + } + ast.FnType { + return g.concrete_types_for_fn_type_symbol(sym) + } + ast.Alias { + return g.alias_parent_concrete_types(sym.info) + } + else {} + } + + return []ast.Type{} +} + +fn (mut g Gen) concrete_types_for_fn_type_symbol(sym &ast.TypeSymbol) []ast.Type { + if sym.info is ast.FnType && sym.generic_types.len > 0 + && !sym.generic_types.any(it.has_flag(.generic) + || g.table.generic_type_names(it).len > 0) { + return sym.generic_types.clone() + } + return []ast.Type{} +} + +fn (mut g Gen) alias_parent_concrete_types(info ast.Alias) []ast.Type { + parent_sym := g.table.sym(info.parent_type) + match parent_sym.info { + ast.Struct, ast.SumType, ast.Interface { + mut concrete_types := parent_sym.info.concrete_types.clone() + if concrete_types.len == 0 + && parent_sym.generic_types.len == parent_sym.info.generic_types.len + && parent_sym.generic_types != parent_sym.info.generic_types { + concrete_types = parent_sym.generic_types.clone() + } + return concrete_types + } + ast.GenericInst { + return parent_sym.info.concrete_types.clone() + } + ast.Array { + return [parent_sym.info.elem_type] + } + ast.ArrayFixed { + return [parent_sym.info.elem_type] + } + ast.Chan { + return [parent_sym.info.elem_type] + } + ast.Map { + return [parent_sym.info.key_type, parent_sym.info.value_type] + } + ast.Alias { + return g.alias_parent_concrete_types(parent_sym.info) + } + else {} + } + + return []ast.Type{} +} + // deref_kind returns deref, deref_label fn deref_kind(str_method_expects_ptr bool, is_elem_ptr bool, typ ast.Type) (string, string) { if typ.has_flag(.option) { @@ -1159,8 +1260,19 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, lang ast.Language, styp strin left_fn_name := util.no_dots(left_cc_type) '${left_fn_name}_str' } - if sym.info is ast.Struct && !g.pref.new_generic_solver { - field_fn_name = g.generic_fn_name(sym.info.concrete_types, field_fn_name) + if !g.pref.new_generic_solver { + if str_method := sym.find_method_with_generic_parent('str') { + if method_has_generic_source(str_method) && !ftyp_noshared.has_flag(.option) { + match sym.info { + ast.Struct, ast.SumType, ast.Interface, ast.Alias, ast.GenericInst, + ast.FnType { + field_fn_name = g.generic_fn_name(g.str_method_concrete_types(ftyp_noshared, sym), + field_fn_name) + } + else {} + } + } + } } if sym.is_builtin() && !field_fn_name.starts_with('builtin__') { field_fn_name = 'builtin__${field_fn_name}' @@ -1357,6 +1469,9 @@ fn struct_auto_str_func(sym &ast.TypeSymbol, lang ast.Language, _field_type ast. } return 'indent_${fn_name}(${obj}, indent_count + 1)', true } else if sym.kind == .function { + if has_custom_str { + return '${fn_name}(${prefix}it${op}${final_field_name}${sufix})', true + } return '${fn_name}()', true } else if sym.kind == .chan { return '${fn_name}(${deref}it${op}${final_field_name}${sufix})', true diff --git a/vlib/v/gen/c/testdata/generic_alias_str_auto_field.c.must_have b/vlib/v/gen/c/testdata/generic_alias_str_auto_field.c.must_have new file mode 100644 index 000000000..805d39b30 --- /dev/null +++ b/vlib/v/gen/c/testdata/generic_alias_str_auto_field.c.must_have @@ -0,0 +1,2 @@ +main__GenericAliasStrIntBox_str_T_int(it.b) +main__GenericAliasStrNestedIntBox_str_T_int(it.nested) diff --git a/vlib/v/gen/c/testdata/generic_alias_str_auto_field.vv b/vlib/v/gen/c/testdata/generic_alias_str_auto_field.vv new file mode 100644 index 000000000..8d413a1b0 --- /dev/null +++ b/vlib/v/gen/c/testdata/generic_alias_str_auto_field.vv @@ -0,0 +1,31 @@ +struct GenericAliasStrBox[T] { + val T +} + +type GenericAliasStrIntBox = GenericAliasStrBox[int] +type GenericAliasStrNestedIntBox = GenericAliasStrIntBox + +struct GenericAliasStrContainer { + b GenericAliasStrIntBox + nested GenericAliasStrNestedIntBox +} + +fn (b GenericAliasStrIntBox) str[T]() string { + return 'alias generic str' +} + +fn (b GenericAliasStrNestedIntBox) str[T]() string { + return 'nested alias generic str' +} + +fn main() { + c := GenericAliasStrContainer{ + b: GenericAliasStrIntBox{ + val: 1 + } + nested: GenericAliasStrNestedIntBox{ + val: 2 + } + } + println(c) +} diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 20a5b9a19..8d439e2b1 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -719,6 +719,7 @@ pub fn (mut w Walker) stmt(node_ ast.Stmt) { if node.is_used { w.uses_asserts = true w.expr(node.expr) + w.mark_assert_generic_str_methods(node) if node.extra !is ast.EmptyExpr { w.expr(node.extra) } @@ -962,13 +963,15 @@ fn (mut w Walker) expr(node_ ast.Expr) { w.mark_by_type(w.table.find_or_register_array(sym.info.key_type)) } } else if !node.is_method && node.args.len == 1 - && node.name in ['println', 'print', 'eprint', 'eprintln'] { + && node.name in ['println', 'print', 'eprint', 'eprintln', 'panic'] { if f := w.table.find_fn(node.name) { if f.mod == 'builtin' { if node.args[0].typ != ast.string_type { w.uses_str[node.args[0].typ] = true + w.mark_generic_str_method_for_type(node.args[0].typ) } - if w.pref.gc_mode == .boehm_leak && (node.args[0].typ != ast.string_type + if node.name != 'panic' && w.pref.gc_mode == .boehm_leak + && (node.args[0].typ != ast.string_type || node.args[0].expr !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr, ast.ComptimeSelector]) { w.uses_free[ast.string_type] = true } @@ -1055,6 +1058,7 @@ fn (mut w Walker) expr(node_ ast.Expr) { w.expr(node.expr) w.features.dump = true w.mark_by_type(node.expr_type) + w.mark_generic_str_method_for_type(node.expr_type) } ast.SpawnExpr { if node.is_expr { @@ -1393,6 +1397,7 @@ fn (mut w Walker) expr(node_ ast.Expr) { } else { w.mark_simple_string_inter_literal(node) } + w.mark_string_inter_literal_generic_str_methods(node) w.exprs(node.exprs) for expr in node.fwidth_exprs { if expr !is ast.EmptyExpr { @@ -2786,6 +2791,48 @@ fn (mut w Walker) string_inter_literal_uses_isnil(node ast.StringInterLiteral) b return false } +fn (mut w Walker) mark_string_inter_literal_generic_str_methods(node ast.StringInterLiteral) { + for i in 0 .. node.exprs.len { + if i >= node.expr_types.len { + break + } + w.mark_generic_str_method_for_type(node.expr_types[i]) + } +} + +fn (mut w Walker) mark_assert_generic_str_methods(node ast.AssertStmt) { + if node.expr is ast.InfixExpr { + w.mark_generic_str_method_for_type(node.expr.left_type) + w.mark_generic_str_method_for_type(node.expr.right_type) + } +} + +fn (mut w Walker) mark_generic_str_method_for_type(source_typ ast.Type) { + typ := w.resolve_current_generic_type(source_typ) + if typ == 0 || typ == ast.void_type || typ == ast.string_type { + return + } + fkey, _ := w.resolve_method_fkey_for_type(typ, 'str') + if fkey == '' { + return + } + concrete_type_lists := w.table.fn_generic_types[fkey] + if concrete_type_lists.len == 0 { + return + } + for concrete_types in concrete_type_lists { + if concrete_types.len == 0 { + continue + } + w.record_used_fn_generic_types(fkey, concrete_types) + if mut fn_decl := w.all_fns[fkey] { + w.fn_decl_with_concrete_types(mut fn_decl, concrete_types) + } else { + w.mark_fn_as_used(fkey) + } + } +} + fn (mut w Walker) mark_simple_string_inter_literal(node ast.StringInterLiteral) { if node.vals.any(it.len > 0) && !w.uses_str_literal { w.mark_by_sym_name('string') diff --git a/vlib/v/tests/generics/generic_auto_str_nested_generic_field_test.v b/vlib/v/tests/generics/generic_auto_str_nested_generic_field_test.v new file mode 100644 index 000000000..810446339 --- /dev/null +++ b/vlib/v/tests/generics/generic_auto_str_nested_generic_field_test.v @@ -0,0 +1,224 @@ +import datatypes + +struct MyNode[T] { + val T +} + +struct MyContainer[T] { + lst datatypes.LinkedList[MyNode[T]] +} + +struct Box[T] { + val T +} + +struct InterpBox[T] { + val T +} + +struct OverrideBox[T] { + val T +} + +struct PointerInterpBad[T] { + val T +} + +type InterpIntBox = InterpBox[int] + +type OverrideIntBox = OverrideBox[[]int] + +type Ints = []int + +type Parser[T] = fn (string) T + +struct StructuredContainer[T] { + box Box[[]T] +} + +struct IntsContainer { + values Ints +} + +struct ParserContainer { + parser Parser[int] @[required] +} + +struct AssertNode[T] { + val T +} + +struct AssertList[T] { + item T +} + +struct AssertContainer[T] { + list AssertList[AssertNode[T]] +} + +struct SkippedBad[T] { + val T +} + +struct SkipContainer { + bad SkippedBad[[]int] @[str: skip] +} + +fn (box Box[[]T]) str() string { + return 'structured box' +} + +fn (box InterpIntBox) str[T]() string { + return 'interp alias ${box.val}' +} + +fn (box OverrideBox[[]T]) str() string { + $if T is int { + $compile_error('overridden parent str should not be registered') + } + return 'parent override ${box.val}' +} + +fn (box OverrideIntBox) str[T]() string { + return 'override alias ${box.val[0]}' +} + +fn (bad &PointerInterpBad[[]T]) str() string { + $if T is int { + $compile_error('pointer interpolation should not register str') + } + return 'bad pointer ${bad.val}' +} + +fn (values Ints) str[T]() string { + return 'ints ${values[0]}' +} + +fn (p Parser[T]) str[T]() string { + return 'parser' +} + +fn new_int_parser() Parser[int] { + return fn (input string) int { + return input.int() + } +} + +fn (list AssertList[T]) str() string { + return 'assert list' +} + +fn (bad SkippedBad[[]T]) str() string { + return bad.val[0].len.str() +} + +fn maybe_panic_auto_str_container(should_panic bool) { + if should_panic { + panic(MyContainer[string]{}) + } +} + +fn test_auto_str_registers_nested_generic_field_str_method() { + c := MyContainer[string]{} + assert '${c}' == 'MyContainer[string]{ + lst: [] +}' +} + +fn test_auto_str_registers_structured_receiver_generic_str_method() { + c := StructuredContainer[int]{ + box: Box[[]int]{ + val: [1, 2, 3] + } + } + assert '${c}'.contains('box: structured box') +} + +fn test_interpolation_registers_exact_generic_alias_str_method() { + box := InterpIntBox{ + val: 987 + } + assert '${box}' == 'interp alias 987' +} + +fn test_interpolation_registers_only_overridden_alias_str_method() { + box := OverrideIntBox{ + val: [246] + } + assert '${box}' == 'override alias 246' +} + +fn test_pointer_interpolation_does_not_register_generic_str_method() { + mut value := PointerInterpBad[[]int]{ + val: [135] + } + ptr := &value + assert '${ptr:p}'.len > 0 +} + +fn test_auto_str_registers_container_alias_parent_str_method() { + c := IntsContainer{ + values: Ints([654]) + } + assert '${c}'.contains('values: ints 654') +} + +fn test_interpolation_registers_container_alias_parent_str_method() { + values := Ints([321]) + assert '${values}' == 'ints 321' +} + +fn test_auto_str_registers_generic_fn_type_alias_str_method() { + c := ParserContainer{ + parser: new_int_parser() + } + assert '${c}'.contains('parser: parser') +} + +fn test_interpolation_registers_generic_fn_type_alias_str_method() { + parser := new_int_parser() + assert '${parser}' == 'parser' +} + +fn test_dump_registers_exact_generic_alias_str_method() { + box := InterpIntBox{ + val: 654 + } + dumped := dump(box) + assert dumped.val == 654 +} + +fn test_panic_marks_auto_str_dependencies_under_skip_unused() { + maybe_panic_auto_str_container(false) + assert true +} + +fn test_assert_auto_str_registers_nested_generic_field_str_method() { + left := AssertContainer[string]{ + list: AssertList[AssertNode[string]]{ + item: AssertNode[string]{ + val: 'abc' + } + } + } + right := left + assert left == right +} + +fn test_assert_preserves_exact_generic_alias_str_method_under_skip_unused() { + left := InterpIntBox{ + val: 741 + } + right := left + assert left == right +} + +fn test_auto_str_dependency_walk_ignores_str_skip_fields() { + c := SkipContainer{ + bad: SkippedBad[[]int]{ + val: [123] + } + } + assert '${c}'.contains('SkipContainer') + assert !'${c}'.contains('bad:') +} diff --git a/vlib/v/tests/generics/generic_sumtype_str_test.v b/vlib/v/tests/generics/generic_sumtype_str_test.v index 99e23510e..f21916804 100644 --- a/vlib/v/tests/generics/generic_sumtype_str_test.v +++ b/vlib/v/tests/generics/generic_sumtype_str_test.v @@ -2,6 +2,14 @@ struct None {} pub type Maybe[T] = None | T +struct MaybeContainer[T] { + value Maybe[T] +} + +struct MaybeOptionalContainer[T] { + value ?Maybe[T] +} + pub fn (m Maybe[T]) str[T]() string { return if m is T { x := m as T @@ -76,3 +84,17 @@ fn test_generic_sumtype_match_resolves_variant_type() { assert generic_match_has_value(some(123)) assert !generic_match_has_value(noth[int]()) } + +fn test_auto_str_struct_field_with_generic_sumtype_str() { + c := MaybeContainer[int]{ + value: some(456) + } + assert '${c}'.contains('value: Some(456)') +} + +fn test_auto_str_optional_field_with_generic_sumtype_str() { + c := MaybeOptionalContainer[int]{ + value: some(789) + } + assert '${c}'.contains('Some(789)') +} -- 2.39.5