@@ -1670,6 +1670,56 @@ fn is_decimal_int_literal(s string) bool {
16701670 return true
16711671}
16721672
1673+ // v_int_literal_value parses a complete V integer literal — decimal, hex (`0x`), octal
1674+ // (`0o`), or binary (`0b`), with optional `_` digit separators — to its value. Returns
1675+ // none when `s` is not a whole integer literal (a const name, an expression, etc.), so
1676+ // const-length folding accepts `0xF & 6` / `[0b1100 >> 1]int`, not just decimal text.
1677+ fn v_int_literal_value (s string ) ? int {
1678+ if s.len == 0 {
1679+ return none
1680+ }
1681+ t := s.replace ('_' , '' )
1682+ if t.len == 0 {
1683+ return none
1684+ }
1685+ mut base := 10
1686+ mut digits := t
1687+ if t.len > = 2 && t[0 ] == `0` {
1688+ c := t[1 ]
1689+ if c == `x` || c == `X` {
1690+ base = 16
1691+ digits = t[2 ..]
1692+ } else if c == `o` || c == `O` {
1693+ base = 8
1694+ digits = t[2 ..]
1695+ } else if c == `b` || c == `B` {
1696+ base = 2
1697+ digits = t[2 ..]
1698+ }
1699+ }
1700+ if digits.len == 0 {
1701+ return none
1702+ }
1703+ mut value := 0
1704+ for ch in digits {
1705+ mut d := 0
1706+ if ch > = `0` && ch < = `9` {
1707+ d = int (ch - `0` )
1708+ } else if ch > = `a` && ch < = `f` {
1709+ d = int (ch - `a` ) + 10
1710+ } else if ch > = `A` && ch < = `F` {
1711+ d = int (ch - `A` ) + 10
1712+ } else {
1713+ return none
1714+ }
1715+ if d > = base {
1716+ return none
1717+ }
1718+ value = value * base + d
1719+ }
1720+ return value
1721+ }
1722+
16731723// is_bare_generic_param reports whether is bare generic param applies in types.
16741724fn is_bare_generic_param (typ string ) bool {
16751725 return typ.len == 1 && typ[0 ] > = `A` && typ[0 ] < = `Z`
@@ -2080,12 +2130,17 @@ fn (mut tc TypeChecker) check_node(id flat.NodeId) {
20802130 tc.check_select_stmt (node)
20812131 return
20822132 }
2083- // A method value stored in an array escapes the single-use guarantee of its per-site
2084- // static receiver, so reject `[obj.method]` literals and `arr << obj.method` appends .
2133+ // A method value stored in a container escapes the single-use guarantee of its per-site
2134+ // static receiver, so reject `[obj.method]` / `arr << obj.method` / `{'k': obj.method}` .
20852135 if node.kind == .array_literal {
20862136 for i in 0 .. node.children_count {
20872137 tc.reject_stored_method_value (tc.a.child (& node, i))
20882138 }
2139+ } else if node.kind == .map_init {
2140+ // children alternate key, value, key, value, ...; check the value positions.
2141+ for j := 1 ; j < node.children_count; j + = 2 {
2142+ tc.reject_stored_method_value (tc.a.child (& node, j))
2143+ }
20892144 } else if node.kind == .infix && node.op == .left_shift && node.children_count > = 2 {
20902145 if unwrap_pointer (tc.resolve_type (tc.a.child (& node, 0 ))) is Array {
20912146 tc.reject_stored_method_value (tc.a.child (& node, 1 ))
@@ -2516,6 +2571,12 @@ fn (mut tc TypeChecker) resolve_lvalue_type(lhs_id flat.NodeId) Type {
25162571
25172572// check_return validates check return state for types.
25182573fn (mut tc TypeChecker) check_return (id flat.NodeId, node flat.Node) {
2574+ // A returned method value escapes the function, where its per-site static receiver
2575+ // can't keep multiple returned callbacks distinct (a factory `fn bind(c) fn () int {
2576+ // return c.report }`); reject it rather than emitting invalid C.
2577+ for i in 0 .. node.children_count {
2578+ tc.reject_stored_method_value (tc.a.child (& node, i))
2579+ }
25192580 expected := tc.cur_fn_ret_type
25202581 if expected is Void {
25212582 if node.children_count > 0 && tc.should_diagnose (id) {
@@ -4465,6 +4526,9 @@ fn (mut tc TypeChecker) check_struct_init(id flat.NodeId, node flat.Node) {
44654526 continue
44664527 }
44674528 value_id := tc.a.child (& field, 0 )
4529+ // A method value stored in a struct field escapes the evaluation site (several
4530+ // instances from the same `Foo{cb: obj.method}` site would share one receiver).
4531+ tc.reject_stored_method_value (value_id)
44684532 mut expected := Type (void_)
44694533 if field.value.len > 0 {
44704534 mut found := false
@@ -4527,14 +4591,17 @@ fn (tc &TypeChecker) expr_is_method_value(id flat.NodeId) bool {
45274591 return false
45284592}
45294593
4530- // reject_stored_method_value reports a clear error when a method value is stored into an
4531- // array, where the per-site static receiver slot cannot keep multiple instances distinct
4532- // (e.g. `cbs << obj.method` in a loop would make every callback use the last receiver).
4533- // Without this the value reaches cgen and emits C referencing an unsupported helper.
4594+ // reject_stored_method_value reports a clear error when a method value escapes its
4595+ // evaluation site — stored in an array/map/struct field or returned. The per-site static
4596+ // receiver slot cannot keep several live instances distinct, so a factory like
4597+ // `fn bind(c Counter) fn () int { return c.report }` (or storage in a loop) would make
4598+ // every escaped callback use the last receiver; without this the value also reaches cgen
4599+ // and emits C referencing an unsupported helper. Pass method values directly as a
4600+ // callback argument instead (a real closure capture is not yet supported).
45344601fn (mut tc TypeChecker) reject_stored_method_value (id flat.NodeId) {
45354602 if tc.expr_is_method_value (id) && tc.should_diagnose (id) {
45364603 tc.record_error (.assignment_mismatch,
4537- 'a method value (`obj.method`) cannot be stored in an array (no closure capture); pass it directly as a callback argument' ,
4604+ 'a method value (`obj.method`) cannot escape its call site (no closure capture); pass it directly as a callback argument' ,
45384605 id)
45394606 }
45404607}
@@ -5354,8 +5421,8 @@ pub fn (tc &TypeChecker) const_int_value(name string, seen []string) ?int {
53545421 return tc.const_int_expr (expr_id, next_seen)
53555422 }
53565423 }
5357- if is_decimal_int_literal (name) {
5358- return name. int ()
5424+ if v := v_int_literal_value (name) {
5425+ return v
53595426 }
53605427 // Simple const arithmetic in string form, e.g. a fixed-array size `[SEGS + 1]`,
53615428 // `[SEGS+1]`, `[segs / 2]`, `[segs % 4]` or `[2 * (segs + 1)]`. A length wrapped
@@ -5453,8 +5520,8 @@ fn (tc &TypeChecker) const_int_expr(id flat.NodeId, seen []string) ?int {
54535520 node := tc.a.nodes[int (id)]
54545521 match node.kind {
54555522 .int_literal {
5456- if is_decimal_int_literal (node.value) {
5457- return node.value. int ()
5523+ if v := v_int_literal_value (node.value) {
5524+ return v
54585525 }
54595526 }
54605527 .ident {
@@ -7597,6 +7664,39 @@ fn subst_generic_text(typ string, args []string, params []string) string {
75977664 1 ..], args, params)
75987665 }
75997666 }
7667+ if clean.starts_with ('fn(' ) || clean.starts_with ('fn (' ) {
7668+ // A function-type parameter (`fn (T) int`) carries the generic params in its own
7669+ // signature; substitute each parameter and the return type so a `Box[string].apply`
7670+ // callback is expected as `fn (string) int`, not the unsubstituted `fn (T) int`.
7671+ params_start := clean.index_u8 (`(` ) + 1
7672+ mut depth := 1
7673+ mut params_end := params_start
7674+ for params_end < clean.len {
7675+ if clean[params_end] == `(` {
7676+ depth++
7677+ } else if clean[params_end] == `)` {
7678+ depth--
7679+ if depth == 0 {
7680+ break
7681+ }
7682+ }
7683+ params_end++
7684+ }
7685+ if params_end < clean.len {
7686+ mut fn_parts := []string {}
7687+ params_str := clean[params_start..params_end]
7688+ if params_str.trim_space ().len > 0 {
7689+ for part in split_params (params_str) {
7690+ fn_parts << subst_generic_text (normalize_fn_type_param_text (part), args, params)
7691+ }
7692+ }
7693+ ret_str := clean[params_end + 1 ..].trim_space ()
7694+ if ret_str.len > 0 {
7695+ return 'fn(${fn_parts .join (', ')}) ${subst_generic_text (ret_str , args , params )}'
7696+ }
7697+ return 'fn(${fn_parts .join (', ')})'
7698+ }
7699+ }
76007700 bracket := clean.index_u8 (`[` )
76017701 if bracket > 0 {
76027702 bracket_end := find_matching_bracket (clean, bracket)
0 commit comments