Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions vlib/v/checker/fn.v
Original file line number Diff line number Diff line change
Expand Up @@ -3590,6 +3590,12 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool)
&& c.table.register_fn_concrete_types(method.fkey(), concrete_types) {
c.need_recheck_generic_fns = true
}
if method_name == 'str' && c.table.cur_fn != unsafe { nil } && c.table.cur_fn.is_method
&& c.table.cur_fn.name == 'str' && node.left is ast.Ident
&& (node.left as ast.Ident).name == c.table.cur_fn.receiver.name
Comment on lines +3594 to +3595

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle parenthesized receivers in recursive str check

In a str method, (n).str() is semantically the same recursive call as n.str(), but node.left remains an ast.ParExpr, so this node.left is ast.Ident guard skips the new diagnostic and still allows the runtime recursion/stack overflow that this check is meant to prevent. method_call already computes an unwrapped left_expr := node.left.remove_par() earlier, so using that here would catch both forms.

Useful? React with 👍 / 👎.

&& left_type.idx() == c.table.cur_fn.receiver.typ.idx() {
c.error('cannot call `str()` method recursively', node.pos)
Comment on lines +3593 to +3597

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve existing alias-array str implementations

This condition also fires while checking alias receivers that implement an interface by providing str() and delegate to the underlying array formatting, for example the existing type List = []Value; fn (x List) str() string { return x.str() } fixtures in vlib/v/tests/aliases/alias_array_returned_as_interface_test.v and vlib/v/tests/aliases/modules/value/value.v. In those files x still has the alias receiver type, so left_type.idx() equals the receiver idx and the checker now rejects existing vlib tests before they can run; either the check needs to distinguish this alias-delegation case or those fixtures need to be updated with an explicit cast.

Useful? React with 👍 / 👎.

}
node.is_noreturn = method.is_noreturn
node.is_expand_simple_interpolation = method.is_expand_simple_interpolation
node.is_ctor_new = method.is_ctor_new
Expand Down
7 changes: 7 additions & 0 deletions vlib/v/checker/tests/sum_type_recursive_str_err.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
vlib/v/checker/tests/sum_type_recursive_str_err.vv:6:12: error: cannot call `str()` method recursively
4 | return match n {
5 | u8 { n.str() }
6 | else { n.str() }
| ~~~~~
7 | }
8 | }
12 changes: 12 additions & 0 deletions vlib/v/checker/tests/sum_type_recursive_str_err.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type Num = u8 | u16

fn (n Num) str() string {
return match n {
u8 { n.str() }
else { n.str() }
}
}

fn main() {
println(Num(u16(0)))
}
Loading