From a7039193c3425333ef1f754a2780111733af8167 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 15:30:02 +0300 Subject: [PATCH] checker: fix returning private struct from public function works without noinit (fixes #18427) --- vlib/v/checker/checker.v | 22 +++++++++++++++++++ vlib/v/checker/fn.v | 7 ++++++ vlib/v/checker/str.v | 2 ++ .../private_struct_return_autostr_err.out | 6 +++++ .../private_struct_return_autostr_err.vv | 6 +++++ 5 files changed, 43 insertions(+) create mode 100644 vlib/v/checker/tests/private_struct_return_autostr_err.out create mode 100644 vlib/v/checker/tests/private_struct_return_autostr_err.vv diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 0579622bf..1aaa6a389 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4908,6 +4908,9 @@ pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type { node.expr.pos()) return ast.void_type } + if c.fail_if_private_implicit_str(node.expr_type, node.expr.pos(), 'dump') { + return ast.void_type + } unwrapped_expr_type := c.unwrap_generic(node.expr_type) tsym := c.table.sym(unwrapped_expr_type) @@ -8643,6 +8646,25 @@ fn (mut c Checker) fail_if_unreadable(expr ast.Expr, typ ast.Type, what string) return false } +fn (mut c Checker) fail_if_private_implicit_str(typ ast.Type, pos token.Pos, action string) bool { + base_typ := c.unwrap_generic(typ.clear_option_and_result().clear_flags(.variadic, .shared_f, + .atomic_f).clear_ref()) + if base_typ == 0 { + return false + } + final_sym := c.table.final_sym(base_typ) + if final_sym.language != .v || final_sym.kind != .struct || final_sym.is_pub + || final_sym.mod == c.mod || final_sym.mod in ['builtin', 'main'] { + return false + } + if final_sym.has_method_with_generic_parent('str') { + return false + } + c.error('cannot ${action} private type `${final_sym.name}` outside module `${final_sym.mod}` without an explicit `str()` method', + pos) + return true +} + fn (mut c Checker) interface_embeds_interface(interface_type ast.Type, embedded_interface_type ast.Type) bool { mut visited := map[int]bool{} return c.interface_embeds_interface_recursive(interface_type, embedded_interface_type, mut diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 5425fb172..bf5e31bd1 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -1468,6 +1468,8 @@ fn (mut c Checker) builtin_args(mut node ast.CallExpr, fn_name string, func &ast node.pos) } else if arg.expr is ast.ArrayDecompose { c.error('`${fn_name}` cannot print variadic values', node.pos) + } else if c.fail_if_private_implicit_str(arg.typ, node.pos, 'print') { + return } c.fail_if_unreadable(arg.expr, arg.typ, 'argument to print') c.inside_interface_deref = false @@ -3107,6 +3109,11 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) c.error('interface `${iname}` does not have a .str() method. Use typeof() instead', node.pos) } + if c.fail_if_private_implicit_str(left_type, node.pos, + 'call auto-generated `.str()` on') + { + return ast.string_type + } node.receiver_type = left_type.clear_ref() node.return_type = ast.string_type if node.args.len > 0 { diff --git a/vlib/v/checker/str.v b/vlib/v/checker/str.v index 8eb7aeb84..b5644cdba 100644 --- a/vlib/v/checker/str.v +++ b/vlib/v/checker/str.v @@ -90,6 +90,8 @@ fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Type { } else if ftyp == ast.char_type && ftyp.nr_muls() == 0 { c.error('expression returning type `char` cannot be used in string interpolation directly, print its address or cast it to an integer instead', expr.pos()) + } else if c.fail_if_private_implicit_str(ftyp, expr.pos(), 'interpolate') { + return ast.string_type } if ftyp == 0 { return ast.void_type diff --git a/vlib/v/checker/tests/private_struct_return_autostr_err.out b/vlib/v/checker/tests/private_struct_return_autostr_err.out new file mode 100644 index 000000000..fe54c3fc2 --- /dev/null +++ b/vlib/v/checker/tests/private_struct_return_autostr_err.out @@ -0,0 +1,6 @@ +vlib/v/checker/tests/private_struct_return_autostr_err.vv:5:7: error: cannot dump private type `v.checker.tests.amod.PrivateFoo` outside module `v.checker.tests.amod` without an explicit `str()` method + 3 | fn main() { + 4 | a := amod.new_private_foo(1) + 5 | dump(a) + | ^ + 6 | } diff --git a/vlib/v/checker/tests/private_struct_return_autostr_err.vv b/vlib/v/checker/tests/private_struct_return_autostr_err.vv new file mode 100644 index 000000000..3ba341eee --- /dev/null +++ b/vlib/v/checker/tests/private_struct_return_autostr_err.vv @@ -0,0 +1,6 @@ +import v.checker.tests.amod + +fn main() { + a := amod.new_private_foo(1) + dump(a) +} -- 2.39.5