Skip to content

Commit cbd9c57

Browse files
committed
v3: keep pointer-bearing generic args from parsing as fixed-array lengths (fix #28884)
`is_fixed_array_len_text` treated every non-leading `&` as a bitwise-and length operator, so `Foo[fn (&Baz) int]` (and `Foo[[]&T]`, `Foo[?&T]`, `Foo[&&T]`, `Foo[Box[&T]]`, ...) was parsed as a fixed array of `Foo` with length `fn (&Baz) int`. Field access then failed with "`[fn (&Baz) int]foo.Foo` has no property `value`", and fn-field calls became "unknown function". Reject argument text that starts with type-only syntax (`&`, `?`, `!`, `[`, `...`, `map[`, `fn`, `chan`, `thread`, `shared`, `atomic`, `mut`) and ignore `&`/`-` nested in brackets. The transform and cgen typeof helpers get the same `&`/`?`/`!` prefix check so `?&T` and `&&T` instances are not mangled as fixed arrays later on.
1 parent b595356 commit cbd9c57

6 files changed

Lines changed: 128 additions & 7 deletions

File tree

‎vlib/v/gen/c/cleanc.v‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17206,7 +17206,7 @@ fn typeof_display_fixed_array_len_text(text string) bool {
1720617206
if clean.len == 0 || clean.contains(',') || clean.contains('[') || clean.contains(']') {
1720717207
return false
1720817208
}
17209-
if clean.starts_with('fn(') || clean.starts_with('fn (') || clean.starts_with('chan ') || clean.starts_with('shared ') || clean.starts_with('atomic ') || clean.starts_with('mut ') || clean.starts_with('thread ') {
17209+
if clean[0] in [`&`, `?`, `!`] || clean.starts_with('fn(') || clean.starts_with('fn (') || clean.starts_with('chan ') || clean.starts_with('shared ') || clean.starts_with('atomic ') || clean.starts_with('mut ') || clean.starts_with('thread ') {
1721017210
return false
1721117211
}
1721217212
if clean[0] >= `0` && clean[0] <= `9` {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
@[has_globals]
2+
module main
3+
4+
import generics28884 { Box, Foo }
5+
6+
struct Baz {
7+
n int
8+
}
9+
10+
type Bar = fn (&Baz) int
11+
12+
__global alias_global generics28884.Foo[Bar]
13+
__global inline_global generics28884.Foo[fn (&Baz) int]
14+
15+
struct Holder {
16+
mut:
17+
f generics28884.Foo[fn (&Baz) int]
18+
}
19+
20+
fn double(b &Baz) int {
21+
return b.n * 2
22+
}
23+
24+
fn triple(b &Baz) int {
25+
return b.n * 3
26+
}
27+
28+
fn make_baz() &Baz {
29+
return &Baz{9}
30+
}
31+
32+
fn call_stored(mut f generics28884.Foo[fn (&Baz) int], n int) int {
33+
return f.value(&Baz{n})
34+
}
35+
36+
// https://github.com/vlang/v/issues/28884
37+
fn test_global_generic_container_of_fn_alias() {
38+
alias_global.set(double)
39+
alias_global.set(alias_global.value)
40+
assert alias_global.value(&Baz{21}) == 42
41+
mut local := generics28884.Foo[Bar]{}
42+
local.set(alias_global.value)
43+
assert local.value(&Baz{5}) == 10
44+
}
45+
46+
fn test_global_generic_container_of_inline_fn_type() {
47+
inline_global.set(triple)
48+
inline_global.set(inline_global.value)
49+
assert inline_global.value(&Baz{4}) == 12
50+
stored := inline_global.value
51+
assert stored(&Baz{5}) == 15
52+
}
53+
54+
fn test_local_generic_container_of_inline_fn_type() {
55+
mut local := generics28884.Foo[fn (&Baz) int]{}
56+
local.set(double)
57+
local.set(local.value)
58+
assert local.value(&Baz{6}) == 12
59+
mut holder := Holder{}
60+
holder.f.set(triple)
61+
assert holder.f.value(&Baz{7}) == 21
62+
assert call_stored(mut holder.f, 2) == 6
63+
mut named := Foo[fn (b &Baz) int]{}
64+
named.set(double)
65+
assert named.value(&Baz{8}) == 16
66+
mut returning := Foo[fn () &Baz]{}
67+
returning.set(make_baz)
68+
assert returning.value().n == 9
69+
}
70+
71+
fn test_generic_args_with_nested_pointer_types() {
72+
b := &Baz{3}
73+
mut arr := Foo[[]&Baz]{}
74+
arr.set([b])
75+
assert arr.value[0].n == 3
76+
mut opt := Foo[?&Baz]{}
77+
opt.set(b)
78+
got := opt.value or { &Baz{0} }
79+
assert got.n == 3
80+
mut ptr_ptr := Foo[&&Baz]{}
81+
ptr_ptr.set(&b)
82+
assert (**ptr_ptr.value).n == 3
83+
mut boxed := Foo[Box[&Baz]]{}
84+
boxed.set(Box[&Baz]{b})
85+
assert boxed.value.v.n == 3
86+
mut boxed_fn := Foo[Box[fn (&Baz) int]]{}
87+
boxed_fn.set(Box[fn (&Baz) int]{double})
88+
assert boxed_fn.value.v(b) == 6
89+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module generics28884
2+
3+
pub struct Foo[T] {
4+
pub mut:
5+
value T
6+
}
7+
8+
// set stores `value` in the container.
9+
pub fn (mut self Foo[T]) set(value T) {
10+
self.value = value
11+
}
12+
13+
pub struct Box[T] {
14+
pub:
15+
v T
16+
}

‎vlib/v/tests/generic_fn_ptr_arg_28884/v.mod‎

Whitespace-only changes.

‎vlib/v/transform/transform.v‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22219,9 +22219,9 @@ fn typeof_display_is_fixed_array_len_text(text string) bool {
2221922219
if clean.len == 0 || clean.contains(',') || clean.contains('[') || clean.contains(']') {
2222022220
return false
2222122221
}
22222-
if clean.starts_with('fn(') || clean.starts_with('fn (') || clean.starts_with('chan ')
22223-
|| clean.starts_with('shared ') || clean.starts_with('atomic ') || clean.starts_with('mut ')
22224-
|| clean.starts_with('thread ') {
22222+
if clean[0] in [`&`, `?`, `!`] || clean.starts_with('fn(') || clean.starts_with('fn (')
22223+
|| clean.starts_with('chan ') || clean.starts_with('shared ') || clean.starts_with('atomic ')
22224+
|| clean.starts_with('mut ') || clean.starts_with('thread ') {
2222522225
return false
2222622226
}
2222722227
if is_decimal_text(clean) || (clean[0] >= `0` && clean[0] <= `9`) {

‎vlib/v/types/checker.v‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14398,14 +14398,30 @@ fn is_fixed_array_len_text(inner string) bool {
1439814398
if v_int_literal_value(s) != none {
1439914399
return true
1440014400
}
14401+
// A type argument can hold a pointer type after its first character (`&&Node`, `?&Node`,
14402+
// `[]&Node`, `map[string]&Node`, `fn (&Node) int`, `chan &Node`). No integer length
14403+
// expression starts with such type syntax, so its `&` is never a bitwise-and.
14404+
if s[0] in [`&`, `?`, `!`, `[`] || s.starts_with('...') || s.starts_with('map[')
14405+
|| s.starts_with('fn(') || s.starts_with('fn ') || s.starts_with('chan ')
14406+
|| s.starts_with('thread ') || s.starts_with('shared ') || s.starts_with('atomic ')
14407+
|| s.starts_with('mut ') {
14408+
return false
14409+
}
14410+
mut depth := 0
1440114411
for i in 0 .. s.len {
1440214412
c := s[i]
14413+
if c == `[` {
14414+
depth++
14415+
} else if c == `]` {
14416+
depth--
14417+
}
1440314418
if c in [`+`, `*`, `/`, `%`, `|`, `^`, `<`, `>`] {
1440414419
return true
1440514420
}
14406-
// A leading `-`/`&` is a negative literal / pointer-type argument; elsewhere they are the
14407-
// subtraction / bitwise-and operators of a length expression.
14408-
if (c == `-` || c == `&`) && i > 0 {
14421+
// A leading `-`/`&` is a negative literal / pointer-type argument, and one inside
14422+
// brackets belongs to a nested type argument (`Box[&Node]`, `Box[fn (&Node) int]`);
14423+
// elsewhere they are the subtraction / bitwise-and operators of a length expression.
14424+
if (c == `-` || c == `&`) && i > 0 && depth == 0 {
1440914425
return true
1441014426
}
1441114427
}

0 commit comments

Comments
 (0)