v / vlib / v2 / markused / markused.v
4726 lines · 4524 sloc · 132.99 KB
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module markused
6
7import v2.ast
8import v2.token
9import v2.types
10
11// Guard functions for ARM64 backend where default-initialized sum types have NULL data pointers.
12fn sumtype_payload_word_is_valid(tag_word u64, data_word u64) bool {
13 if data_word == 0 {
14 return false
15 }
16 // Native v2 backends use `(tag, data_ptr)` for sumtypes. If the first word
17 // looks like a small tag, the payload must be a real pointer, not a leaked
18 // enum/default value like `3`.
19 if tag_word < 256 {
20 return data_word >= 0x10000 && data_word < 0x0000800000000000
21 }
22 return true
23}
24
25fn string_ok(s string) bool {
26 if s.len == 0 {
27 return true
28 }
29 if s.len < 0 || s.len > 1024 {
30 return false
31 }
32 ptr := unsafe { u64(s.str) }
33 return ptr >= 0x10000 && ptr < 0x0000800000000000
34}
35
36fn expr_ok(expr ast.Expr) bool {
37 tag_word := unsafe { (&u64(&expr))[0] }
38 data_word := unsafe { (&u64(&expr))[1] }
39 return sumtype_payload_word_is_valid(tag_word, data_word)
40}
41
42fn stmt_ok(stmt ast.Stmt) bool {
43 tag_word := unsafe { (&u64(&stmt))[0] }
44 data_word := unsafe { (&u64(&stmt))[1] }
45 return sumtype_payload_word_is_valid(tag_word, data_word)
46}
47
48fn type_ok(typ types.Type) bool {
49 tag_word := unsafe { (&u64(&typ))[0] }
50 data_word := unsafe { (&u64(&typ))[1] }
51 if data_word == 0 {
52 return false
53 }
54 return sumtype_payload_word_is_valid(tag_word, data_word)
55}
56
57const builtin_cast_type_names = [
58 'bool',
59 'byte',
60 'byteptr',
61 'char',
62 'charptr',
63 'f32',
64 'f64',
65 'i8',
66 'i16',
67 'i32',
68 'i64',
69 'int',
70 'isize',
71 'rune',
72 'u8',
73 'u16',
74 'u32',
75 'u64',
76 'usize',
77 'voidptr',
78]
79
80struct FnInfo {
81 key string
82 mod string
83 file string
84 decl ast.FnDecl
85 // decl_id is the FlatNodeId of the FnDecl in the source FlatAst, or
86 // -1 when the FnInfo was produced by the legacy []ast.File path that
87 // has no flat backing. When >= 0, walk_collected_from_flat iterates
88 // the body via Cursor instead of info.decl.stmts so future PRs can
89 // port walk_stmt arms to cursor input one at a time.
90 decl_id ast.FlatNodeId = -1
91 // has_body is true when the FnDecl has a non-empty body. In flat mode
92 // the body is not decoded into info.decl.stmts (signature-only
93 // decode), so we precompute the bit at collect time from the body
94 // edge's child count. In legacy mode it mirrors info.decl.stmts.len > 0.
95 has_body bool
96}
97
98struct Walker {
99 files []ast.File
100 env &types.Environment = unsafe { nil }
101 opts MarkUsedOptions
102mut:
103 fns []FnInfo
104 queue []int
105 queued_fn_indices map[int]bool
106
107 used_keys map[string]bool
108
109 module_names map[string]bool
110 module_alias_to_real map[string]string
111 type_names map[string]bool
112 interface_type_names map[string]bool
113 interface_method_names map[string][]string
114 interface_embedded_names map[string][]string
115 methods_by_receiver map[string][]int
116 struct_field_receivers map[string][]string
117 struct_embedded_receivers map[string][]string
118 struct_fn_fields map[string]bool
119 global_interface_names map[string]string
120 const_fn_value_aliases map[string]string
121 selective_import_fn_targets map[string]string
122 called_fn_name_candidate_cache map[string][]string
123
124 lookup map[string][]int
125
126 cur_fn_scope &types.Scope = unsafe { nil }
127 cur_fn_decl ast.FnDecl
128 cur_fn_file string
129}
130
131pub struct MarkUsedOptions {
132pub:
133 minimal_runtime_roots bool
134}
135
136// mark_used walks reachable function/method bodies and returns declaration keys
137// for the functions that are used at least once.
138pub fn mark_used(files []ast.File, env &types.Environment) map[string]bool {
139 mut w := new_walker(files, env, MarkUsedOptions{})
140 return w.walk()
141}
142
143pub fn mark_used_with_options(files []ast.File, env &types.Environment, opts MarkUsedOptions) map[string]bool {
144 mut w := new_walker(files, env, opts)
145 return w.walk()
146}
147
148// decl_key computes the stable key used by mark_used for a function declaration.
149pub fn decl_key(module_name string, decl ast.FnDecl, env &types.Environment) string {
150 mod_name := normalize_module_name(module_name)
151 if is_method_decl(decl) {
152 receiver := receiver_primary_name(mod_name, decl, env)
153 return '${mod_name}|m|${receiver}|${normalize_method_name(decl.name)}'
154 }
155 return '${mod_name}|f|${decl.name}'
156}
157
158// decl_key_from_cursor computes the stable markused key for a flat FnDecl cursor.
159pub fn decl_key_from_cursor(module_name string, c ast.Cursor, env &types.Environment) string {
160 decl := c.fn_decl_signature()
161 if decl.name == '' {
162 return ''
163 }
164 return decl_key(module_name, decl, env)
165}
166
167fn new_walker(files []ast.File, env &types.Environment, opts MarkUsedOptions) Walker {
168 return Walker{
169 files: files
170 env: unsafe { env }
171 opts: opts
172 used_keys: map[string]bool{}
173 queued_fn_indices: map[int]bool{}
174 module_names: map[string]bool{}
175 type_names: map[string]bool{}
176 interface_type_names: map[string]bool{}
177 interface_method_names: map[string][]string{}
178 interface_embedded_names: map[string][]string{}
179 methods_by_receiver: map[string][]int{}
180 struct_field_receivers: map[string][]string{}
181 struct_embedded_receivers: map[string][]string{}
182 struct_fn_fields: map[string]bool{}
183 global_interface_names: map[string]string{}
184 const_fn_value_aliases: map[string]string{}
185 selective_import_fn_targets: map[string]string{}
186 called_fn_name_candidate_cache: map[string][]string{}
187 lookup: map[string][]int{}
188 }
189}
190
191fn (mut w Walker) walk() map[string]bool {
192 w.collect_defs()
193 return w.walk_collected()
194}
195
196// walk_collected runs the body-walking phase after collect_defs (or
197// collect_defs_from_flat) has populated w.fns. Split out so flat-input
198// entry points can substitute their own collect step.
199fn (mut w Walker) walk_collected() map[string]bool {
200 if w.fns.len == 0 {
201 return map[string]bool{}
202 }
203 if !w.seed_roots() {
204 mut all := map[string]bool{}
205 for info in w.fns {
206 all[info.key] = true
207 }
208 return all
209 }
210 mut qi := 0
211 for qi < w.queue.len {
212 idx := w.queue[qi]
213 qi++
214 info := w.fns[idx]
215 w.cur_fn_scope = w.lookup_fn_scope(info)
216 w.cur_fn_decl = info.decl
217 w.cur_fn_file = info.file
218 w.walk_stmts(info.decl.stmts, info.mod)
219 w.cur_fn_scope = unsafe { nil }
220 w.cur_fn_file = ''
221 }
222 return w.used_keys
223}
224
225// walk_collected_from_flat mirrors walk_collected but, for FnInfos that
226// originated from flat input (decl_id >= 0), iterates the body via Cursor
227// instead of the pre-rehydrated info.decl.stmts slice. For now each body
228// stmt is decoded back to ast.Stmt before being dispatched to walk_stmt —
229// later PRs port walk_stmt arms to accept cursor input directly so the
230// per-stmt rehydration can drop out. FnInfos without a flat backing
231// (decl_id < 0) fall back to the legacy slice walk.
232fn (mut w Walker) walk_collected_from_flat(flat &ast.FlatAst) map[string]bool {
233 if w.fns.len == 0 {
234 return map[string]bool{}
235 }
236 if !w.seed_roots_from_flat(flat) {
237 mut all := map[string]bool{}
238 for info in w.fns {
239 all[info.key] = true
240 }
241 return all
242 }
243 mut qi := 0
244 for qi < w.queue.len {
245 idx := w.queue[qi]
246 qi++
247 info := w.fns[idx]
248 w.cur_fn_scope = w.lookup_fn_scope(info)
249 w.cur_fn_decl = info.decl
250 w.cur_fn_file = info.file
251 if info.decl_id >= 0 {
252 w.walk_fn_body_cursor(flat, info.decl_id, info.mod)
253 } else {
254 w.walk_stmts(info.decl.stmts, info.mod)
255 }
256 w.cur_fn_scope = unsafe { nil }
257 w.cur_fn_file = ''
258 }
259 return w.used_keys
260}
261
262// walk_fn_body_cursor iterates an FnDecl body (edge 3 of stmt_fn_decl) via
263// CursorList and dispatches each child through walk_stmt_cursor. Arms that
264// have been ported to cursor input avoid per-stmt rehydration; un-ported
265// arms decode back to ast.Stmt and call walk_stmt.
266fn (mut w Walker) walk_fn_body_cursor(flat &ast.FlatAst, decl_id ast.FlatNodeId, mod_name string) {
267 body := ast.Cursor{
268 flat: unsafe { flat }
269 id: decl_id
270 }.list_at(3)
271 for i in 0 .. body.len() {
272 w.walk_stmt_cursor(body.at(i), mod_name)
273 }
274}
275
276// walk_stmt_cursor is the cursor-input analogue of walk_stmt. Arms that have
277// been ported operate directly on the FlatNode (no Stmt rehydration); the
278// fallback decodes the stmt and delegates to walk_stmt so partial coverage
279// stays bit-identical with the legacy walker.
280//
281// Ported arms: stmt_assert, stmt_assign, stmt_attributes, stmt_block,
282// stmt_comptime, stmt_const_decl, stmt_defer, stmt_enum_decl, stmt_expr,
283// stmt_for, stmt_for_in, stmt_global_decl, stmt_label, stmt_return,
284// stmt_struct_decl, stmt_type_decl — every kind the legacy walk_stmt
285// matched explicitly. Nested expressions are still routed through
286// walk_expr (decoded per edge) until walk_expr itself gets a cursor-input
287// port. The else arm catches stmt kinds the legacy walker ignored
288// (stmt_asm/_directive/_empty/_flow_control/_fn_decl/_import/_interface_decl
289// /_module) plus any future kinds; it stays in place as a safety net.
290fn (mut w Walker) walk_stmt_cursor(c ast.Cursor, mod_name string) {
291 if !c.is_valid() {
292 return
293 }
294 match c.kind() {
295 .stmt_assert {
296 w.walk_expr_cursor_edge(c, 0, mod_name)
297 w.walk_expr_cursor_edge(c, 1, mod_name)
298 }
299 .stmt_attributes {
300 // edge 0 = aux_list of aux_attribute children; each attribute has
301 // edge 0 = value expr, edge 1 = comptime_cond expr.
302 attrs := c.list_at(0)
303 for i in 0 .. attrs.len() {
304 attr := attrs.at(i)
305 if !attr.is_valid() {
306 continue
307 }
308 w.walk_expr_cursor_edge(attr, 0, mod_name)
309 w.walk_expr_cursor_edge(attr, 1, mod_name)
310 }
311 }
312 .stmt_assign {
313 ec := c.edge_count()
314 lhs_len := c.extra_int()
315 // Mirror the legacy ordering: first the interface-conversion
316 // checks across paired (lhs[i], rhs[i]) edges, then walk all
317 // lhs exprs, then all rhs exprs.
318 pair_len := if lhs_len < ec - lhs_len { lhs_len } else { ec - lhs_len }
319 for i in 0 .. pair_len {
320 lhs_c := c.edge(i)
321 rhs_c := c.edge(lhs_len + i)
322 if !lhs_c.is_valid() || !rhs_c.is_valid() {
323 continue
324 }
325 w.mark_assignment_interface_conversion_cursor(lhs_c, rhs_c, mod_name)
326 }
327 for i in lhs_len .. ec {
328 w.mark_fn_value_expr_cursor(c.edge(i), mod_name)
329 }
330 for i in 0 .. ec {
331 w.walk_expr_cursor_edge(c, i, mod_name)
332 }
333 }
334 .stmt_block {
335 for i in 0 .. c.edge_count() {
336 w.walk_stmt_cursor(c.edge(i), mod_name)
337 }
338 }
339 .stmt_comptime {
340 w.mark_comptime_method_loop_receivers_cursor(c.edge(0), mod_name)
341 w.walk_stmt_cursor(c.edge(0), mod_name)
342 }
343 .stmt_const_decl {
344 // edge 0 = fields (aux_field_init list); each field edge 0 = value expr.
345 w.walk_field_value_list(c, 0, mod_name)
346 }
347 .stmt_defer {
348 for i in 0 .. c.edge_count() {
349 w.walk_stmt_cursor(c.edge(i), mod_name)
350 }
351 }
352 .stmt_enum_decl {
353 // edge 2 = fields (aux_field_decl list); each field edge 1 = value expr.
354 w.walk_field_decl_list(c, 2, mod_name, false)
355 }
356 .stmt_expr {
357 w.walk_expr_cursor_edge(c, 0, mod_name)
358 }
359 .stmt_for {
360 w.walk_stmt_cursor(c.edge(0), mod_name)
361 w.walk_expr_cursor_edge(c, 1, mod_name)
362 w.walk_stmt_cursor(c.edge(2), mod_name)
363 for i in 3 .. c.edge_count() {
364 w.walk_stmt_cursor(c.edge(i), mod_name)
365 }
366 }
367 .stmt_for_in {
368 w.walk_expr_cursor_edge(c, 0, mod_name)
369 w.walk_expr_cursor_edge(c, 1, mod_name)
370 w.walk_expr_cursor_edge(c, 2, mod_name)
371 }
372 .stmt_global_decl {
373 // edge 1 = fields (aux_field_decl list); each field walks typ+value.
374 w.walk_field_decl_list(c, 1, mod_name, true)
375 }
376 .stmt_label {
377 w.walk_stmt_cursor(c.edge(0), mod_name)
378 }
379 .stmt_return {
380 for i in 0 .. c.edge_count() {
381 ec := c.edge(i)
382 if !ec.is_valid() {
383 continue
384 }
385 w.mark_fn_value_expr_cursor(ec, mod_name)
386 w.walk_expr_cursor(ec, mod_name)
387 w.mark_interface_conversion_methods_cursor(w.cur_fn_decl.typ.return_type, ec,
388 mod_name)
389 w.mark_result_error_return_methods_cursor(w.cur_fn_decl.typ.return_type, ec,
390 mod_name)
391 }
392 }
393 .stmt_struct_decl {
394 // edge 4 = fields (aux_field_decl list); each field walks typ+value.
395 w.walk_field_decl_list(c, 4, mod_name, true)
396 }
397 .stmt_type_decl {
398 // edge 0 = base_type (expr); edge 3 = variants (aux expr list).
399 w.walk_expr_cursor_edge(c, 0, mod_name)
400 variants := c.list_at(3)
401 for i in 0 .. variants.len() {
402 vc := variants.at(i)
403 if !vc.is_valid() {
404 continue
405 }
406 w.walk_expr_cursor(vc, mod_name)
407 }
408 }
409 // Kinds intentionally ignored by the walker (mirror of legacy
410 // walk_stmt's `else {}`): stmt_asm, stmt_directive, stmt_empty,
411 // stmt_flow_control (break/continue/goto — common in fn bodies,
412 // hence worth avoiding a decode_stmt), stmt_fn_decl,
413 // stmt_import, stmt_interface_decl, stmt_module. The legacy
414 // walker walks none of these so neither do we.
415 else {}
416 }
417}
418
419// walk_expr_cursor_edge dispatches child edge `edge_i` of `parent` through
420// walk_expr_cursor. Kept as a tiny helper so callers don't have to do the
421// edge-+-validity dance inline.
422fn (mut w Walker) walk_expr_cursor_edge(parent ast.Cursor, edge_i int, mod_name string) {
423 w.walk_expr_cursor(parent.edge(edge_i), mod_name)
424}
425
426// walk_expr_cursor is the cursor-input analogue of walk_expr. Ported arms
427// operate directly on the FlatNode; un-ported arms decode the expr and
428// delegate to walk_expr so partial coverage stays bit-identical with the
429// legacy walker.
430//
431// Ported arms (no per-node decoding):
432// * leaves: expr_basic_literal, expr_empty, expr_ident, typ_nil, typ_none
433// * single-edge recursers: expr_comptime, expr_lambda, expr_modifier,
434// expr_paren, expr_postfix, expr_prefix, expr_sql, typ_array,
435// typ_option, typ_pointer, typ_result, typ_thread
436// * two-edge recursers: expr_as_cast, expr_generic_arg_or_index,
437// expr_index, expr_range, typ_array_fixed, typ_channel, typ_map
438// * N-edge recursers: expr_keyword_operator, expr_tuple, typ_tuple
439// * nested-stmt / packed-shape arms: expr_array_init, expr_fn_literal,
440// expr_if, expr_if_guard, expr_lock, expr_map_init, expr_match,
441// expr_or, expr_select, expr_unsafe, typ_anon_struct, typ_fn,
442// typ_generic
443// * no-mark structural arms: expr_assoc, expr_generic_args
444//
445// The remaining un-ported arms (expr_call, expr_call_or_cast, expr_cast,
446// expr_infix, expr_init, expr_selector, expr_string_inter) all invoke
447// mark_* helpers that take decoded ast.Expr / ast.CallExpr / ast.InfixExpr
448// today. Porting them buys little until those helpers grow cursor-input
449// variants; the else fallback decodes + delegates and stays bit-identical.
450fn (mut w Walker) walk_expr_cursor(c ast.Cursor, mod_name string) {
451 if !c.is_valid() {
452 return
453 }
454 match c.kind() {
455 // Leaves with no children and no walker side-effects. The legacy
456 // walk_expr arms for Keyword / StringLiteral / Lifetime hit its
457 // else {}, so they belong here too.
458 .expr_basic_literal, .expr_empty, .expr_keyword, .expr_lifetime, .expr_string, .typ_nil,
459 .typ_none {}
460 .expr_ident {
461 name := c.name()
462 w.mark_ierror_wrapper_dependencies(name, mod_name)
463 if (!w.opts.minimal_runtime_roots || should_mark_ident_as_fn(name))
464 && !w.is_cast_type_name(name) {
465 w.mark_fn_name(name, mod_name)
466 }
467 }
468 // Single-edge recursers — child at edge 0.
469 .expr_comptime, .expr_lambda, .expr_modifier, .expr_paren, .expr_postfix, .expr_prefix,
470 .expr_sql, .typ_array, .typ_option, .typ_pointer, .typ_result, .typ_thread {
471 w.walk_expr_cursor(c.edge(0), mod_name)
472 }
473 // Two-edge recursers.
474 .expr_as_cast, .expr_generic_arg_or_index, .expr_index, .expr_range, .typ_array_fixed,
475 .typ_channel, .typ_map {
476 w.walk_expr_cursor(c.edge(0), mod_name)
477 w.walk_expr_cursor(c.edge(1), mod_name)
478 }
479 // N-edge recursers — every direct edge is an Expr to walk.
480 .expr_keyword_operator, .typ_tuple {
481 for i in 0 .. c.edge_count() {
482 w.walk_expr_cursor(c.edge(i), mod_name)
483 }
484 }
485 .expr_tuple {
486 for i in 0 .. c.edge_count() {
487 value_c := c.edge(i)
488 w.mark_fn_value_expr_cursor(value_c, mod_name)
489 w.walk_expr_cursor(value_c, mod_name)
490 }
491 }
492 // typ_generic packs name as edge 0 and generic params at edges 1..N;
493 // the legacy walker calls walk_expr on each of them in order.
494 .typ_generic {
495 for i in 0 .. c.edge_count() {
496 w.walk_expr_cursor(c.edge(i), mod_name)
497 }
498 }
499 // expr_array_init: edge 0 = typ, 1 = init, 2 = cap, 3 = len,
500 // 4 = update_expr, 5.. = exprs. Legacy walk order is typ, exprs,
501 // init, cap, len, update_expr.
502 .expr_array_init {
503 ec := c.edge_count()
504 w.walk_expr_cursor(c.edge(0), mod_name)
505 for i in 5 .. ec {
506 item_c := c.edge(i)
507 w.mark_fn_value_expr_cursor(item_c, mod_name)
508 w.walk_expr_cursor(item_c, mod_name)
509 }
510 w.mark_fn_value_expr_cursor(c.edge(1), mod_name)
511 w.walk_expr_cursor(c.edge(1), mod_name)
512 w.walk_expr_cursor(c.edge(2), mod_name)
513 w.walk_expr_cursor(c.edge(3), mod_name)
514 w.walk_expr_cursor(c.edge(4), mod_name)
515 }
516 // expr_map_init: edge 0 = typ, 1..1+keys_len = keys,
517 // 1+keys_len.. = vals. keys_len is packed in `extra`.
518 .expr_map_init {
519 keys_len := c.extra_int()
520 ec := c.edge_count()
521 w.walk_expr_cursor(c.edge(0), mod_name)
522 for i in 1 .. (1 + keys_len) {
523 key_c := c.edge(i)
524 w.mark_fn_value_expr_cursor(key_c, mod_name)
525 w.walk_expr_cursor(key_c, mod_name)
526 }
527 for i in (1 + keys_len) .. ec {
528 value_c := c.edge(i)
529 w.mark_fn_value_expr_cursor(value_c, mod_name)
530 w.walk_expr_cursor(value_c, mod_name)
531 }
532 }
533 // expr_lock: lock_len + rlock_len are packed in `extra` (low/high u16).
534 // Layout is lock_exprs, rlock_exprs, stmts.
535 .expr_lock {
536 packed := c.extra_int()
537 lock_len := packed & 0xFFFF
538 rlock_len := (packed >> 16) & 0xFFFF
539 ec := c.edge_count()
540 for i in 0 .. lock_len {
541 w.walk_expr_cursor(c.edge(i), mod_name)
542 }
543 for i in lock_len .. (lock_len + rlock_len) {
544 w.walk_expr_cursor(c.edge(i), mod_name)
545 }
546 for i in (lock_len + rlock_len) .. ec {
547 w.walk_stmt_cursor(c.edge(i), mod_name)
548 }
549 }
550 // expr_if: edge 0 = cond, edge 1 = else_expr, edges 2.. = stmts.
551 // Legacy walks cond → stmts → else_expr.
552 .expr_if {
553 ec := c.edge_count()
554 w.walk_expr_cursor(c.edge(0), mod_name)
555 for i in 2 .. ec {
556 w.walk_stmt_cursor(c.edge(i), mod_name)
557 }
558 w.walk_expr_cursor(c.edge(1), mod_name)
559 }
560 // expr_if_guard: edge 0 = the embedded AssignStmt.
561 .expr_if_guard {
562 w.walk_stmt_cursor(c.edge(0), mod_name)
563 }
564 // expr_or: edge 0 = expr, edges 1.. = stmts.
565 .expr_or {
566 w.walk_expr_cursor(c.edge(0), mod_name)
567 for i in 1 .. c.edge_count() {
568 w.walk_stmt_cursor(c.edge(i), mod_name)
569 }
570 }
571 // expr_unsafe: all edges are stmts.
572 .expr_unsafe {
573 for i in 0 .. c.edge_count() {
574 w.walk_stmt_cursor(c.edge(i), mod_name)
575 }
576 }
577 // expr_fn_literal: edge 0 = fn_typ, edges 1..1+cap_len = captured
578 // (not walked by the legacy walker), edges 1+cap_len.. = stmts.
579 // cap_len is packed in `extra`.
580 .expr_fn_literal {
581 cap_len := c.extra_int()
582 for i in (1 + cap_len) .. c.edge_count() {
583 w.walk_stmt_cursor(c.edge(i), mod_name)
584 }
585 }
586 // expr_select: edge 0 = stmt, edge 1 = next (expr), edges 2.. = stmts.
587 // Legacy walks stmt → stmts → next.
588 .expr_select {
589 ec := c.edge_count()
590 w.walk_stmt_cursor(c.edge(0), mod_name)
591 for i in 2 .. ec {
592 w.walk_stmt_cursor(c.edge(i), mod_name)
593 }
594 w.walk_expr_cursor(c.edge(1), mod_name)
595 }
596 // expr_match: edge 0 = scrutinee expr, edges 1.. = aux_match_branch.
597 // Each match_branch has edge 0 = aux_list of cond exprs, edge 1 =
598 // aux_list of stmts.
599 .expr_match {
600 w.walk_expr_cursor(c.edge(0), mod_name)
601 for i in 1 .. c.edge_count() {
602 branch := c.edge(i)
603 if !branch.is_valid() {
604 continue
605 }
606 conds := branch.list_at(0)
607 for j in 0 .. conds.len() {
608 w.walk_expr_cursor(conds.at(j), mod_name)
609 }
610 stmts := branch.list_at(1)
611 for j in 0 .. stmts.len() {
612 w.walk_stmt_cursor(stmts.at(j), mod_name)
613 }
614 }
615 }
616 // typ_fn: edge 0 = generic_params (not walked by legacy), edge 1 =
617 // aux_list of aux_parameter, edge 2 = return_type. Each parameter
618 // node has edge 0 = its type.
619 .typ_fn {
620 params_list := c.list_at(1)
621 for i in 0 .. params_list.len() {
622 param := params_list.at(i)
623 if !param.is_valid() {
624 continue
625 }
626 w.walk_expr_cursor(param.edge(0), mod_name)
627 }
628 w.walk_expr_cursor(c.edge(2), mod_name)
629 }
630 // typ_anon_struct: edge 0 = generic_params (not walked by legacy),
631 // edge 1 = embedded (aux_list of exprs), edge 2 = fields
632 // (aux_list of aux_field_decl).
633 .typ_anon_struct {
634 embedded := c.list_at(1)
635 for i in 0 .. embedded.len() {
636 w.walk_expr_cursor(embedded.at(i), mod_name)
637 }
638 w.walk_field_decl_list(c, 2, mod_name, true)
639 }
640 // expr_assoc: edge 0 = typ, edge 1 = expr, edges 2.. = aux_field_init
641 // nodes (each with edge 0 = value expr). No mark side-effects, so the
642 // arm is purely structural.
643 .expr_assoc {
644 w.walk_expr_cursor(c.edge(0), mod_name)
645 w.walk_expr_cursor(c.edge(1), mod_name)
646 for i in 2 .. c.edge_count() {
647 field := c.edge(i)
648 if !field.is_valid() {
649 continue
650 }
651 value_c := field.edge(0)
652 w.mark_fn_value_expr_cursor(value_c, mod_name)
653 w.walk_expr_cursor(value_c, mod_name)
654 }
655 }
656 // expr_generic_args: edge 0 = lhs, edges 1.. = args. No mark
657 // side-effects — every edge is a walked expr.
658 .expr_generic_args {
659 for i in 0 .. c.edge_count() {
660 w.walk_expr_cursor(c.edge(i), mod_name)
661 }
662 }
663 // expr_call: edge 0 = lhs, edges 1.. = args. Cursor port uses
664 // mark_call_lhs_cursor + mark_call_arg_interface_conversions_cursor —
665 // both decode lhs.lhs (selector receiver fallback) and args (only
666 // when the matching param is an interface) on demand. Everything
667 // else is structural.
668 .expr_call {
669 lhs_c := c.edge(0)
670 w.mark_call_lhs_cursor(lhs_c, mod_name)
671 w.mark_call_arg_interface_conversions_cursor(c, mod_name)
672 w.walk_expr_cursor(lhs_c, mod_name)
673 for i in 1 .. c.edge_count() {
674 arg_c := c.edge(i)
675 if arg_c.is_valid() && arg_c.kind() == .aux_field_init {
676 value_c := arg_c.edge(0)
677 if value_c.is_valid() && value_c.kind() == .expr_selector
678 && value_c.edge(0).kind() != .expr_empty {
679 w.mark_selector_fn_value_cursor_with_fallback(value_c, mod_name, true)
680 }
681 w.mark_fn_value_expr_cursor(value_c, mod_name)
682 w.walk_expr_cursor(value_c, mod_name)
683 continue
684 }
685 w.mark_fn_value_expr_cursor(arg_c, mod_name)
686 w.walk_expr_cursor(arg_c, mod_name)
687 }
688 }
689 // expr_call_or_cast: edge 0 = lhs, edge 1 = inner. When lhs is an
690 // Ident naming a cast type, behaves like a cast — call
691 // mark_interface_conversion_methods and walk only the inner expr.
692 // Otherwise behaves like a call (mark_call_lhs + the single-arg
693 // interface conversion check).
694 .expr_call_or_cast {
695 lhs_c := c.edge(0)
696 expr_c := c.edge(1)
697 if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && w.is_cast_type_name(lhs_c.name()) {
698 iface_name := w.interface_name_from_cursor(lhs_c, mod_name)
699 if iface_name != '' && expr_c.is_valid() {
700 w.mark_interface_conversion_methods_for_name_cursor(iface_name, expr_c,
701 mod_name)
702 }
703 w.walk_expr_cursor(expr_c, mod_name)
704 } else {
705 w.mark_call_lhs_cursor(lhs_c, mod_name)
706 w.mark_call_or_cast_arg_interface_conversion_cursor(c, mod_name)
707 w.walk_expr_cursor(lhs_c, mod_name)
708 w.mark_fn_value_expr_cursor(expr_c, mod_name)
709 w.walk_expr_cursor(expr_c, mod_name)
710 }
711 }
712 // expr_cast: edge 0 = typ, edge 1 = value. Legacy walker calls
713 // mark_interface_conversion_methods(typ, value) which is a no-op unless
714 // typ resolves to a known interface. We peek typ via
715 // interface_name_from_cursor (only matches Ident / SelectorExpr,
716 // both cheap), and only when it fires do we decode the value subtree
717 // to feed receiver_candidates_for_expr.
718 .expr_cast {
719 typ_c := c.edge(0)
720 expr_c := c.edge(1)
721 iface_name := w.interface_name_from_cursor(typ_c, mod_name)
722 if iface_name != '' && expr_c.is_valid() {
723 w.mark_interface_conversion_methods_for_name_cursor(iface_name, expr_c, mod_name)
724 }
725 w.walk_expr_cursor(typ_c, mod_name)
726 w.walk_expr_cursor(expr_c, mod_name)
727 }
728 // expr_init: edge 0 = typ, edges 1.. = aux_field_init (edge 0 = value).
729 // Legacy walker calls mark_interface_conversion_methods(typ, expr)
730 // where the value_expr is the whole InitExpr; receiver_candidates_for_
731 // cursor's expr_init branch reads pos + recurses into the typ cursor,
732 // so passing this cursor directly matches the legacy semantics.
733 .expr_init {
734 typ_c := c.edge(0)
735 iface_name := w.interface_name_from_cursor(typ_c, mod_name)
736 if iface_name != '' {
737 w.mark_interface_conversion_methods_for_name_cursor(iface_name, c, mod_name)
738 }
739 w.walk_expr_cursor(typ_c, mod_name)
740 for i in 1 .. c.edge_count() {
741 field := c.edge(i)
742 if !field.is_valid() {
743 continue
744 }
745 value_c := field.edge(0)
746 if value_c.is_valid() && value_c.kind() == .expr_selector
747 && w.init_field_is_fn_value_cursor(typ_c, field.name(), mod_name) {
748 w.mark_selector_fn_value_cursor_with_fallback(value_c, mod_name, true)
749 }
750 w.mark_fn_value_expr_cursor(value_c, mod_name)
751 w.walk_expr_cursor(value_c, mod_name)
752 }
753 }
754 // expr_infix: aux = op (Token int), edge 0 = lhs, edge 1 = rhs.
755 // mark_infix_operator_method is a no-op for ops outside the method-
756 // dispatching set, so we gate the decode on the op kind. When the op
757 // does qualify, we decode just the lhs subtree to feed
758 // receiver_candidates_for_expr, then walk both children via cursor.
759 .expr_infix {
760 op := unsafe { token.Token(int(c.aux())) }
761 if infix_operator_may_use_method(op) {
762 method_name := op.str()
763 receivers := w.receiver_candidates_for_cursor(c.edge(0), mod_name)
764 if receivers.len > 0 {
765 w.mark_method_name(method_name, receivers)
766 } else {
767 w.mark_method_name_fallback(method_name)
768 }
769 }
770 w.walk_expr_cursor(c.edge(0), mod_name)
771 w.walk_expr_cursor(c.edge(1), mod_name)
772 }
773 // expr_string_inter: edge 0 = aux_list of value strings (not walked),
774 // edge 1 = aux_list of inter nodes. Each inter has edge 0 = expr,
775 // edge 1 = format_expr. Legacy walker calls
776 // mark_string_interpolation_str_dependency(inter.expr) which needs a
777 // decoded ast.Expr to feed receiver_candidates_for_expr. We decode
778 // just that subtree per inter and walk both children via cursor —
779 // format_expr's subtree never gets decoded.
780 .expr_string_inter {
781 inters := c.list_at(1)
782 for i in 0 .. inters.len() {
783 inter := inters.at(i)
784 if !inter.is_valid() {
785 continue
786 }
787 expr_c := inter.edge(0)
788 if expr_c.is_valid() {
789 w.mark_string_interpolation_str_dependency_cursor(expr_c, mod_name)
790 }
791 w.walk_expr_cursor(expr_c, mod_name)
792 w.walk_expr_cursor(inter.edge(1), mod_name)
793 }
794 }
795 // expr_selector: edge 0 = lhs, edge 1 = rhs (Ident). Legacy walker calls
796 // mark_selector_fn_value(expr), which only marks module function values
797 // that resolve to an indexed function.
798 .expr_selector {
799 lhs_c := c.edge(0)
800 w.mark_selector_fn_value_cursor(c, mod_name)
801 w.walk_expr_cursor(lhs_c, mod_name)
802 }
803 // Every expr/type FlatNodeKind has an explicit arm above; aux_*
804 // nodes never reach walk_expr_cursor (they are addressed via list_at
805 // / edge by their parents). Anything else is a schema mismatch.
806 else {}
807 }
808}
809
810// walk_field_value_list iterates the aux_field_init list at `parent.edge(list_edge)`
811// and handles each field's value expr (aux_field_init edge 0). Used by
812// stmt_const_decl, whose only walk-relevant per-field datum is the value.
813fn (mut w Walker) walk_field_value_list(parent ast.Cursor, list_edge int, mod_name string) {
814 fields := parent.list_at(list_edge)
815 for i in 0 .. fields.len() {
816 field := fields.at(i)
817 if !field.is_valid() {
818 continue
819 }
820 w.mark_fn_value_expr_cursor(field.edge(0), mod_name)
821 w.walk_expr_cursor_edge(field, 0, mod_name)
822 }
823}
824
825// walk_field_decl_list iterates the aux_field_decl list at `parent.edge(list_edge)`.
826// Each aux_field_decl has edge 0 = typ, edge 1 = value, edge 2 = attrs. When
827// `walk_typ` is true the typ edge is also walked (struct/global decls);
828// otherwise only the value edge is walked (enum decls).
829fn (mut w Walker) walk_field_decl_list(parent ast.Cursor, list_edge int, mod_name string, walk_typ bool) {
830 fields := parent.list_at(list_edge)
831 for i in 0 .. fields.len() {
832 field := fields.at(i)
833 if !field.is_valid() {
834 continue
835 }
836 if walk_typ {
837 w.walk_expr_cursor_edge(field, 0, mod_name)
838 }
839 w.walk_expr_cursor_edge(field, 1, mod_name)
840 }
841}
842
843fn (mut w Walker) collect_defs() {
844 for file in w.files {
845 mod_name := normalize_module_name(file.mod)
846 w.add_module_name(mod_name)
847 w.collect_file_imports(file.name, file.imports)
848 for stmt in file.stmts {
849 w.collect_def_stmt(stmt, mod_name, file.name, ast.FlatNodeId(-1))
850 }
851 }
852 w.collect_const_fn_value_aliases()
853}
854
855// collect_defs_from_flat is the flat-input analogue of collect_defs. It
856// walks each file's top-level statements through Cursor and only decodes
857// the decls collect_def_stmt actually consumes (struct/enum/interface/
858// type/global/fn). Imports are read directly from cursor fields
859// (name / extra_str alias) so the []ImportStmt slice is never allocated.
860// The shared collect_def_stmt dispatch keeps the flat path bit-identical
861// to the legacy path on the same input.
862fn (mut w Walker) collect_defs_from_flat(flat &ast.FlatAst) {
863 for fi in 0 .. flat.files.len {
864 fc := flat.file_cursor(fi)
865 mod_name := normalize_module_name(fc.mod())
866 w.add_module_name(mod_name)
867 imports_list := fc.imports()
868 for i in 0 .. imports_list.len() {
869 imp_cur := imports_list.at(i)
870 name := imp_cur.name()
871 w.add_module_name(name)
872 alias := imp_cur.extra_str()
873 import_mod := if imp_cur.flag(ast.flag_is_aliased) {
874 name.all_after_last('.')
875 } else {
876 alias
877 }
878 for si in 0 .. imp_cur.edge_count() {
879 symbol := imp_cur.edge(si)
880 if symbol.is_valid() && symbol.kind() == .expr_ident {
881 w.selective_import_fn_targets['${fc.name()}:${symbol.name()}'] = markused_imported_symbol_fn_name(import_mod,
882 symbol.name())
883 }
884 }
885 if alias != '' {
886 w.add_module_name(alias)
887 // Map alias to real module name for correct symbol resolution.
888 // e.g., 'import v2.ssa.optimize as ssa_optimize' → 'ssa_optimize' → 'optimize'
889 real_name := if name.contains('.') {
890 name.all_after_last('.')
891 } else {
892 name
893 }
894 w.module_alias_to_real[alias] = real_name
895 }
896 }
897 file_name := fc.name()
898 stmts_list := fc.stmts()
899 for i in 0 .. stmts_list.len() {
900 c := stmts_list.at(i)
901 match c.kind() {
902 .stmt_fn_decl {
903 fn_decl := c.fn_decl_signature()
904 if fn_decl.name == '' {
905 continue
906 }
907 has_body := c.list_at(3).len() > 0
908 info := FnInfo{
909 key: decl_key(mod_name, fn_decl, w.env)
910 mod: mod_name
911 file: file_name
912 decl: fn_decl
913 decl_id: c.id
914 has_body: has_body
915 }
916 w.fns << info
917 idx := w.fns.len - 1
918 w.index_fn(idx, info)
919 }
920 .stmt_struct_decl, .stmt_enum_decl, .stmt_interface_decl, .stmt_type_decl,
921 .stmt_global_decl {
922 w.collect_def_cursor(c, mod_name)
923 }
924 else {}
925 }
926 }
927 }
928 w.collect_const_fn_value_aliases_from_flat(flat)
929}
930
931fn const_fn_value_alias_key(mod_name string, name string) string {
932 return '${normalize_module_name(mod_name)}:${name}'
933}
934
935fn (mut w Walker) collect_const_fn_value_aliases() {
936 for file in w.files {
937 mod_name := normalize_module_name(file.mod)
938 prev_fn_file := w.cur_fn_file
939 w.cur_fn_file = file.name
940 for stmt in file.stmts {
941 if !stmt_ok(stmt) {
942 continue
943 }
944 if stmt is ast.ConstDecl {
945 for field in stmt.fields {
946 if field.name == '' {
947 continue
948 }
949 if field.value is ast.Ident
950 && w.ident_resolves_to_fn_value(field.value.name, mod_name) {
951 w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, field.name)] = field.value.name
952 } else if field.value is ast.SelectorExpr {
953 target := w.selector_fn_value_target(field.value)
954 if target != '' {
955 w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, field.name)] = target
956 }
957 }
958 }
959 }
960 }
961 w.cur_fn_file = prev_fn_file
962 }
963}
964
965fn (mut w Walker) collect_const_fn_value_aliases_from_flat(flat &ast.FlatAst) {
966 for fi in 0 .. flat.files.len {
967 fc := flat.file_cursor(fi)
968 mod_name := normalize_module_name(fc.mod())
969 prev_fn_file := w.cur_fn_file
970 w.cur_fn_file = fc.name()
971 stmts_list := fc.stmts()
972 for i in 0 .. stmts_list.len() {
973 c := stmts_list.at(i)
974 if c.kind() != .stmt_const_decl {
975 continue
976 }
977 fields := c.list_at(0)
978 for j in 0 .. fields.len() {
979 field := fields.at(j)
980 name := field.name()
981 if name == '' {
982 continue
983 }
984 value_c := field.edge(0)
985 if !value_c.is_valid() {
986 continue
987 }
988 if value_c.kind() == .expr_ident
989 && w.ident_resolves_to_fn_value(value_c.name(), mod_name) {
990 w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, name)] =
991 value_c.name()
992 } else if value_c.kind() == .expr_selector {
993 target := w.selector_fn_value_target_cursor(value_c)
994 if target != '' {
995 w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, name)] = target
996 }
997 }
998 }
999 }
1000 w.cur_fn_file = prev_fn_file
1001 }
1002}
1003
1004fn (mut w Walker) collect_imports(imports []ast.ImportStmt) {
1005 for imp in imports {
1006 w.add_module_name(imp.name)
1007 if imp.alias != '' {
1008 w.add_module_name(imp.alias)
1009 // Map alias to real module name for correct symbol resolution.
1010 // e.g., 'import v2.ssa.optimize as ssa_optimize' → 'ssa_optimize' → 'optimize'
1011 real_name := if imp.name.contains('.') {
1012 imp.name.all_after_last('.')
1013 } else {
1014 imp.name
1015 }
1016 w.module_alias_to_real[imp.alias] = real_name
1017 }
1018 }
1019}
1020
1021fn markused_imported_symbol_fn_name(module_name string, name string) string {
1022 if module_name == '' || module_name == 'main' {
1023 return name
1024 }
1025 return '${module_name}__${name}'
1026}
1027
1028fn selective_import_fn_key(file_name string, name string) string {
1029 return '${file_name}:${name}'
1030}
1031
1032fn (mut w Walker) collect_file_imports(file_name string, imports []ast.ImportStmt) {
1033 w.collect_imports(imports)
1034 for imp in imports {
1035 if imp.symbols.len == 0 {
1036 continue
1037 }
1038 import_mod := if imp.is_aliased { imp.name.all_after_last('.') } else { imp.alias }
1039 for symbol in imp.symbols {
1040 if symbol is ast.Ident {
1041 w.selective_import_fn_targets[selective_import_fn_key(file_name, symbol.name)] = markused_imported_symbol_fn_name(import_mod,
1042 symbol.name)
1043 }
1044 }
1045 }
1046}
1047
1048fn (mut w Walker) collect_def_stmt(stmt ast.Stmt, mod_name string, file_name string, decl_id ast.FlatNodeId) {
1049 if !stmt_ok(stmt) {
1050 return
1051 }
1052 match stmt {
1053 ast.StructDecl {
1054 w.add_type_name(mod_name, stmt.name)
1055 w.add_struct_field_receivers(mod_name, stmt)
1056 w.add_struct_embedded_receivers(mod_name, stmt)
1057 w.add_struct_fn_fields(mod_name, stmt)
1058 }
1059 ast.EnumDecl {
1060 w.add_type_name(mod_name, stmt.name)
1061 }
1062 ast.InterfaceDecl {
1063 w.add_type_name(mod_name, stmt.name)
1064 w.add_interface_decl(mod_name, stmt)
1065 }
1066 ast.TypeDecl {
1067 w.add_type_name(mod_name, stmt.name)
1068 }
1069 ast.GlobalDecl {
1070 w.add_global_interface_names(mod_name, stmt)
1071 }
1072 ast.FnDecl {
1073 if stmt.name == '' {
1074 return
1075 }
1076 info := FnInfo{
1077 key: decl_key(mod_name, stmt, w.env)
1078 mod: mod_name
1079 file: file_name
1080 decl: stmt
1081 decl_id: decl_id
1082 has_body: stmt.stmts.len > 0
1083 }
1084 w.fns << info
1085 idx := w.fns.len - 1
1086 w.index_fn(idx, info)
1087 }
1088 else {}
1089 }
1090}
1091
1092fn (mut w Walker) collect_def_cursor(c ast.Cursor, mod_name string) {
1093 if !c.is_valid() {
1094 return
1095 }
1096 match c.kind() {
1097 .stmt_struct_decl {
1098 w.add_type_name(mod_name, c.name())
1099 w.add_struct_field_receivers_cursor(mod_name, c)
1100 w.add_struct_embedded_receivers_cursor(mod_name, c)
1101 w.add_struct_fn_fields_cursor(mod_name, c)
1102 }
1103 .stmt_enum_decl, .stmt_type_decl {
1104 w.add_type_name(mod_name, c.name())
1105 }
1106 .stmt_interface_decl {
1107 w.add_interface_decl_cursor(mod_name, c)
1108 }
1109 .stmt_global_decl {
1110 w.add_global_interface_names_cursor(mod_name, c)
1111 }
1112 else {}
1113 }
1114}
1115
1116fn (mut w Walker) seed_roots() bool {
1117 mut has_root := false
1118 for i, info in w.fns {
1119 if is_main_root(info) {
1120 w.mark_fn(i)
1121 has_root = true
1122 }
1123 }
1124 if has_root {
1125 if w.opts.minimal_runtime_roots {
1126 w.seed_minimal_runtime_roots()
1127 } else {
1128 w.seed_generic_specialization_roots()
1129 w.seed_codegen_required_roots()
1130 // Also seed module init() functions (called from synthesized main)
1131 for i, info in w.fns {
1132 if is_module_init(info) {
1133 w.mark_fn(i)
1134 }
1135 }
1136 w.seed_top_level_initializer_roots()
1137 w.seed_drop_method_roots()
1138 }
1139 return true
1140 }
1141 for i, info in w.fns {
1142 if is_test_root(info) || is_module_init(info) {
1143 w.mark_fn(i)
1144 has_root = true
1145 }
1146 }
1147 if has_root {
1148 if w.opts.minimal_runtime_roots {
1149 w.seed_minimal_runtime_roots()
1150 } else {
1151 w.seed_generic_specialization_roots()
1152 w.seed_codegen_required_roots()
1153 w.seed_top_level_initializer_roots()
1154 w.seed_drop_method_roots()
1155 }
1156 }
1157 return has_root
1158}
1159
1160fn (mut w Walker) seed_minimal_runtime_roots() {
1161}
1162
1163fn (mut w Walker) seed_roots_from_flat(flat &ast.FlatAst) bool {
1164 mut has_root := false
1165 for i, info in w.fns {
1166 if is_main_root(info) {
1167 w.mark_fn(i)
1168 has_root = true
1169 }
1170 }
1171 if has_root {
1172 if !w.opts.minimal_runtime_roots {
1173 w.seed_generic_specialization_roots()
1174 w.seed_codegen_required_roots()
1175 // Also seed module init() functions (called from synthesized main)
1176 for i, info in w.fns {
1177 if is_module_init(info) {
1178 w.mark_fn(i)
1179 }
1180 }
1181 w.seed_top_level_initializer_roots_from_flat(flat)
1182 w.seed_drop_method_roots()
1183 }
1184 return true
1185 }
1186 for i, info in w.fns {
1187 if is_test_root(info) || is_module_init(info) {
1188 w.mark_fn(i)
1189 has_root = true
1190 }
1191 }
1192 if has_root {
1193 if !w.opts.minimal_runtime_roots {
1194 w.seed_generic_specialization_roots()
1195 w.seed_codegen_required_roots()
1196 w.seed_top_level_initializer_roots_from_flat(flat)
1197 w.seed_drop_method_roots()
1198 }
1199 }
1200 return has_root
1201}
1202
1203fn (mut w Walker) seed_codegen_required_roots() {
1204 for i, info in w.fns {
1205 if w.is_codegen_required_root(info) {
1206 w.mark_fn(i)
1207 }
1208 }
1209}
1210
1211// seed_drop_method_roots marks every `Type.drop` method as live for the
1212// types that the ownership checker recorded in `env.drop_at_fn_exit`. The
1213// cleanc backend will emit synthetic `Type__drop(&var)` calls at fn-exit
1214// for those bindings; without seeding the methods here, dead-code elim
1215// would strip the bodies and leave the linker with unresolved symbols.
1216fn (mut w Walker) seed_drop_method_roots() {
1217 if w.env == unsafe { nil } {
1218 return
1219 }
1220 mut seen := map[string]bool{}
1221 for _, entries in w.env.drop_at_fn_exit {
1222 for entry in entries {
1223 if entry.type_name.len == 0 || entry.type_name in seen {
1224 continue
1225 }
1226 seen[entry.type_name] = true
1227 w.mark_method_name('drop', [entry.type_name])
1228 }
1229 }
1230}
1231
1232fn (w &Walker) is_codegen_required_root(info FnInfo) bool {
1233 decl := info.decl
1234 if !w.opts.minimal_runtime_roots && should_always_emit_for_markused(info.file) {
1235 return true
1236 }
1237 if !w.opts.minimal_runtime_roots && info.mod == 'builtin' && decl.name == 'print_backtrace' {
1238 return true
1239 }
1240 if info.mod == 'json2' && decl.name == 'enum_uses_json_as_number' {
1241 return true
1242 }
1243 if info.mod == 'time' && decl.is_method && decl.receiver.typ is ast.Ident
1244 && decl.receiver.typ.name == 'Duration' {
1245 return true
1246 }
1247 if info.mod == 'sync' {
1248 if decl.name in ['try_pop_priv', 'try_push_priv', 'new_channel_st', 'new_spin_lock'] {
1249 return true
1250 }
1251 if decl.is_method && decl.name == 'try_wait' {
1252 return true
1253 }
1254 }
1255 if is_builtin_map_file(info.file) && should_keep_builtin_map_decl(decl) {
1256 return true
1257 }
1258 if is_builtin_string_file(info.file) && should_keep_builtin_string_decl(decl) {
1259 return true
1260 }
1261 if is_builtin_array_file(info.file) && should_keep_builtin_array_decl(decl) {
1262 return true
1263 }
1264 if decl.name == 'str' || decl.name.ends_with('__str') {
1265 return true
1266 }
1267 if decl.name.starts_with('__sort_cmp_') {
1268 return true
1269 }
1270 if decl.is_method {
1271 if decl.name in ['+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>='] {
1272 return true
1273 }
1274 if decl.receiver.typ is ast.Type && decl.receiver.typ is ast.ArrayType {
1275 return true
1276 }
1277 }
1278 return false
1279}
1280
1281pub fn should_always_emit_for_markused(path string) bool {
1282 if path.ends_with('.vh') {
1283 return true
1284 }
1285 return is_builtin_runtime_keep_file(path)
1286}
1287
1288pub fn is_builtin_runtime_keep_file(path string) bool {
1289 normalized := path.replace('\\', '/')
1290 return normalized.ends_with('vlib/builtin/map.c.v')
1291 || normalized.ends_with('vlib/builtin/builtin.c.v')
1292 || normalized.ends_with('vlib/builtin/builtin.v')
1293 || normalized.ends_with('vlib/builtin/cfns_wrapper.c.v')
1294 || normalized.ends_with('vlib/builtin/allocation.c.v')
1295 || normalized.ends_with('vlib/builtin/prealloc.c.v')
1296 || normalized.ends_with('vlib/builtin/panicing.c.v')
1297 || normalized.ends_with('vlib/builtin/chan_option_result.v')
1298 || normalized.ends_with('vlib/builtin/int.v') || normalized.ends_with('vlib/builtin/rune.v')
1299 || normalized.ends_with('vlib/builtin/float.c.v')
1300 || normalized.ends_with('vlib/builtin/utf8.v')
1301 || normalized.ends_with('vlib/builtin/utf8.c.v')
1302 || normalized.ends_with('vlib/strings/builder.c.v')
1303 || normalized.ends_with('vlib/strconv/utilities.v')
1304 || normalized.ends_with('vlib/strconv/utilities.c.v')
1305 || normalized.ends_with('vlib/strconv/ftoa.c.v')
1306 || normalized.ends_with('vlib/strconv/f32_str.c.v')
1307 || normalized.ends_with('vlib/strconv/f64_str.c.v')
1308 || normalized.ends_with('vlib/math/bits/bits.v')
1309 || normalized.ends_with('vlib/math/bits/bits.c.v')
1310 || normalized.ends_with('vlib/sokol/memory/memory.c.v')
1311}
1312
1313pub fn is_builtin_map_file(path string) bool {
1314 normalized := path.replace('\\', '/')
1315 return normalized.ends_with('vlib/builtin/map.v')
1316}
1317
1318pub fn should_keep_builtin_map_decl(decl ast.FnDecl) bool {
1319 base_keep := decl.name in ['new_map', 'move', 'clear', 'key_to_index', 'meta_less',
1320 'meta_greater', 'ensure_extra_metas', 'ensure_extra_metas_grow', 'set', 'expand', 'rehash',
1321 'reserve', 'cached_rehash', 'get_and_set', 'get', 'get_check', 'exists', 'delete', 'keys',
1322 'values', 'clone', 'free', 'key', 'value', 'has_index', 'zeros_to_end', 'new_dense_array']
1323 return base_keep || decl.name.starts_with('map_eq_') || decl.name.starts_with('map_clone_')
1324 || decl.name.starts_with('map_free_')
1325}
1326
1327pub fn is_builtin_string_file(path string) bool {
1328 normalized := path.replace('\\', '/')
1329 return normalized.ends_with('vlib/builtin/string.v')
1330}
1331
1332pub fn should_keep_builtin_string_decl(decl ast.FnDecl) bool {
1333 return decl.name in ['eq', 'plus', 'plus_two', 'substr', 'substr_unsafe', 'repeat', 'free',
1334 'vstring', 'vstring_with_len', 'vstring_literal', 'vstring_literal_with_len', 'runes',
1335 'join', 'compare_strings', 'compare_strings_by_len', 'compare_lower_strings']
1336}
1337
1338pub fn is_builtin_array_file(path string) bool {
1339 normalized := path.replace('\\', '/')
1340 return normalized.ends_with('vlib/builtin/array.v')
1341}
1342
1343pub fn should_keep_builtin_array_decl(decl ast.FnDecl) bool {
1344 return decl.name in [
1345 '__new_array',
1346 '__new_array_with_default',
1347 '__new_array_with_multi_default',
1348 '__new_array_with_array_default',
1349 '__new_array_with_map_default',
1350 'new_array_from_c_array',
1351 'new_array_from_c_array_no_alloc',
1352 'new_array_from_array_and_c_array',
1353 'ensure_cap',
1354 'repeat',
1355 'repeat_to_depth',
1356 'insert',
1357 'insert_many',
1358 'prepend',
1359 'prepend_many',
1360 'delete',
1361 'delete_many',
1362 'clear',
1363 'reset',
1364 'trim',
1365 'drop',
1366 'get_unsafe',
1367 'get',
1368 'get_with_check',
1369 'first',
1370 'last',
1371 'pop_left',
1372 'pop',
1373 'delete_last',
1374 'slice',
1375 'slice_ni',
1376 'contains',
1377 'clone_static_to_depth',
1378 'clone',
1379 'clone_to_depth',
1380 'set_unsafe',
1381 'set',
1382 'push',
1383 'push_many',
1384 'reverse_in_place',
1385 'reverse',
1386 'free',
1387 'sort',
1388 'sort_with_compare',
1389 'sorted_with_compare',
1390 'copy',
1391 'write_u8',
1392 'write_rune',
1393 'write_string',
1394 'write_ptr',
1395 'grow_cap',
1396 'grow_len',
1397 'pointers',
1398 'panic_on_negative_len',
1399 'panic_on_negative_cap',
1400 ]
1401}
1402
1403fn (mut w Walker) seed_top_level_initializer_roots() {
1404 for file in w.files {
1405 mod_name := normalize_module_name(file.mod)
1406 for stmt in file.stmts {
1407 if !stmt_ok(stmt) {
1408 continue
1409 }
1410 match stmt {
1411 ast.ConstDecl, ast.GlobalDecl {
1412 w.walk_stmt(stmt, mod_name)
1413 }
1414 else {}
1415 }
1416 }
1417 }
1418}
1419
1420fn (mut w Walker) seed_top_level_initializer_roots_from_flat(flat &ast.FlatAst) {
1421 for i in 0 .. flat.files.len {
1422 fc := flat.file_cursor(i)
1423 mod_name := normalize_module_name(fc.mod())
1424 stmts := fc.stmts()
1425 for j in 0 .. stmts.len() {
1426 stmt_c := stmts.at(j)
1427 match stmt_c.kind() {
1428 .stmt_const_decl, .stmt_global_decl {
1429 w.walk_stmt_cursor(stmt_c, mod_name)
1430 }
1431 else {}
1432 }
1433 }
1434 }
1435}
1436
1437fn generic_base_name_from_key(key string) string {
1438 mut name := key
1439 bracket_idx := name.index_u8(`[`)
1440 if bracket_idx > 0 {
1441 name = name[..bracket_idx]
1442 }
1443 dot_idx := name.last_index_u8(`.`)
1444 if dot_idx > 0 && dot_idx < name.len - 1 {
1445 name = name[dot_idx + 1..]
1446 }
1447 return strip_generic_specialization_suffix(name)
1448}
1449
1450fn (mut w Walker) seed_generic_specialization_roots() {
1451 if w.env == unsafe { nil } {
1452 return
1453 }
1454 for key, _ in w.env.generic_types {
1455 base_name := generic_base_name_from_key(key)
1456 if base_name == '' {
1457 continue
1458 }
1459 w.mark_lookup('fn:${base_name}')
1460 w.mark_lookup('mname:${base_name}')
1461 }
1462}
1463
1464fn is_main_root(info FnInfo) bool {
1465 return !is_method_decl(info.decl) && info.mod == 'main' && info.decl.name == 'main'
1466}
1467
1468fn is_test_root(info FnInfo) bool {
1469 return !is_method_decl(info.decl) && info.decl.name.starts_with('test_')
1470 && info.decl.typ.params.len == 0
1471}
1472
1473fn is_method_decl(decl ast.FnDecl) bool {
1474 return decl.is_method || decl.is_static
1475}
1476
1477fn is_module_init(info FnInfo) bool {
1478 return !is_method_decl(info.decl) && (info.decl.name == 'init'
1479 || info.decl.name == 'deinit'
1480 || info.decl.name.starts_with('__v_init_consts_'))
1481}
1482
1483fn normalize_module_name(module_name string) string {
1484 if module_name == '' {
1485 return 'main'
1486 }
1487 return module_name
1488}
1489
1490fn sanitize_receiver_name(name string) string {
1491 if !string_ok(name) {
1492 return ''
1493 }
1494 mut out := name.trim_space()
1495 for out.len > 0 && (out[0] == `&` || out[0] == `?` || out[0] == `!`) {
1496 out = out[1..]
1497 }
1498 return out
1499}
1500
1501fn maybe_trim_module_prefix(mod_name string, name string) string {
1502 if mod_name == '' || mod_name == 'main' {
1503 return name
1504 }
1505 prefix := '${mod_name}__'
1506 if name.starts_with(prefix) {
1507 return name[prefix.len..]
1508 }
1509 return name
1510}
1511
1512fn add_unique_string(mut out []string, value string) {
1513 if value == '' {
1514 return
1515 }
1516 for existing in out {
1517 if existing == value {
1518 return
1519 }
1520 }
1521 out << value
1522}
1523
1524fn add_unique_int(mut out []int, value int) {
1525 for existing in out {
1526 if existing == value {
1527 return
1528 }
1529 }
1530 out << value
1531}
1532
1533fn add_unique_fn_name(mut out []string, value string) {
1534 if value == '' {
1535 return
1536 }
1537 for existing in out {
1538 if existing == value {
1539 return
1540 }
1541 }
1542 out << value
1543}
1544
1545fn type_name_candidates_from_type(mod_name string, typ types.Type) []string {
1546 mut out := []string{}
1547 names := [typ.name(), typ.base_type().name()]
1548 for raw_name in names {
1549 name := sanitize_receiver_name(raw_name)
1550 add_unique_string(mut out, name)
1551 add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
1552 if name.contains('__') {
1553 add_unique_string(mut out, name.all_after_last('__'))
1554 } else if mod_name != '' && mod_name != 'main' {
1555 add_unique_string(mut out, '${mod_name}__${name}')
1556 }
1557 // For array types like []Attribute, also add Array_Attribute form.
1558 if name.starts_with('[]') {
1559 elem := name[2..]
1560 add_unique_string(mut out, 'Array_${elem}')
1561 add_unique_string(mut out, maybe_trim_module_prefix(mod_name, 'Array_${elem}'))
1562 if mod_name != '' && mod_name != 'main' {
1563 add_unique_string(mut out, 'Array_${mod_name}__${elem}')
1564 }
1565 }
1566 }
1567 return out
1568}
1569
1570fn type_is_fn_value(typ types.Type) bool {
1571 return type_is_fn_value_with_depth(typ, 0)
1572}
1573
1574fn type_is_fn_value_with_depth(typ types.Type, depth int) bool {
1575 if depth > 16 || !type_ok(typ) {
1576 return false
1577 }
1578 match typ {
1579 types.FnType {
1580 return true
1581 }
1582 types.Alias {
1583 return type_is_fn_value_with_depth(typ.base_type, depth + 1)
1584 }
1585 else {
1586 return false
1587 }
1588 }
1589}
1590
1591fn (w &Walker) type_name_is_fn_value(name string, mod_name string) bool {
1592 if w.env == unsafe { nil } || name == '' || !string_ok(name) {
1593 return false
1594 }
1595 mut type_names := []string{}
1596 add_unique_string(mut type_names, name)
1597 add_unique_string(mut type_names, maybe_trim_module_prefix(mod_name, name))
1598 if name.contains('__') {
1599 add_unique_string(mut type_names, name.all_after_last('__'))
1600 } else if mod_name != '' && mod_name != 'main' {
1601 add_unique_string(mut type_names, '${mod_name}__${name}')
1602 }
1603 mut scope_names := []string{}
1604 add_unique_string(mut scope_names, mod_name)
1605 if name.contains('__') {
1606 add_unique_string(mut scope_names, name.all_before('__'))
1607 }
1608 add_unique_string(mut scope_names, 'builtin')
1609 for scope_name in scope_names {
1610 if scope_name == '' {
1611 continue
1612 }
1613 if scope := w.env.get_scope(scope_name) {
1614 for type_name in type_names {
1615 if typ := scope.lookup_type_parent(type_name, 0) {
1616 if type_is_fn_value(typ) {
1617 return true
1618 }
1619 }
1620 }
1621 }
1622 }
1623 return false
1624}
1625
1626fn (w &Walker) type_expr_is_fn_value(expr ast.Expr, mod_name string) bool {
1627 match expr {
1628 ast.Ident {
1629 return w.type_name_is_fn_value(expr.name, mod_name)
1630 }
1631 ast.SelectorExpr {
1632 if expr.lhs is ast.Ident {
1633 lhs_name := expr.lhs.name
1634 real_mod := w.module_alias_to_real[lhs_name] or { lhs_name }
1635 return w.type_name_is_fn_value(expr.rhs.name, real_mod)
1636 || w.type_name_is_fn_value('${real_mod}__${expr.rhs.name}', real_mod)
1637 }
1638 }
1639 ast.PrefixExpr {
1640 if expr.op == .amp {
1641 return w.type_expr_is_fn_value(expr.expr, mod_name)
1642 }
1643 }
1644 ast.ModifierExpr {
1645 return w.type_expr_is_fn_value(expr.expr, mod_name)
1646 }
1647 ast.GenericArgs {
1648 return w.type_expr_is_fn_value(expr.lhs, mod_name)
1649 }
1650 ast.GenericArgOrIndexExpr {
1651 return w.type_expr_is_fn_value(expr.lhs, mod_name)
1652 }
1653 ast.Type {
1654 match expr {
1655 ast.FnType {
1656 return true
1657 }
1658 ast.PointerType {
1659 return w.type_expr_is_fn_value(expr.base_type, mod_name)
1660 }
1661 ast.GenericType {
1662 return w.type_expr_is_fn_value(expr.name, mod_name)
1663 }
1664 else {}
1665 }
1666 }
1667 else {}
1668 }
1669
1670 return false
1671}
1672
1673fn (w &Walker) expr_has_fn_value_type(expr ast.Expr) bool {
1674 if w.env == unsafe { nil } || !expr_ok(expr) {
1675 return false
1676 }
1677 pos := expr.pos()
1678 if !pos.is_valid() {
1679 return false
1680 }
1681 if typ := w.env.get_expr_type(pos.id) {
1682 return type_is_fn_value(typ)
1683 }
1684 return false
1685}
1686
1687fn (w &Walker) cursor_has_fn_value_type(c ast.Cursor) bool {
1688 if w.env == unsafe { nil } || !c.is_valid() {
1689 return false
1690 }
1691 pos := c.pos()
1692 if !pos.is_valid() {
1693 return false
1694 }
1695 if typ := w.env.get_expr_type(pos.id) {
1696 return type_is_fn_value(typ)
1697 }
1698 return false
1699}
1700
1701fn (w &Walker) type_expr_is_fn_value_cursor(c ast.Cursor, mod_name string) bool {
1702 if !c.is_valid() {
1703 return false
1704 }
1705 if w.cursor_has_fn_value_type(c) {
1706 return true
1707 }
1708 match c.kind() {
1709 .expr_ident {
1710 return w.type_name_is_fn_value(c.name(), mod_name)
1711 }
1712 .expr_selector {
1713 lhs_c := c.edge(0)
1714 rhs_c := c.edge(1)
1715 if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && rhs_c.is_valid() {
1716 real_mod := w.module_alias_to_real[lhs_c.name()] or { lhs_c.name() }
1717 return w.type_name_is_fn_value(rhs_c.name(), real_mod)
1718 || w.type_name_is_fn_value('${real_mod}__${rhs_c.name()}', real_mod)
1719 }
1720 }
1721 .expr_prefix, .expr_modifier, .typ_pointer, .typ_generic {
1722 return w.type_expr_is_fn_value_cursor(c.edge(0), mod_name)
1723 }
1724 .typ_fn {
1725 return true
1726 }
1727 else {}
1728 }
1729
1730 return false
1731}
1732
1733fn add_receiver_name_candidates(mut out []string, mod_name string, raw_name string) {
1734 name := sanitize_receiver_name(raw_name)
1735 if name == '' {
1736 return
1737 }
1738 add_unique_string(mut out, name)
1739 add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
1740 if name.contains('__') {
1741 add_unique_string(mut out, name.all_after_last('__'))
1742 } else if mod_name != '' && mod_name != 'main' {
1743 add_unique_string(mut out, '${mod_name}__${name}')
1744 }
1745}
1746
1747fn receiver_type_expr_name(expr ast.Expr) string {
1748 match expr {
1749 ast.Ident {
1750 return expr.name
1751 }
1752 ast.SelectorExpr {
1753 return expr.rhs.name
1754 }
1755 ast.PrefixExpr {
1756 if expr.op == .amp {
1757 return receiver_type_expr_name(expr.expr)
1758 }
1759 }
1760 ast.ModifierExpr {
1761 return receiver_type_expr_name(expr.expr)
1762 }
1763 ast.GenericArgs {
1764 return receiver_type_expr_name(expr.lhs)
1765 }
1766 ast.GenericArgOrIndexExpr {
1767 return receiver_type_expr_name(expr.lhs)
1768 }
1769 ast.Type {
1770 match expr {
1771 ast.PointerType {
1772 return receiver_type_expr_name(expr.base_type)
1773 }
1774 ast.ArrayType {
1775 elem_name := receiver_type_expr_name(expr.elem_type)
1776 if elem_name != '' {
1777 return 'Array_${elem_name}'
1778 }
1779 }
1780 ast.ArrayFixedType {
1781 elem_name := receiver_type_expr_name(expr.elem_type)
1782 len_name := receiver_fixed_array_len_name(expr.len)
1783 if elem_name != '' && len_name != '' {
1784 return 'Array_fixed_${elem_name}_${len_name}'
1785 }
1786 }
1787 ast.GenericType {
1788 return receiver_type_expr_name(expr.name)
1789 }
1790 else {}
1791 }
1792 }
1793 else {}
1794 }
1795
1796 return ''
1797}
1798
1799fn receiver_fixed_array_len_name(expr ast.Expr) string {
1800 match expr {
1801 ast.BasicLiteral {
1802 return expr.value
1803 }
1804 ast.Ident {
1805 return expr.name
1806 }
1807 else {
1808 return expr.name()
1809 }
1810 }
1811}
1812
1813fn type_expr_receiver_candidates(mod_name string, expr ast.Expr) []string {
1814 mut out := []string{}
1815 match expr {
1816 ast.SelectorExpr {
1817 if expr.lhs is ast.Ident {
1818 add_receiver_name_candidates(mut out, mod_name, expr.rhs.name)
1819 add_unique_string(mut out, '${expr.lhs.name}__${expr.rhs.name}')
1820 }
1821 }
1822 ast.PrefixExpr {
1823 if expr.op == .amp {
1824 return type_expr_receiver_candidates(mod_name, expr.expr)
1825 }
1826 }
1827 ast.ModifierExpr {
1828 return type_expr_receiver_candidates(mod_name, expr.expr)
1829 }
1830 ast.GenericArgs {
1831 return type_expr_receiver_candidates(mod_name, expr.lhs)
1832 }
1833 ast.GenericArgOrIndexExpr {
1834 return type_expr_receiver_candidates(mod_name, expr.lhs)
1835 }
1836 ast.Type {
1837 match expr {
1838 ast.PointerType {
1839 return type_expr_receiver_candidates(mod_name, expr.base_type)
1840 }
1841 ast.GenericType {
1842 return type_expr_receiver_candidates(mod_name, expr.name)
1843 }
1844 else {}
1845 }
1846 }
1847 else {}
1848 }
1849
1850 name := receiver_type_expr_name(expr)
1851 if name != '' {
1852 add_receiver_name_candidates(mut out, mod_name, name)
1853 }
1854 return out
1855}
1856
1857fn type_expr_receiver_candidates_cursor(c ast.Cursor, mod_name string) []string {
1858 mut out := []string{}
1859 if !c.is_valid() {
1860 return out
1861 }
1862 match c.kind() {
1863 .expr_selector {
1864 lhs_c := c.edge(0)
1865 rhs_c := c.edge(1)
1866 if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && rhs_c.is_valid() {
1867 add_receiver_name_candidates(mut out, mod_name, rhs_c.name())
1868 add_unique_string(mut out, '${lhs_c.name()}__${rhs_c.name()}')
1869 }
1870 }
1871 .expr_prefix, .expr_modifier, .expr_generic_args, .expr_generic_arg_or_index, .typ_pointer,
1872 .typ_generic {
1873 return type_expr_receiver_candidates_cursor(c.edge(0), mod_name)
1874 }
1875 .expr_ident {
1876 add_receiver_name_candidates(mut out, mod_name, c.name())
1877 }
1878 else {}
1879 }
1880
1881 name := receiver_type_expr_name_cursor(c)
1882 if name != '' {
1883 add_receiver_name_candidates(mut out, mod_name, name)
1884 }
1885 return out
1886}
1887
1888fn receiver_type_expr_name_cursor(c ast.Cursor) string {
1889 if !c.is_valid() {
1890 return ''
1891 }
1892 match c.kind() {
1893 .expr_ident {
1894 return c.name()
1895 }
1896 .expr_selector {
1897 rhs := c.edge(1)
1898 if rhs.is_valid() {
1899 return rhs.name()
1900 }
1901 }
1902 .expr_prefix, .expr_modifier, .expr_generic_args, .expr_generic_arg_or_index, .typ_pointer,
1903 .typ_generic {
1904 return receiver_type_expr_name_cursor(c.edge(0))
1905 }
1906 .typ_array {
1907 elem_name := receiver_type_expr_name_cursor(c.edge(0))
1908 if elem_name != '' {
1909 return 'Array_${elem_name}'
1910 }
1911 }
1912 .typ_array_fixed {
1913 elem_name := receiver_type_expr_name_cursor(c.edge(1))
1914 len_name := receiver_fixed_array_len_name_cursor(c.edge(0))
1915 if elem_name != '' && len_name != '' {
1916 return 'Array_fixed_${elem_name}_${len_name}'
1917 }
1918 }
1919 else {}
1920 }
1921
1922 return ''
1923}
1924
1925fn receiver_fixed_array_len_name_cursor(c ast.Cursor) string {
1926 if !c.is_valid() {
1927 return ''
1928 }
1929 if c.kind() == .expr_basic_literal || c.kind() == .expr_ident {
1930 return c.name()
1931 }
1932 return c.name()
1933}
1934
1935fn receiver_names_from_decl(mod_name string, decl ast.FnDecl, env &types.Environment) []string {
1936 mut out := []string{}
1937 add_receiver_name_candidates(mut out, mod_name, receiver_type_expr_name(decl.receiver.typ))
1938 match decl.receiver.typ {
1939 ast.Ident {
1940 name := sanitize_receiver_name(decl.receiver.typ.name)
1941 add_unique_string(mut out, name)
1942 add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
1943 if mod_name != '' && mod_name != 'main' {
1944 add_unique_string(mut out, '${mod_name}__${name}')
1945 }
1946 }
1947 ast.PrefixExpr {
1948 if decl.receiver.typ.expr is ast.Ident {
1949 name := sanitize_receiver_name(decl.receiver.typ.expr.name)
1950 add_unique_string(mut out, name)
1951 add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
1952 if mod_name != '' && mod_name != 'main' {
1953 add_unique_string(mut out, '${mod_name}__${name}')
1954 }
1955 }
1956 }
1957 else {}
1958 }
1959
1960 // Method on []ElemType — receiver name is Array_ElemType.
1961 // Use the string name which includes [] prefix for array types.
1962 recv_name := decl.receiver.typ.name()
1963 if recv_name.starts_with('[]') {
1964 elem_name := recv_name[2..]
1965 if elem_name.len > 0 {
1966 arr_name := 'Array_${elem_name}'
1967 add_unique_string(mut out, arr_name)
1968 add_unique_string(mut out, maybe_trim_module_prefix(mod_name, arr_name))
1969 if mod_name != '' && mod_name != 'main' {
1970 add_unique_string(mut out, 'Array_${mod_name}__${elem_name}')
1971 }
1972 }
1973 }
1974 pos := decl.receiver.typ.pos()
1975 if env != unsafe { nil } && pos.is_valid() {
1976 if receiver_type := env.get_expr_type(pos.id) {
1977 for name in type_name_candidates_from_type(mod_name, receiver_type) {
1978 add_unique_string(mut out, name)
1979 }
1980 }
1981 }
1982 return out
1983}
1984
1985fn receiver_primary_name(mod_name string, decl ast.FnDecl, env &types.Environment) string {
1986 names := receiver_names_from_decl(mod_name, decl, env)
1987 if names.len > 0 {
1988 return names[0]
1989 }
1990 return 'unknown'
1991}
1992
1993fn normalize_method_name(name string) string {
1994 if !string_ok(name) {
1995 return ''
1996 }
1997 return match name {
1998 '+' { 'plus' }
1999 '-' { 'minus' }
2000 '*' { 'mul' }
2001 '/' { 'div' }
2002 '%' { 'mod' }
2003 '==' { 'eq' }
2004 '!=' { 'ne' }
2005 '<' { 'lt' }
2006 '>' { 'gt' }
2007 '<=' { 'le' }
2008 '>=' { 'ge' }
2009 else { name }
2010 }
2011}
2012
2013fn (w &Walker) lookup_fn_scope(info FnInfo) &types.Scope {
2014 if w.env == unsafe { nil } {
2015 return unsafe { nil }
2016 }
2017 decl := info.decl
2018 mod_name := info.mod
2019 if !is_method_decl(decl) {
2020 if scope := w.env.get_fn_scope(mod_name, decl.name) {
2021 return scope
2022 }
2023 return unsafe { nil }
2024 }
2025 receivers := receiver_names_from_decl(mod_name, decl, w.env)
2026 for recv in receivers {
2027 if scope := w.env.get_fn_scope(mod_name, '${recv}__${decl.name}') {
2028 return scope
2029 }
2030 }
2031 return unsafe { nil }
2032}
2033
2034fn (mut w Walker) add_lookup(key string, idx int) {
2035 if key == '' {
2036 return
2037 }
2038 if key !in w.lookup {
2039 w.lookup[key] = [idx]
2040 return
2041 }
2042 mut values := w.lookup[key]
2043 add_unique_int(mut values, idx)
2044 w.lookup[key] = values
2045}
2046
2047fn (mut w Walker) index_fn(idx int, info FnInfo) {
2048 decl := info.decl
2049 mod_name := info.mod
2050 if is_method_decl(decl) {
2051 mut method_names := []string{}
2052 add_unique_string(mut method_names, decl.name)
2053 add_unique_string(mut method_names, normalize_method_name(decl.name))
2054 receivers := receiver_names_from_decl(mod_name, decl, w.env)
2055 for method_name in method_names {
2056 w.add_lookup('mname:${method_name}', idx)
2057 for receiver_name in receivers {
2058 w.add_method_receiver(receiver_name, idx)
2059 if receiver_name.contains('__') {
2060 w.add_method_receiver(receiver_name.all_after_last('__'), idx)
2061 }
2062 w.add_lookup('meth:${receiver_name}:${method_name}', idx)
2063 w.add_lookup('fn:${receiver_name}__${method_name}', idx)
2064 w.add_lookup('mod:${mod_name}:${receiver_name}__${method_name}', idx)
2065 }
2066 }
2067 return
2068 }
2069 w.add_lookup('fn:${decl.name}', idx)
2070 w.add_lookup('mod:${mod_name}:${decl.name}', idx)
2071 if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
2072 w.add_lookup('fn:${mod_name}__${decl.name}', idx)
2073 }
2074}
2075
2076fn (mut w Walker) add_module_name(name string) {
2077 if name == '' {
2078 return
2079 }
2080 w.module_names[name] = true
2081 if name.contains('.') {
2082 w.module_names[name.all_after_last('.')] = true
2083 }
2084}
2085
2086fn (mut w Walker) add_type_name(mod_name string, name string) {
2087 if name == '' {
2088 return
2089 }
2090 w.type_names[name] = true
2091 trimmed := maybe_trim_module_prefix(mod_name, name)
2092 w.type_names[trimmed] = true
2093 if mod_name != '' && mod_name != 'main' {
2094 w.type_names['${mod_name}__${trimmed}'] = true
2095 }
2096}
2097
2098fn (mut w Walker) add_interface_name(mod_name string, name string) {
2099 if name == '' {
2100 return
2101 }
2102 w.interface_type_names[name] = true
2103 trimmed := maybe_trim_module_prefix(mod_name, name)
2104 w.interface_type_names[trimmed] = true
2105 if mod_name != '' && mod_name != 'main' {
2106 w.interface_type_names['${mod_name}__${trimmed}'] = true
2107 }
2108}
2109
2110fn (mut w Walker) add_interface_decl(mod_name string, decl ast.InterfaceDecl) {
2111 w.add_interface_name(mod_name, decl.name)
2112 mut method_names := []string{}
2113 for field in decl.fields {
2114 if field.is_interface_method && field.typ is ast.Type && field.typ is ast.FnType {
2115 add_unique_fn_name(mut method_names, field.name)
2116 add_unique_fn_name(mut method_names, normalize_method_name(field.name))
2117 }
2118 }
2119 mut embedded_names := []string{}
2120 for embedded in decl.embedded {
2121 for embedded_name in type_expr_receiver_candidates(mod_name, embedded) {
2122 add_unique_string(mut embedded_names, embedded_name)
2123 }
2124 }
2125 mut iface_names := []string{}
2126 add_unique_string(mut iface_names, decl.name)
2127 add_unique_string(mut iface_names, maybe_trim_module_prefix(mod_name, decl.name))
2128 if mod_name != '' && mod_name != 'main' {
2129 trimmed := maybe_trim_module_prefix(mod_name, decl.name)
2130 add_unique_string(mut iface_names, '${mod_name}__${trimmed}')
2131 }
2132 for iface_name in iface_names {
2133 if method_names.len > 0 {
2134 if iface_name in w.interface_method_names {
2135 mut existing := w.interface_method_names[iface_name]
2136 for method_name in method_names {
2137 add_unique_fn_name(mut existing, method_name)
2138 }
2139 w.interface_method_names[iface_name] = existing
2140 } else {
2141 w.interface_method_names[iface_name] = method_names.clone()
2142 }
2143 }
2144 if embedded_names.len > 0 {
2145 mut existing := w.interface_embedded_names[iface_name] or { []string{} }
2146 for embedded_name in embedded_names {
2147 add_unique_string(mut existing, embedded_name)
2148 }
2149 w.interface_embedded_names[iface_name] = existing
2150 }
2151 }
2152}
2153
2154fn (mut w Walker) add_interface_decl_cursor(mod_name string, decl ast.Cursor) {
2155 w.add_interface_name(mod_name, decl.name())
2156 mut method_names := []string{}
2157 fields := decl.list_at(3)
2158 for i in 0 .. fields.len() {
2159 field := fields.at(i)
2160 typ := field.edge(0)
2161 if field.flag(ast.flag_field_is_interface_method) && typ.is_valid() && typ.kind() == .typ_fn {
2162 add_unique_fn_name(mut method_names, field.name())
2163 add_unique_fn_name(mut method_names, normalize_method_name(field.name()))
2164 }
2165 }
2166 mut embedded_names := []string{}
2167 embedded := decl.list_at(2)
2168 for i in 0 .. embedded.len() {
2169 for embedded_name in type_expr_receiver_candidates_cursor(embedded.at(i), mod_name) {
2170 add_unique_string(mut embedded_names, embedded_name)
2171 }
2172 }
2173 mut iface_names := []string{}
2174 add_unique_string(mut iface_names, decl.name())
2175 add_unique_string(mut iface_names, maybe_trim_module_prefix(mod_name, decl.name()))
2176 if mod_name != '' && mod_name != 'main' {
2177 trimmed := maybe_trim_module_prefix(mod_name, decl.name())
2178 add_unique_string(mut iface_names, '${mod_name}__${trimmed}')
2179 }
2180 for iface_name in iface_names {
2181 if method_names.len > 0 {
2182 if iface_name in w.interface_method_names {
2183 mut existing := w.interface_method_names[iface_name]
2184 for method_name in method_names {
2185 add_unique_fn_name(mut existing, method_name)
2186 }
2187 w.interface_method_names[iface_name] = existing
2188 } else {
2189 w.interface_method_names[iface_name] = method_names.clone()
2190 }
2191 }
2192 if embedded_names.len > 0 {
2193 mut existing := w.interface_embedded_names[iface_name] or { []string{} }
2194 for embedded_name in embedded_names {
2195 add_unique_string(mut existing, embedded_name)
2196 }
2197 w.interface_embedded_names[iface_name] = existing
2198 }
2199 }
2200}
2201
2202fn (mut w Walker) add_struct_field_receivers(mod_name string, decl ast.StructDecl) {
2203 mut struct_names := []string{}
2204 add_receiver_name_candidates(mut struct_names, mod_name, decl.name)
2205 for field in decl.fields {
2206 field_receivers := type_expr_receiver_candidates(mod_name, field.typ)
2207 if field_receivers.len == 0 {
2208 continue
2209 }
2210 for struct_name in struct_names {
2211 key := '${struct_name}:${field.name}'
2212 mut existing := w.struct_field_receivers[key] or { []string{} }
2213 for receiver in field_receivers {
2214 add_unique_string(mut existing, receiver)
2215 }
2216 w.struct_field_receivers[key] = existing
2217 }
2218 }
2219}
2220
2221fn (mut w Walker) add_struct_field_receivers_cursor(mod_name string, decl ast.Cursor) {
2222 mut struct_names := []string{}
2223 add_receiver_name_candidates(mut struct_names, mod_name, decl.name())
2224 fields := decl.list_at(4)
2225 for i in 0 .. fields.len() {
2226 field := fields.at(i)
2227 field_receivers := type_expr_receiver_candidates_cursor(field.edge(0), mod_name)
2228 if field_receivers.len == 0 {
2229 continue
2230 }
2231 for struct_name in struct_names {
2232 key := '${struct_name}:${field.name()}'
2233 mut existing := w.struct_field_receivers[key] or { []string{} }
2234 for receiver in field_receivers {
2235 add_unique_string(mut existing, receiver)
2236 }
2237 w.struct_field_receivers[key] = existing
2238 }
2239 }
2240}
2241
2242fn (mut w Walker) add_struct_embedded_receivers(mod_name string, decl ast.StructDecl) {
2243 if decl.embedded.len == 0 {
2244 return
2245 }
2246 mut struct_names := []string{}
2247 add_receiver_name_candidates(mut struct_names, mod_name, decl.name)
2248 for embedded in decl.embedded {
2249 embedded_receivers := type_expr_receiver_candidates(mod_name, embedded)
2250 if embedded_receivers.len == 0 {
2251 continue
2252 }
2253 for struct_name in struct_names {
2254 mut existing := w.struct_embedded_receivers[struct_name] or { []string{} }
2255 for receiver in embedded_receivers {
2256 add_unique_string(mut existing, receiver)
2257 }
2258 w.struct_embedded_receivers[struct_name] = existing
2259 }
2260 }
2261}
2262
2263fn (mut w Walker) add_struct_embedded_receivers_cursor(mod_name string, decl ast.Cursor) {
2264 embedded := decl.list_at(2)
2265 if embedded.len() == 0 {
2266 return
2267 }
2268 mut struct_names := []string{}
2269 add_receiver_name_candidates(mut struct_names, mod_name, decl.name())
2270 for i in 0 .. embedded.len() {
2271 embedded_receivers := type_expr_receiver_candidates_cursor(embedded.at(i), mod_name)
2272 if embedded_receivers.len == 0 {
2273 continue
2274 }
2275 for struct_name in struct_names {
2276 mut existing := w.struct_embedded_receivers[struct_name] or { []string{} }
2277 for receiver in embedded_receivers {
2278 add_unique_string(mut existing, receiver)
2279 }
2280 w.struct_embedded_receivers[struct_name] = existing
2281 }
2282 }
2283}
2284
2285fn (mut w Walker) add_struct_fn_fields(mod_name string, decl ast.StructDecl) {
2286 mut struct_names := []string{}
2287 add_receiver_name_candidates(mut struct_names, mod_name, decl.name)
2288 for field in decl.fields {
2289 if !w.type_expr_is_fn_value(field.typ, mod_name) {
2290 continue
2291 }
2292 for struct_name in struct_names {
2293 w.struct_fn_fields['${struct_name}:${field.name}'] = true
2294 }
2295 }
2296}
2297
2298fn (mut w Walker) add_struct_fn_fields_cursor(mod_name string, decl ast.Cursor) {
2299 mut struct_names := []string{}
2300 add_receiver_name_candidates(mut struct_names, mod_name, decl.name())
2301 fields := decl.list_at(4)
2302 for i in 0 .. fields.len() {
2303 field := fields.at(i)
2304 if !w.type_expr_is_fn_value_cursor(field.edge(0), mod_name) {
2305 continue
2306 }
2307 for struct_name in struct_names {
2308 w.struct_fn_fields['${struct_name}:${field.name()}'] = true
2309 }
2310 }
2311}
2312
2313fn (w &Walker) init_field_is_fn_value(init_typ ast.Expr, field_name string, mod_name string) bool {
2314 if field_name == '' {
2315 return false
2316 }
2317 for struct_name in type_expr_receiver_candidates(mod_name, init_typ) {
2318 if w.struct_fn_fields['${struct_name}:${field_name}'] {
2319 return true
2320 }
2321 }
2322 return false
2323}
2324
2325fn (w &Walker) init_field_is_fn_value_cursor(init_typ ast.Cursor, field_name string, mod_name string) bool {
2326 if field_name == '' {
2327 return false
2328 }
2329 for struct_name in type_expr_receiver_candidates_cursor(init_typ, mod_name) {
2330 if w.struct_fn_fields['${struct_name}:${field_name}'] {
2331 return true
2332 }
2333 }
2334 return false
2335}
2336
2337fn (mut w Walker) add_global_interface_names(mod_name string, decl ast.GlobalDecl) {
2338 for field in decl.fields {
2339 iface_name := w.interface_name_from_type_expr(field.typ, mod_name)
2340 if iface_name == '' {
2341 continue
2342 }
2343 w.global_interface_names[field.name] = iface_name
2344 if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
2345 w.global_interface_names['${mod_name}__${field.name}'] = iface_name
2346 }
2347 }
2348}
2349
2350fn (mut w Walker) add_global_interface_names_cursor(mod_name string, decl ast.Cursor) {
2351 fields := decl.list_at(1)
2352 for i in 0 .. fields.len() {
2353 field := fields.at(i)
2354 iface_name := w.interface_name_from_cursor(field.edge(0), mod_name)
2355 if iface_name == '' {
2356 continue
2357 }
2358 w.global_interface_names[field.name()] = iface_name
2359 if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
2360 w.global_interface_names['${mod_name}__${field.name()}'] = iface_name
2361 }
2362 }
2363}
2364
2365fn (mut w Walker) add_method_receiver(receiver_name string, idx int) {
2366 if receiver_name == '' {
2367 return
2368 }
2369 if receiver_name !in w.methods_by_receiver {
2370 w.methods_by_receiver[receiver_name] = [idx]
2371 return
2372 }
2373 mut values := w.methods_by_receiver[receiver_name]
2374 add_unique_int(mut values, idx)
2375 w.methods_by_receiver[receiver_name] = values
2376}
2377
2378fn (mut w Walker) mark_fn(idx int) {
2379 if idx < 0 || idx >= w.fns.len {
2380 return
2381 }
2382 info := w.fns[idx]
2383 key := info.key
2384 was_used := key in w.used_keys
2385 if !was_used {
2386 w.used_keys[key] = true
2387 w.mark_generic_binding_receiver_methods(info)
2388 }
2389 if idx in w.queued_fn_indices {
2390 return
2391 }
2392 w.queued_fn_indices[idx] = true
2393 if info.has_body {
2394 w.queue << idx
2395 }
2396}
2397
2398fn (mut w Walker) mark_generic_binding_receiver_methods(info FnInfo) {
2399 if w.env == unsafe { nil } || info.decl.typ.generic_params.len == 0 {
2400 return
2401 }
2402 for key, bindings_list in w.env.generic_types {
2403 if generic_base_name_from_key(key) != info.decl.name {
2404 continue
2405 }
2406 for bindings in bindings_list {
2407 for _, concrete in bindings {
2408 receivers := type_name_candidates_from_type(info.mod, concrete)
2409 w.mark_all_methods_for_receivers(receivers)
2410 }
2411 }
2412 }
2413}
2414
2415fn (w &Walker) lookup_count(key string) int {
2416 if !string_ok(key) {
2417 return 0
2418 }
2419 if values := w.lookup[key] {
2420 return values.len
2421 }
2422 return 0
2423}
2424
2425fn (mut w Walker) mark_lookup(key string) {
2426 if !string_ok(key) {
2427 return
2428 }
2429 if values := w.lookup[key] {
2430 for idx in values {
2431 w.mark_fn(idx)
2432 }
2433 }
2434}
2435
2436fn called_fn_name_candidates(name string) []string {
2437 mut out := []string{}
2438 if name == '' || !string_ok(name) {
2439 return out
2440 }
2441 add_unique_string(mut out, name)
2442 generic_base := strip_generic_specialization_suffix(name)
2443 if generic_base != name {
2444 add_unique_string(mut out, generic_base)
2445 }
2446 if name == 'builtin__new_array_from_c_array_noscan' {
2447 add_unique_string(mut out, 'new_array_from_c_array')
2448 return out
2449 }
2450 if name == 'builtin__new_array_from_array_and_c_array' {
2451 add_unique_string(mut out, 'new_array_from_array_and_c_array')
2452 return out
2453 }
2454 if name == 'builtin__array_push_noscan' || name == 'builtin__array__push_noscan' {
2455 add_unique_string(mut out, 'array__push_noscan')
2456 return out
2457 }
2458 if name.starts_with('builtin__') {
2459 base := name['builtin__'.len..]
2460 add_unique_string(mut out, base)
2461 }
2462 if name == 'gen_v__new_gen' {
2463 add_unique_string(mut out, 'v__new_gen')
2464 }
2465 if name.starts_with('strings__Builder__') {
2466 method_name := name.all_after_last('__')
2467 add_unique_string(mut out, 'array__${method_name}')
2468 }
2469 return out
2470}
2471
2472fn (mut w Walker) cached_called_fn_name_candidates(name string) []string {
2473 if candidates := w.called_fn_name_candidate_cache[name] {
2474 return candidates
2475 }
2476 candidates := called_fn_name_candidates(name)
2477 w.called_fn_name_candidate_cache[name] = candidates
2478 return candidates
2479}
2480
2481fn strip_generic_specialization_suffix(name string) string {
2482 if idx := name.index('_T_') {
2483 if idx > 0 {
2484 return name[..idx]
2485 }
2486 }
2487 return name
2488}
2489
2490fn should_mark_ident_as_fn(name string) bool {
2491 if name == '' || !string_ok(name) {
2492 return false
2493 }
2494 return name.starts_with('map_') || name.starts_with('map__') || name.starts_with('new_map')
2495 || name.starts_with('array__') || name.starts_with('IError_') || name.starts_with('Error__')
2496 || name.starts_with('MessageError__') || name.starts_with('_option_')
2497 || name.starts_with('_result_')
2498}
2499
2500fn (mut w Walker) ident_resolves_to_fn_value(name string, mod_name string) bool {
2501 if name == '' || !string_ok(name) || name == 'C' || name in w.module_names
2502 || w.is_cast_type_name(name) {
2503 return false
2504 }
2505 if w.cur_fn_scope != unsafe { nil } {
2506 if obj := w.cur_fn_scope.lookup_parent(name, 0) {
2507 return obj is types.Fn
2508 }
2509 }
2510 candidates := w.cached_called_fn_name_candidates(name)
2511 if _ := w.exact_mangled_fn_lookup_key(name) {
2512 return true
2513 }
2514 for candidate in candidates {
2515 if _ := w.current_module_fn_lookup_key(candidate, mod_name) {
2516 return true
2517 }
2518 }
2519 for candidate in candidates {
2520 if _ := w.selective_import_fn_lookup_key(candidate) {
2521 return true
2522 }
2523 }
2524 for candidate in candidates {
2525 if w.lookup_count('mod:${mod_name}:${candidate}') > 0
2526 || w.lookup_count('fn:${candidate}') > 0 {
2527 return true
2528 }
2529 if !candidate.contains('__') && mod_name != '' && mod_name != 'main'
2530 && mod_name != 'builtin' && w.lookup_count('fn:${mod_name}__${candidate}') > 0 {
2531 return true
2532 }
2533 }
2534 return false
2535}
2536
2537fn (w &Walker) current_module_fn_lookup_key(name string, mod_name string) ?string {
2538 key := 'mod:${mod_name}:${name}'
2539 if w.lookup_count(key) > 0 {
2540 return key
2541 }
2542 return none
2543}
2544
2545fn (w &Walker) exact_mangled_fn_lookup_key(name string) ?string {
2546 if !name.contains('__') {
2547 return none
2548 }
2549 key := 'fn:${name}'
2550 if w.lookup_count(key) > 0 {
2551 return key
2552 }
2553 return none
2554}
2555
2556fn (w &Walker) selective_import_fn_lookup_key(name string) ?string {
2557 if w.cur_fn_file == '' {
2558 return none
2559 }
2560 if target := w.selective_import_fn_targets[selective_import_fn_key(w.cur_fn_file, name)] {
2561 key := 'fn:${target}'
2562 if w.lookup_count(key) > 0 {
2563 return key
2564 }
2565 }
2566 return none
2567}
2568
2569fn (mut w Walker) mark_fn_name(name string, mod_name string) {
2570 if name == '' || !string_ok(name) || !string_ok(mod_name) {
2571 return
2572 }
2573 if w.opts.minimal_runtime_roots && name in ['array__eq', 'builtin__array__eq'] {
2574 w.mark_fn_name('map_map_eq', 'builtin')
2575 }
2576 candidates := w.cached_called_fn_name_candidates(name)
2577 if key := w.exact_mangled_fn_lookup_key(name) {
2578 w.mark_lookup(key)
2579 return
2580 }
2581 for candidate in candidates {
2582 if key := w.current_module_fn_lookup_key(candidate, mod_name) {
2583 w.mark_lookup(key)
2584 return
2585 }
2586 }
2587 for candidate in candidates {
2588 if key := w.selective_import_fn_lookup_key(candidate) {
2589 w.mark_lookup(key)
2590 return
2591 }
2592 }
2593 for candidate in candidates {
2594 w.mark_lookup('mod:${mod_name}:${candidate}')
2595 w.mark_lookup('fn:${candidate}')
2596 if !candidate.contains('__') && mod_name != '' && mod_name != 'main'
2597 && mod_name != 'builtin' {
2598 w.mark_lookup('fn:${mod_name}__${candidate}')
2599 }
2600 }
2601}
2602
2603fn (mut w Walker) mark_method_name(name string, receivers []string) {
2604 if name == '' || !string_ok(name) {
2605 return
2606 }
2607 normalized := normalize_method_name(name)
2608 for receiver in receivers {
2609 if !string_ok(receiver) {
2610 continue
2611 }
2612 w.mark_method_receiver_candidate(receiver, name, normalized)
2613 }
2614}
2615
2616fn (w &Walker) method_lookup_count(name string, receivers []string) int {
2617 if name == '' || !string_ok(name) {
2618 return 0
2619 }
2620 normalized := normalize_method_name(name)
2621 mut count := 0
2622 for receiver in receivers {
2623 if !string_ok(receiver) {
2624 continue
2625 }
2626 count += w.method_receiver_candidate_count(receiver, name, normalized)
2627 }
2628 return count
2629}
2630
2631fn (mut w Walker) mark_method_lookup_candidate(candidate string, name string, normalized string) {
2632 w.mark_lookup('meth:${candidate}:${name}')
2633 if normalized != name {
2634 w.mark_lookup('meth:${candidate}:${normalized}')
2635 }
2636}
2637
2638fn (mut w Walker) mark_method_receiver_candidate(receiver string, name string, normalized string) {
2639 w.mark_method_lookup_candidate(receiver, name, normalized)
2640 mut short_name := ''
2641 if receiver.contains('__') {
2642 short_name = receiver.all_after_last('__')
2643 if short_name != '' && short_name != receiver {
2644 w.mark_method_lookup_candidate(short_name, name, normalized)
2645 }
2646 }
2647 if receiver.starts_with('Array_') || receiver.starts_with('Array_fixed_') {
2648 if receiver != 'array' && short_name != 'array' {
2649 w.mark_method_lookup_candidate('array', name, normalized)
2650 }
2651 }
2652 if receiver.starts_with('Map_') {
2653 if receiver != 'map' && short_name != 'map' {
2654 w.mark_method_lookup_candidate('map', name, normalized)
2655 }
2656 }
2657 if receiver.starts_with('_option_') {
2658 if receiver != '_option' && short_name != '_option' {
2659 w.mark_method_lookup_candidate('_option', name, normalized)
2660 }
2661 }
2662 if receiver.starts_with('_result_') {
2663 if receiver != '_result' && short_name != '_result' {
2664 w.mark_method_lookup_candidate('_result', name, normalized)
2665 }
2666 }
2667}
2668
2669fn (w &Walker) method_lookup_candidate_count(candidate string, name string, normalized string) int {
2670 mut count := w.lookup_count('meth:${candidate}:${name}')
2671 if normalized != name {
2672 count += w.lookup_count('meth:${candidate}:${normalized}')
2673 }
2674 return count
2675}
2676
2677fn (w &Walker) method_receiver_candidate_count(receiver string, name string, normalized string) int {
2678 mut count := w.method_lookup_candidate_count(receiver, name, normalized)
2679 mut short_name := ''
2680 if receiver.contains('__') {
2681 short_name = receiver.all_after_last('__')
2682 if short_name != '' && short_name != receiver {
2683 count += w.method_lookup_candidate_count(short_name, name, normalized)
2684 }
2685 }
2686 if receiver.starts_with('Array_') || receiver.starts_with('Array_fixed_') {
2687 if receiver != 'array' && short_name != 'array' {
2688 count += w.method_lookup_candidate_count('array', name, normalized)
2689 }
2690 }
2691 if receiver.starts_with('Map_') {
2692 if receiver != 'map' && short_name != 'map' {
2693 count += w.method_lookup_candidate_count('map', name, normalized)
2694 }
2695 }
2696 if receiver.starts_with('_option_') {
2697 if receiver != '_option' && short_name != '_option' {
2698 count += w.method_lookup_candidate_count('_option', name, normalized)
2699 }
2700 }
2701 if receiver.starts_with('_result_') {
2702 if receiver != '_result' && short_name != '_result' {
2703 count += w.method_lookup_candidate_count('_result', name, normalized)
2704 }
2705 }
2706 return count
2707}
2708
2709fn (mut w Walker) mark_method_name_fallback(name string) {
2710 if name == '' || !string_ok(name) {
2711 return
2712 }
2713 normalized := normalize_method_name(name)
2714 // Check count first to avoid pulling in everything for common names.
2715 count := w.lookup_count('mname:${name}') +
2716 if normalized != name { w.lookup_count('mname:${normalized}') } else { 0 }
2717 if count == 0 || count > 64 {
2718 return
2719 }
2720 w.mark_lookup('mname:${name}')
2721 if normalized != name {
2722 w.mark_lookup('mname:${normalized}')
2723 }
2724}
2725
2726fn infix_operator_may_use_method(op token.Token) bool {
2727 return op in [.plus, .minus, .mul, .div, .mod, .eq, .ne, .lt, .gt, .le, .ge, .pipe, .xor]
2728}
2729
2730fn (mut w Walker) mark_infix_operator_method(expr ast.InfixExpr, mod_name string) {
2731 if !infix_operator_may_use_method(expr.op) {
2732 return
2733 }
2734 method_name := expr.op.str()
2735 receivers := w.receiver_candidates_for_expr(expr.lhs, mod_name)
2736 if receivers.len > 0 {
2737 w.mark_method_name(method_name, receivers)
2738 return
2739 }
2740 w.mark_method_name_fallback(method_name)
2741}
2742
2743fn (mut w Walker) mark_all_methods_for_receivers(receivers []string) {
2744 for receiver in receivers {
2745 if receiver in w.methods_by_receiver {
2746 for idx in w.methods_by_receiver[receiver] {
2747 w.mark_fn(idx)
2748 }
2749 }
2750 if receiver.contains('__') {
2751 short_name := receiver.all_after_last('__')
2752 if short_name in w.methods_by_receiver {
2753 for idx in w.methods_by_receiver[short_name] {
2754 w.mark_fn(idx)
2755 }
2756 }
2757 }
2758 }
2759}
2760
2761fn (mut w Walker) mark_comptime_method_loop_receivers(stmt ast.Stmt, mod_name string) {
2762 if stmt !is ast.ForStmt {
2763 return
2764 }
2765 if stmt.init !is ast.ForInStmt {
2766 return
2767 }
2768 for_in := stmt.init as ast.ForInStmt
2769 if for_in.expr !is ast.SelectorExpr {
2770 return
2771 }
2772 sel := for_in.expr as ast.SelectorExpr
2773 if sel.rhs.name != 'methods' {
2774 return
2775 }
2776 mut receivers := []string{}
2777 for receiver in type_expr_receiver_candidates(mod_name, sel.lhs) {
2778 add_unique_string(mut receivers, receiver)
2779 }
2780 for receiver in w.receiver_candidates_for_expr(sel.lhs, mod_name) {
2781 add_unique_string(mut receivers, receiver)
2782 }
2783 w.mark_all_methods_for_receivers(receivers)
2784}
2785
2786fn (mut w Walker) mark_comptime_method_loop_receivers_cursor(stmt ast.Cursor, mod_name string) {
2787 if !stmt.is_valid() || stmt.kind() != .stmt_for {
2788 return
2789 }
2790 init := stmt.edge(0)
2791 if !init.is_valid() || init.kind() != .stmt_for_in {
2792 return
2793 }
2794 expr := init.edge(2)
2795 if !expr.is_valid() || expr.kind() != .expr_selector {
2796 return
2797 }
2798 rhs := expr.edge(1)
2799 if !rhs.is_valid() || rhs.name() != 'methods' {
2800 return
2801 }
2802 lhs := expr.edge(0)
2803 mut receivers := []string{}
2804 for receiver in type_expr_receiver_candidates_cursor(lhs, mod_name) {
2805 add_unique_string(mut receivers, receiver)
2806 }
2807 for receiver in w.receiver_candidates_for_cursor(lhs, mod_name) {
2808 add_unique_string(mut receivers, receiver)
2809 }
2810 w.mark_all_methods_for_receivers(receivers)
2811}
2812
2813fn (w &Walker) embedded_receivers_for(receivers []string) []string {
2814 mut out := []string{}
2815 mut seen := map[string]bool{}
2816 mut stack := []string{}
2817 for receiver in receivers {
2818 if receiver == '' || receiver in seen {
2819 continue
2820 }
2821 seen[receiver] = true
2822 stack << receiver
2823 }
2824 for stack.len > 0 {
2825 receiver := stack[stack.len - 1]
2826 stack.delete(stack.len - 1)
2827 embedded := w.struct_embedded_receivers[receiver] or { continue }
2828 for embedded_receiver in embedded {
2829 add_unique_string(mut out, embedded_receiver)
2830 if embedded_receiver !in seen {
2831 seen[embedded_receiver] = true
2832 stack << embedded_receiver
2833 }
2834 }
2835 }
2836 return out
2837}
2838
2839fn ierror_wrapper_base_from_ident(name string) string {
2840 if !name.starts_with('IError_') {
2841 return ''
2842 }
2843 prefix_len := 'IError_'.len
2844 if name.ends_with('_type_name_wrapper') {
2845 return name[prefix_len..name.len - '_type_name_wrapper'.len]
2846 }
2847 if name.ends_with('_msg_wrapper') {
2848 return name[prefix_len..name.len - '_msg_wrapper'.len]
2849 }
2850 if name.ends_with('_code_wrapper') {
2851 return name[prefix_len..name.len - '_code_wrapper'.len]
2852 }
2853 return ''
2854}
2855
2856fn (mut w Walker) mark_ierror_wrapper_dependencies(name string, mod_name string) {
2857 base := ierror_wrapper_base_from_ident(name)
2858 if base == '' {
2859 return
2860 }
2861 w.mark_fn_name('${base}__msg', mod_name)
2862 w.mark_fn_name('${base}__code', mod_name)
2863 w.mark_fn_name('Error__code', mod_name)
2864}
2865
2866fn (w &Walker) interface_name_from_expr(expr ast.Expr, mod_name string) string {
2867 match expr {
2868 ast.Ident {
2869 name := sanitize_receiver_name(expr.name)
2870 if name in w.interface_type_names {
2871 return name
2872 }
2873 if mod_name != '' && mod_name != 'main' {
2874 qualified := '${mod_name}__${name}'
2875 if qualified in w.interface_type_names {
2876 return qualified
2877 }
2878 }
2879 }
2880 ast.SelectorExpr {
2881 if expr.lhs is ast.Ident {
2882 mut candidates := []string{}
2883 add_unique_string(mut candidates, expr.rhs.name)
2884 add_unique_string(mut candidates, '${expr.lhs.name}__${expr.rhs.name}')
2885 if mod_name != '' && mod_name != 'main' {
2886 add_unique_string(mut candidates, '${mod_name}__${expr.rhs.name}')
2887 }
2888 for candidate in candidates {
2889 if candidate in w.interface_type_names {
2890 return candidate
2891 }
2892 }
2893 }
2894 }
2895 else {}
2896 }
2897
2898 return ''
2899}
2900
2901// interface_name_from_cursor mirrors interface_name_from_expr but reads its
2902// inputs from a Cursor instead of a decoded ast.Expr. interface_name_from_expr
2903// only ever inspects Ident and SelectorExpr variants, so the cursor version
2904// only needs to dispatch on .expr_ident / .expr_selector.
2905fn (w &Walker) interface_name_from_cursor(c ast.Cursor, mod_name string) string {
2906 if !c.is_valid() {
2907 return ''
2908 }
2909 match c.kind() {
2910 .expr_ident {
2911 name := sanitize_receiver_name(c.name())
2912 if name in w.interface_type_names {
2913 return name
2914 }
2915 if mod_name != '' && mod_name != 'main' {
2916 qualified := '${mod_name}__${name}'
2917 if qualified in w.interface_type_names {
2918 return qualified
2919 }
2920 }
2921 }
2922 .expr_selector {
2923 lhs_c := c.edge(0)
2924 rhs_c := c.edge(1)
2925 if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && rhs_c.is_valid() {
2926 lhs_name := lhs_c.name()
2927 rhs_name := rhs_c.name()
2928 mut candidates := []string{}
2929 add_unique_string(mut candidates, rhs_name)
2930 add_unique_string(mut candidates, '${lhs_name}__${rhs_name}')
2931 if mod_name != '' && mod_name != 'main' {
2932 add_unique_string(mut candidates, '${mod_name}__${rhs_name}')
2933 }
2934 for candidate in candidates {
2935 if candidate in w.interface_type_names {
2936 return candidate
2937 }
2938 }
2939 }
2940 }
2941 else {}
2942 }
2943
2944 return ''
2945}
2946
2947fn (mut w Walker) mark_interface_conversion_methods(target_expr ast.Expr, value_expr ast.Expr, mod_name string) {
2948 iface_name := w.interface_name_from_expr(target_expr, mod_name)
2949 if iface_name == '' {
2950 return
2951 }
2952 w.mark_interface_conversion_methods_for_name(iface_name, value_expr, mod_name)
2953}
2954
2955fn (w &Walker) interface_name_from_type_expr(expr ast.Expr, mod_name string) string {
2956 match expr {
2957 ast.Ident, ast.SelectorExpr {
2958 return w.interface_name_from_expr(expr, mod_name)
2959 }
2960 ast.PrefixExpr {
2961 if expr.op == .amp {
2962 return w.interface_name_from_type_expr(expr.expr, mod_name)
2963 }
2964 }
2965 ast.ModifierExpr {
2966 return w.interface_name_from_type_expr(expr.expr, mod_name)
2967 }
2968 ast.Type {
2969 match expr {
2970 ast.PointerType {
2971 return w.interface_name_from_type_expr(expr.base_type, mod_name)
2972 }
2973 ast.GenericType {
2974 return w.interface_name_from_type_expr(expr.name, mod_name)
2975 }
2976 else {}
2977 }
2978 }
2979 else {}
2980 }
2981
2982 return ''
2983}
2984
2985fn (w &Walker) interface_name_from_type(typ types.Type) string {
2986 if !type_ok(typ) {
2987 return ''
2988 }
2989 match typ {
2990 types.Interface {
2991 return typ.name
2992 }
2993 types.Pointer {
2994 if !type_ok(typ.base_type) {
2995 return ''
2996 }
2997 return w.interface_name_from_type(typ.base_type)
2998 }
2999 types.Alias {
3000 if !type_ok(typ.base_type) {
3001 return ''
3002 }
3003 return w.interface_name_from_type(typ.base_type)
3004 }
3005 else {}
3006 }
3007
3008 return ''
3009}
3010
3011fn (w &Walker) interface_name_from_expr_type(expr ast.Expr, mod_name string) string {
3012 if expr is ast.Ident {
3013 if iface_name := w.global_interface_names[expr.name] {
3014 return iface_name
3015 }
3016 if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
3017 qualified := '${mod_name}__${expr.name}'
3018 if iface_name := w.global_interface_names[qualified] {
3019 return iface_name
3020 }
3021 }
3022 }
3023 if w.env != unsafe { nil } {
3024 pos := expr.pos()
3025 if pos.is_valid() {
3026 if typ := w.env.get_expr_type(pos.id) {
3027 return w.interface_name_from_type(typ)
3028 }
3029 }
3030 }
3031 return ''
3032}
3033
3034fn (w &Walker) interface_method_names_for(iface_name string) []string {
3035 mut out := []string{}
3036 mut seen := map[string]bool{}
3037 mut stack := []string{}
3038 add_unique_string(mut stack, iface_name)
3039 for stack.len > 0 {
3040 current := stack[stack.len - 1]
3041 stack.delete(stack.len - 1)
3042 if current == '' || current in seen {
3043 continue
3044 }
3045 seen[current] = true
3046 method_names := w.interface_method_names[current] or { []string{} }
3047 for method_name in method_names {
3048 add_unique_fn_name(mut out, method_name)
3049 }
3050 embedded_names := w.interface_embedded_names[current] or { []string{} }
3051 for embedded_name in embedded_names {
3052 if embedded_name !in seen {
3053 add_unique_string(mut stack, embedded_name)
3054 }
3055 }
3056 }
3057 return out
3058}
3059
3060fn (mut w Walker) mark_assignment_interface_conversion(lhs ast.Expr, rhs ast.Expr, mod_name string) {
3061 iface_name := w.interface_name_from_expr_type(lhs, mod_name)
3062 if iface_name == '' {
3063 return
3064 }
3065 w.mark_interface_conversion_methods_for_name(iface_name, rhs, mod_name)
3066}
3067
3068// interface_name_from_expr_type_cursor mirrors interface_name_from_expr_type
3069// but reads from a Cursor without decoding the underlying Expr. Most lhs
3070// nodes are Idents, so the global_interface_names lookup short-circuits
3071// before any env call.
3072fn (w &Walker) interface_name_from_expr_type_cursor(c ast.Cursor, mod_name string) string {
3073 if !c.is_valid() {
3074 return ''
3075 }
3076 if c.kind() == .expr_ident {
3077 name := c.name()
3078 if iface_name := w.global_interface_names[name] {
3079 return iface_name
3080 }
3081 if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
3082 qualified := '${mod_name}__${name}'
3083 if iface_name := w.global_interface_names[qualified] {
3084 return iface_name
3085 }
3086 }
3087 }
3088 if w.env != unsafe { nil } {
3089 pos := c.pos()
3090 if pos.is_valid() {
3091 if typ := w.env.get_expr_type(pos.id) {
3092 return w.interface_name_from_type(typ)
3093 }
3094 }
3095 }
3096 return ''
3097}
3098
3099// mark_assignment_interface_conversion_cursor is the cursor analogue of
3100// mark_assignment_interface_conversion. The lhs cursor is inspected for
3101// the interface-type fast path; only when iface_name is non-empty does
3102// the rhs cursor get handed to the by-cursor receiver lookup.
3103fn (mut w Walker) mark_assignment_interface_conversion_cursor(lhs_c ast.Cursor, rhs_c ast.Cursor, mod_name string) {
3104 iface_name := w.interface_name_from_expr_type_cursor(lhs_c, mod_name)
3105 if iface_name == '' {
3106 return
3107 }
3108 w.mark_interface_conversion_methods_for_name_cursor(iface_name, rhs_c, mod_name)
3109}
3110
3111// mark_interface_conversion_methods_cursor mirrors mark_interface_conversion_methods
3112// but accepts the value as a Cursor. target_expr stays a legacy Expr because
3113// callers (e.g. stmt_return) already hold one — cur_fn_decl.typ.return_type.
3114fn (mut w Walker) mark_interface_conversion_methods_cursor(target_expr ast.Expr, value_c ast.Cursor, mod_name string) {
3115 iface_name := w.interface_name_from_expr(target_expr, mod_name)
3116 if iface_name == '' {
3117 return
3118 }
3119 w.mark_interface_conversion_methods_for_name_cursor(iface_name, value_c, mod_name)
3120}
3121
3122// mark_result_error_return_methods_cursor is the cursor analogue of
3123// mark_result_error_return_methods. Only fires when the function's return
3124// type is ResultType — in that case the receiver candidates for the
3125// returned value are pulled from the cursor, and 'msg' / 'code' are marked
3126// on each.
3127fn (mut w Walker) mark_result_error_return_methods_cursor(return_type ast.Expr, value_c ast.Cursor, mod_name string) {
3128 if return_type is ast.Type && return_type is ast.ResultType {
3129 receivers := w.receiver_candidates_for_cursor(value_c, mod_name)
3130 if receivers.len == 0 {
3131 return
3132 }
3133 w.mark_method_name('msg', receivers)
3134 w.mark_method_name('code', receivers)
3135 }
3136}
3137
3138fn (mut w Walker) mark_interface_conversion_methods_for_name(iface_name string, value_expr ast.Expr, mod_name string) {
3139 receivers := w.receiver_candidates_for_expr(value_expr, mod_name)
3140 if receivers.len == 0 {
3141 return
3142 }
3143 method_names := w.interface_method_names_for(iface_name)
3144 if method_names.len > 0 {
3145 embedded_receivers := w.embedded_receivers_for(receivers)
3146 for method_name in method_names {
3147 w.mark_method_name(method_name, receivers)
3148 w.mark_method_name(method_name, embedded_receivers)
3149 }
3150 }
3151}
3152
3153fn (mut w Walker) mark_call_arg_interface_conversions(expr ast.CallExpr, mod_name string) {
3154 mut marked := false
3155 if w.env == unsafe { nil } {
3156 w.mark_call_arg_interface_conversions_from_decls(expr.lhs, expr.args, mod_name)
3157 return
3158 }
3159 if lhs_type := w.call_lhs_fn_type(expr.lhs, mod_name) {
3160 param_types := lhs_type.get_param_types()
3161 param_offset := if expr.lhs is ast.SelectorExpr { 1 } else { 0 }
3162 for i, arg in expr.args {
3163 param_idx := i + param_offset
3164 if param_idx >= param_types.len {
3165 break
3166 }
3167 param_type := param_types[param_idx]
3168 if param_type is types.Interface {
3169 w.mark_interface_conversion_methods_for_name((param_type as types.Interface).name,
3170 arg, mod_name)
3171 marked = true
3172 }
3173 }
3174 }
3175 if !marked {
3176 w.mark_call_arg_interface_conversions_from_decls(expr.lhs, expr.args, mod_name)
3177 }
3178}
3179
3180fn (mut w Walker) mark_call_or_cast_arg_interface_conversion(expr ast.CallOrCastExpr, mod_name string) {
3181 mut marked := false
3182 if w.env == unsafe { nil } {
3183 w.mark_call_arg_interface_conversions_from_decls(expr.lhs, [expr.expr], mod_name)
3184 return
3185 }
3186 if lhs_type := w.call_lhs_fn_type(expr.lhs, mod_name) {
3187 param_types := lhs_type.get_param_types()
3188 param_offset := if expr.lhs is ast.SelectorExpr { 1 } else { 0 }
3189 if param_offset < param_types.len {
3190 param_type := param_types[param_offset]
3191 if param_type is types.Interface {
3192 w.mark_interface_conversion_methods_for_name((param_type as types.Interface).name,
3193 expr.expr, mod_name)
3194 marked = true
3195 }
3196 }
3197 }
3198 if !marked {
3199 w.mark_call_arg_interface_conversions_from_decls(expr.lhs, [expr.expr], mod_name)
3200 }
3201}
3202
3203fn (w &Walker) add_lookup_indices(key string, mut out []int) {
3204 if !string_ok(key) {
3205 return
3206 }
3207 if values := w.lookup[key] {
3208 for idx in values {
3209 add_unique_int(mut out, idx)
3210 }
3211 }
3212}
3213
3214fn (mut w Walker) add_fn_name_indices(name string, mod_name string, mut out []int) {
3215 if name == '' || !string_ok(name) || !string_ok(mod_name) {
3216 return
3217 }
3218 candidates := w.cached_called_fn_name_candidates(name)
3219 if key := w.exact_mangled_fn_lookup_key(name) {
3220 w.add_lookup_indices(key, mut out)
3221 return
3222 }
3223 for candidate in candidates {
3224 if key := w.current_module_fn_lookup_key(candidate, mod_name) {
3225 w.add_lookup_indices(key, mut out)
3226 return
3227 }
3228 }
3229 for candidate in candidates {
3230 if key := w.selective_import_fn_lookup_key(candidate) {
3231 w.add_lookup_indices(key, mut out)
3232 return
3233 }
3234 }
3235 for candidate in candidates {
3236 w.add_lookup_indices('mod:${mod_name}:${candidate}', mut out)
3237 w.add_lookup_indices('fn:${candidate}', mut out)
3238 if !candidate.contains('__') && mod_name != '' && mod_name != 'main'
3239 && mod_name != 'builtin' {
3240 w.add_lookup_indices('fn:${mod_name}__${candidate}', mut out)
3241 }
3242 }
3243}
3244
3245fn (w &Walker) add_method_name_indices(name string, receivers []string, mut out []int) {
3246 if name == '' || !string_ok(name) {
3247 return
3248 }
3249 normalized := normalize_method_name(name)
3250 for receiver in receivers {
3251 if !string_ok(receiver) {
3252 continue
3253 }
3254 w.add_method_receiver_candidate_indices(receiver, name, normalized, mut out)
3255 }
3256}
3257
3258fn (w &Walker) add_method_candidate_indices(candidate string, name string, normalized string, mut out []int) {
3259 w.add_lookup_indices('meth:${candidate}:${name}', mut out)
3260 if normalized != name {
3261 w.add_lookup_indices('meth:${candidate}:${normalized}', mut out)
3262 }
3263}
3264
3265fn (w &Walker) add_method_receiver_candidate_indices(receiver string, name string, normalized string, mut out []int) {
3266 w.add_method_candidate_indices(receiver, name, normalized, mut out)
3267 mut short_name := ''
3268 if receiver.contains('__') {
3269 short_name = receiver.all_after_last('__')
3270 if short_name != '' && short_name != receiver {
3271 w.add_method_candidate_indices(short_name, name, normalized, mut out)
3272 }
3273 }
3274 if receiver.starts_with('Array_') || receiver.starts_with('Array_fixed_') {
3275 if receiver != 'array' && short_name != 'array' {
3276 w.add_method_candidate_indices('array', name, normalized, mut out)
3277 }
3278 }
3279 if receiver.starts_with('Map_') {
3280 if receiver != 'map' && short_name != 'map' {
3281 w.add_method_candidate_indices('map', name, normalized, mut out)
3282 }
3283 }
3284 if receiver.starts_with('_option_') {
3285 if receiver != '_option' && short_name != '_option' {
3286 w.add_method_candidate_indices('_option', name, normalized, mut out)
3287 }
3288 }
3289 if receiver.starts_with('_result_') {
3290 if receiver != '_result' && short_name != '_result' {
3291 w.add_method_candidate_indices('_result', name, normalized, mut out)
3292 }
3293 }
3294}
3295
3296fn (mut w Walker) call_lhs_decl_indices(lhs ast.Expr, mod_name string) []int {
3297 mut out := []int{}
3298 if !expr_ok(lhs) {
3299 return out
3300 }
3301 match lhs {
3302 ast.Ident {
3303 w.add_fn_name_indices(lhs.name, mod_name, mut out)
3304 }
3305 ast.GenericArgs {
3306 return w.call_lhs_decl_indices(lhs.lhs, mod_name)
3307 }
3308 ast.GenericArgOrIndexExpr {
3309 return w.call_lhs_decl_indices(lhs.lhs, mod_name)
3310 }
3311 ast.SelectorExpr {
3312 method_name := lhs.rhs.name
3313 if !string_ok(method_name) {
3314 return out
3315 }
3316 if lhs.lhs is ast.Ident {
3317 left_name := lhs.lhs.name
3318 if !string_ok(left_name) {
3319 return out
3320 }
3321 if left_name == 'C' {
3322 w.add_fn_name_indices(method_name, mod_name, mut out)
3323 return out
3324 }
3325 if left_name in w.module_names {
3326 real_mod := w.module_alias_to_real[left_name] or { left_name }
3327 w.add_fn_name_indices('${real_mod}__${method_name}', mod_name, mut out)
3328 return out
3329 }
3330 if left_name in w.type_names {
3331 w.add_method_name_indices(method_name,
3332 [left_name, '${mod_name}__${left_name}'], mut out)
3333 return out
3334 }
3335 }
3336 if lhs.lhs is ast.SelectorExpr {
3337 type_sel := lhs.lhs as ast.SelectorExpr
3338 if type_sel.lhs is ast.Ident {
3339 mod_ident := type_sel.lhs.name
3340 type_name := type_sel.rhs.name
3341 if !string_ok(mod_ident) || !string_ok(type_name) {
3342 return out
3343 }
3344 if mod_ident in w.module_names || mod_ident in w.type_names {
3345 mut candidates := []string{cap: 3}
3346 candidates << type_name
3347 candidates << '${mod_name}__${type_name}'
3348 candidates << '${mod_ident}__${type_name}'
3349 w.add_method_name_indices(method_name, candidates, mut out)
3350 return out
3351 }
3352 }
3353 }
3354 receivers := w.receiver_candidates_for_expr(lhs.lhs, mod_name)
3355 if receivers.len > 0 {
3356 w.add_method_name_indices(method_name, receivers, mut out)
3357 if out.len > 0 {
3358 return out
3359 }
3360 }
3361 normalized := normalize_method_name(method_name)
3362 w.add_lookup_indices('mname:${method_name}', mut out)
3363 if normalized != method_name {
3364 w.add_lookup_indices('mname:${normalized}', mut out)
3365 }
3366 }
3367 else {}
3368 }
3369
3370 return out
3371}
3372
3373fn (mut w Walker) mark_call_arg_interface_conversions_from_decls(lhs ast.Expr, args []ast.Expr, mod_name string) {
3374 decl_indices := w.call_lhs_decl_indices(lhs, mod_name)
3375 if decl_indices.len == 0 {
3376 return
3377 }
3378 for idx in decl_indices {
3379 if idx < 0 || idx >= w.fns.len {
3380 continue
3381 }
3382 info := w.fns[idx]
3383 params := info.decl.typ.params
3384 arg_offset := if lhs is ast.Ident && info.decl.is_method && !info.decl.is_static {
3385 1
3386 } else {
3387 0
3388 }
3389 for i, param in params {
3390 arg_idx := i + arg_offset
3391 if arg_idx >= args.len {
3392 break
3393 }
3394 iface_name := w.interface_name_from_expr(param.typ, info.mod)
3395 if iface_name == '' {
3396 continue
3397 }
3398 w.mark_interface_conversion_methods_for_name(iface_name, args[arg_idx], mod_name)
3399 }
3400 }
3401}
3402
3403fn (mut w Walker) mark_string_interpolation_str_dependency(expr ast.Expr, mod_name string) {
3404 receivers := w.receiver_candidates_for_expr(expr, mod_name)
3405 if receivers.len == 0 {
3406 return
3407 }
3408 w.mark_method_name('str', receivers)
3409 for receiver in receivers {
3410 w.mark_fn_name('${receiver}__str', mod_name)
3411 }
3412}
3413
3414fn (mut w Walker) mark_result_error_return_methods(return_type ast.Expr, expr ast.Expr, mod_name string) {
3415 if return_type is ast.Type && return_type is ast.ResultType {
3416 receivers := w.receiver_candidates_for_expr(expr, mod_name)
3417 if receivers.len == 0 {
3418 return
3419 }
3420 w.mark_method_name('msg', receivers)
3421 w.mark_method_name('code', receivers)
3422 }
3423}
3424
3425// call_lhs_fn_type_cursor mirrors call_lhs_fn_type but reads from a Cursor.
3426// receiver_candidates_for_expr still needs a decoded Expr for the
3427// SelectorExpr branch — only that subtree is decoded, and only when env
3428// lookup falls through to it.
3429fn (w &Walker) call_lhs_fn_type_cursor(c ast.Cursor, mod_name string) ?types.FnType {
3430 if !c.is_valid() {
3431 return none
3432 }
3433 if w.env != unsafe { nil } {
3434 if lhs_type := w.env.get_expr_type(c.pos().id) {
3435 if lhs_type is types.FnType {
3436 return lhs_type as types.FnType
3437 }
3438 }
3439 match c.kind() {
3440 .expr_ident {
3441 if fn_type := w.env.lookup_fn(mod_name, c.name()) {
3442 return fn_type
3443 }
3444 }
3445 .expr_selector {
3446 inner_lhs := c.edge(0)
3447 rhs_c := c.edge(1)
3448 if inner_lhs.is_valid() && rhs_c.is_valid() {
3449 receivers := w.receiver_candidates_for_cursor(inner_lhs, mod_name)
3450 for receiver in receivers {
3451 if fn_type := w.env.lookup_method(receiver, rhs_c.name()) {
3452 return fn_type
3453 }
3454 }
3455 }
3456 }
3457 else {}
3458 }
3459 }
3460 if c.kind() == .expr_ident && w.cur_fn_scope != unsafe { nil } {
3461 if obj := w.cur_fn_scope.lookup_parent(c.name(), 0) {
3462 typ := obj.typ()
3463 if typ is types.FnType {
3464 return typ as types.FnType
3465 }
3466 }
3467 }
3468 return none
3469}
3470
3471// call_lhs_decl_indices_cursor mirrors call_lhs_decl_indices with cursor
3472// input. Same shape: Ident / GenericArgs / GenericArgOrIndexExpr / Selector
3473// — the only ast-decoded subtree is lhs.lhs for SelectorExpr's
3474// receiver_candidates_for_expr fallback.
3475fn (mut w Walker) call_lhs_decl_indices_cursor(c ast.Cursor, mod_name string) []int {
3476 mut out := []int{}
3477 if !c.is_valid() {
3478 return out
3479 }
3480 match c.kind() {
3481 .expr_ident {
3482 w.add_fn_name_indices(c.name(), mod_name, mut out)
3483 }
3484 .expr_generic_args {
3485 return w.call_lhs_decl_indices_cursor(c.edge(0), mod_name)
3486 }
3487 .expr_generic_arg_or_index {
3488 return w.call_lhs_decl_indices_cursor(c.edge(0), mod_name)
3489 }
3490 .expr_selector {
3491 rhs_c := c.edge(1)
3492 if !rhs_c.is_valid() {
3493 return out
3494 }
3495 method_name := rhs_c.name()
3496 if !string_ok(method_name) {
3497 return out
3498 }
3499 lhs_c := c.edge(0)
3500 if lhs_c.is_valid() && lhs_c.kind() == .expr_ident {
3501 left_name := lhs_c.name()
3502 if !string_ok(left_name) {
3503 return out
3504 }
3505 if left_name == 'C' {
3506 w.add_fn_name_indices(method_name, mod_name, mut out)
3507 return out
3508 }
3509 if left_name in w.module_names {
3510 real_mod := w.module_alias_to_real[left_name] or { left_name }
3511 w.add_fn_name_indices('${real_mod}__${method_name}', mod_name, mut out)
3512 return out
3513 }
3514 if left_name in w.type_names {
3515 w.add_method_name_indices(method_name,
3516 [left_name, '${mod_name}__${left_name}'], mut out)
3517 return out
3518 }
3519 }
3520 if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
3521 inner_lhs := lhs_c.edge(0)
3522 inner_rhs := lhs_c.edge(1)
3523 if inner_lhs.is_valid() && inner_lhs.kind() == .expr_ident && inner_rhs.is_valid() {
3524 mod_ident := inner_lhs.name()
3525 type_name := inner_rhs.name()
3526 if !string_ok(mod_ident) || !string_ok(type_name) {
3527 return out
3528 }
3529 if mod_ident in w.module_names || mod_ident in w.type_names {
3530 mut candidates := []string{cap: 3}
3531 candidates << type_name
3532 candidates << '${mod_name}__${type_name}'
3533 candidates << '${mod_ident}__${type_name}'
3534 w.add_method_name_indices(method_name, candidates, mut out)
3535 return out
3536 }
3537 }
3538 }
3539 receivers := w.receiver_candidates_for_cursor(lhs_c, mod_name)
3540 if receivers.len > 0 {
3541 w.add_method_name_indices(method_name, receivers, mut out)
3542 if out.len > 0 {
3543 return out
3544 }
3545 }
3546 normalized := normalize_method_name(method_name)
3547 w.add_lookup_indices('mname:${method_name}', mut out)
3548 if normalized != method_name {
3549 w.add_lookup_indices('mname:${normalized}', mut out)
3550 }
3551 }
3552 else {}
3553 }
3554
3555 return out
3556}
3557
3558// mark_call_arg_interface_conversions_cursor / _from_decls_cursor are the
3559// cursor analogues of mark_call_arg_interface_conversions /
3560// mark_call_arg_interface_conversions_from_decls. Args are only decoded on
3561// demand (when the param type is an interface), so non-interface params do
3562// not pay any decode cost.
3563fn (mut w Walker) mark_call_arg_interface_conversions_cursor(call_c ast.Cursor, mod_name string) {
3564 lhs_c := call_c.edge(0)
3565 ec := call_c.edge_count()
3566 args_len := ec - 1
3567 mut marked := false
3568 if w.env != unsafe { nil } {
3569 if lhs_type := w.call_lhs_fn_type_cursor(lhs_c, mod_name) {
3570 param_types := lhs_type.get_param_types()
3571 param_offset := if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
3572 1
3573 } else {
3574 0
3575 }
3576 for i in 0 .. args_len {
3577 param_idx := i + param_offset
3578 if param_idx >= param_types.len {
3579 break
3580 }
3581 param_type := param_types[param_idx]
3582 if param_type is types.Interface {
3583 arg_c := call_c.edge(1 + i)
3584 w.mark_interface_conversion_methods_for_name_cursor((param_type as types.Interface).name,
3585 arg_c, mod_name)
3586 marked = true
3587 }
3588 }
3589 }
3590 } else {
3591 w.mark_call_arg_interface_conversions_from_decls_cursor(call_c, mod_name)
3592 return
3593 }
3594 if !marked {
3595 w.mark_call_arg_interface_conversions_from_decls_cursor(call_c, mod_name)
3596 }
3597}
3598
3599fn (mut w Walker) mark_call_arg_interface_conversions_from_decls_cursor(call_c ast.Cursor, mod_name string) {
3600 lhs_c := call_c.edge(0)
3601 decl_indices := w.call_lhs_decl_indices_cursor(lhs_c, mod_name)
3602 if decl_indices.len == 0 {
3603 return
3604 }
3605 args_len := call_c.edge_count() - 1
3606 lhs_is_ident := lhs_c.is_valid() && lhs_c.kind() == .expr_ident
3607 for idx in decl_indices {
3608 if idx < 0 || idx >= w.fns.len {
3609 continue
3610 }
3611 info := w.fns[idx]
3612 params := info.decl.typ.params
3613 arg_offset := if lhs_is_ident && info.decl.is_method && !info.decl.is_static {
3614 1
3615 } else {
3616 0
3617 }
3618 for i, param in params {
3619 arg_idx := i + arg_offset
3620 if arg_idx >= args_len {
3621 break
3622 }
3623 iface_name := w.interface_name_from_expr(param.typ, info.mod)
3624 if iface_name == '' {
3625 continue
3626 }
3627 arg_c := call_c.edge(1 + arg_idx)
3628 w.mark_interface_conversion_methods_for_name_cursor(iface_name, arg_c, mod_name)
3629 }
3630 }
3631}
3632
3633// mark_call_or_cast_arg_interface_conversion_cursor is the cursor analogue
3634// of mark_call_or_cast_arg_interface_conversion. The "expr" edge (call_c.edge(1))
3635// is treated as a single-arg list; it is decoded only when the param type
3636// is an interface.
3637fn (mut w Walker) mark_call_or_cast_arg_interface_conversion_cursor(call_c ast.Cursor, mod_name string) {
3638 lhs_c := call_c.edge(0)
3639 expr_c := call_c.edge(1)
3640 mut marked := false
3641 if w.env != unsafe { nil } {
3642 if lhs_type := w.call_lhs_fn_type_cursor(lhs_c, mod_name) {
3643 param_types := lhs_type.get_param_types()
3644 param_offset := if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
3645 1
3646 } else {
3647 0
3648 }
3649 if param_offset < param_types.len {
3650 param_type := param_types[param_offset]
3651 if param_type is types.Interface {
3652 w.mark_interface_conversion_methods_for_name_cursor((param_type as types.Interface).name,
3653 expr_c, mod_name)
3654 marked = true
3655 }
3656 }
3657 }
3658 } else {
3659 decl_indices := w.call_lhs_decl_indices_cursor(lhs_c, mod_name)
3660 w.mark_call_or_cast_arg_interface_from_decl_indices_cursor(decl_indices, lhs_c, expr_c,
3661 mod_name)
3662 return
3663 }
3664 if !marked {
3665 decl_indices := w.call_lhs_decl_indices_cursor(lhs_c, mod_name)
3666 w.mark_call_or_cast_arg_interface_from_decl_indices_cursor(decl_indices, lhs_c, expr_c,
3667 mod_name)
3668 }
3669}
3670
3671fn (mut w Walker) mark_call_or_cast_arg_interface_from_decl_indices_cursor(decl_indices []int, lhs_c ast.Cursor, expr_c ast.Cursor, mod_name string) {
3672 if decl_indices.len == 0 || !expr_c.is_valid() {
3673 return
3674 }
3675 lhs_is_ident := lhs_c.is_valid() && lhs_c.kind() == .expr_ident
3676 for idx in decl_indices {
3677 if idx < 0 || idx >= w.fns.len {
3678 continue
3679 }
3680 info := w.fns[idx]
3681 params := info.decl.typ.params
3682 arg_offset := if lhs_is_ident && info.decl.is_method && !info.decl.is_static {
3683 1
3684 } else {
3685 0
3686 }
3687 if arg_offset >= params.len {
3688 continue
3689 }
3690 param := params[arg_offset]
3691 iface_name := w.interface_name_from_expr(param.typ, info.mod)
3692 if iface_name == '' {
3693 continue
3694 }
3695 w.mark_interface_conversion_methods_for_name_cursor(iface_name, expr_c, mod_name)
3696 }
3697}
3698
3699fn (w &Walker) call_lhs_fn_type(lhs ast.Expr, mod_name string) ?types.FnType {
3700 if !expr_ok(lhs) {
3701 return none
3702 }
3703 if w.env != unsafe { nil } {
3704 if lhs_type := w.env.get_expr_type(lhs.pos().id) {
3705 if lhs_type is types.FnType {
3706 return lhs_type as types.FnType
3707 }
3708 }
3709 if lhs is ast.Ident {
3710 if fn_type := w.env.lookup_fn(mod_name, lhs.name) {
3711 return fn_type
3712 }
3713 }
3714 if lhs is ast.SelectorExpr {
3715 receivers := w.receiver_candidates_for_expr(lhs.lhs, mod_name)
3716 for receiver in receivers {
3717 if fn_type := w.env.lookup_method(receiver, lhs.rhs.name) {
3718 return fn_type
3719 }
3720 }
3721 }
3722 }
3723 if lhs is ast.Ident && w.cur_fn_scope != unsafe { nil } {
3724 if obj := w.cur_fn_scope.lookup_parent(lhs.name, 0) {
3725 typ := obj.typ()
3726 if typ is types.FnType {
3727 return typ as types.FnType
3728 }
3729 }
3730 }
3731 return none
3732}
3733
3734fn (w &Walker) current_param_receiver_candidates(name string, mod_name string) []string {
3735 mut out := []string{}
3736 if name == '' || w.cur_fn_decl.name == '' {
3737 return out
3738 }
3739 for param in w.cur_fn_decl.typ.params {
3740 if param.name != name {
3741 continue
3742 }
3743 for receiver in type_expr_receiver_candidates(mod_name, param.typ) {
3744 add_unique_string(mut out, receiver)
3745 }
3746 generic_name := receiver_type_expr_name(param.typ)
3747 if generic_name == '' || w.env == unsafe { nil } {
3748 continue
3749 }
3750 for bindings in w.current_fn_generic_bindings() {
3751 if concrete := bindings[generic_name] {
3752 for receiver in type_name_candidates_from_type(mod_name, concrete) {
3753 add_unique_string(mut out, receiver)
3754 }
3755 }
3756 }
3757 }
3758 return out
3759}
3760
3761fn (w &Walker) current_receiver_candidates(name string, mod_name string) []string {
3762 mut out := []string{}
3763 if name == '' || w.cur_fn_decl.name == '' || !is_method_decl(w.cur_fn_decl)
3764 || w.cur_fn_decl.receiver.name != name {
3765 return out
3766 }
3767 for receiver in type_expr_receiver_candidates(mod_name, w.cur_fn_decl.receiver.typ) {
3768 add_unique_string(mut out, receiver)
3769 }
3770 generic_name := receiver_type_expr_name(w.cur_fn_decl.receiver.typ)
3771 if generic_name == '' || w.env == unsafe { nil } {
3772 return out
3773 }
3774 for bindings in w.current_fn_generic_bindings() {
3775 if concrete := bindings[generic_name] {
3776 for receiver in type_name_candidates_from_type(mod_name, concrete) {
3777 add_unique_string(mut out, receiver)
3778 }
3779 }
3780 }
3781 return out
3782}
3783
3784fn (w &Walker) current_fn_generic_bindings() []map[string]types.Type {
3785 if w.env == unsafe { nil } || w.cur_fn_decl.name == '' {
3786 return []map[string]types.Type{}
3787 }
3788 mut out := []map[string]types.Type{}
3789 for key, bindings_list in w.env.generic_types {
3790 if generic_base_name_from_key(key) != w.cur_fn_decl.name {
3791 continue
3792 }
3793 for bindings in bindings_list {
3794 out << bindings
3795 }
3796 }
3797 return out
3798}
3799
3800// receiver_candidates_for_cursor mirrors receiver_candidates_for_expr but
3801// reads inputs from a Cursor instead of a decoded ast.Expr. Same shape:
3802// env-type lookup by pos.id first, then a structural match on cursor kind.
3803// PrefixExpr / ParenExpr / ModifierExpr / SelectorExpr recurse through
3804// edge(0); InitExpr through its typ at edge(0); CallExpr / CallOrCastExpr
3805// look at edge(0)'s selector.lhs without recursion.
3806fn (w &Walker) receiver_candidates_for_cursor(c ast.Cursor, mod_name string) []string {
3807 mut out := []string{}
3808 if !c.is_valid() {
3809 return out
3810 }
3811 pos := c.pos()
3812 if w.env != unsafe { nil } && pos.is_valid() {
3813 if receiver_type := w.env.get_expr_type(pos.id) {
3814 for name in type_name_candidates_from_type(mod_name, receiver_type) {
3815 add_unique_string(mut out, name)
3816 }
3817 }
3818 }
3819 match c.kind() {
3820 .expr_ident {
3821 ident_name := c.name()
3822 if w.env != unsafe { nil } && w.cur_fn_scope != unsafe { nil } {
3823 if local_type := w.env.lookup_local_var(w.cur_fn_scope, ident_name) {
3824 for type_name in type_name_candidates_from_type(mod_name, local_type) {
3825 add_unique_string(mut out, type_name)
3826 }
3827 }
3828 }
3829 name := sanitize_receiver_name(ident_name)
3830 for type_name in w.current_receiver_candidates(ident_name, mod_name) {
3831 add_unique_string(mut out, type_name)
3832 }
3833 for type_name in w.current_param_receiver_candidates(ident_name, mod_name) {
3834 add_unique_string(mut out, type_name)
3835 }
3836 add_unique_string(mut out, name)
3837 if mod_name != '' && mod_name != 'main' {
3838 add_unique_string(mut out, '${mod_name}__${name}')
3839 }
3840 }
3841 .expr_prefix, .expr_paren, .expr_modifier {
3842 for name in w.receiver_candidates_for_cursor(c.edge(0), mod_name) {
3843 add_unique_string(mut out, name)
3844 }
3845 }
3846 .expr_init {
3847 for name in w.receiver_candidates_for_cursor(c.edge(0), mod_name) {
3848 add_unique_string(mut out, name)
3849 }
3850 }
3851 .expr_call, .expr_call_or_cast {
3852 lhs_c := c.edge(0)
3853 if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
3854 inner_lhs := lhs_c.edge(0)
3855 if inner_lhs.is_valid() && inner_lhs.kind() == .expr_ident {
3856 type_name := inner_lhs.name()
3857 if type_name in w.type_names {
3858 add_receiver_name_candidates(mut out, mod_name, type_name)
3859 }
3860 }
3861 }
3862 }
3863 .expr_selector {
3864 lhs_c := c.edge(0)
3865 rhs_c := c.edge(1)
3866 if rhs_c.is_valid() {
3867 rhs_name := rhs_c.name()
3868 lhs_receivers := w.receiver_candidates_for_cursor(lhs_c, mod_name)
3869 for lhs_receiver in lhs_receivers {
3870 key := '${lhs_receiver}:${rhs_name}'
3871 if field_receivers := w.struct_field_receivers[key] {
3872 for field_receiver in field_receivers {
3873 add_unique_string(mut out, field_receiver)
3874 }
3875 }
3876 }
3877 }
3878 }
3879 else {}
3880 }
3881
3882 return out
3883}
3884
3885// mark_interface_conversion_methods_for_name_cursor is the cursor analogue
3886// of mark_interface_conversion_methods_for_name. The value_expr is replaced
3887// by a value cursor, so receiver_candidates_for_cursor avoids decoding.
3888fn (mut w Walker) mark_interface_conversion_methods_for_name_cursor(iface_name string, value_c ast.Cursor, mod_name string) {
3889 receivers := w.receiver_candidates_for_cursor(value_c, mod_name)
3890 if receivers.len == 0 {
3891 return
3892 }
3893 if iface_name in w.interface_method_names {
3894 embedded_receivers := w.embedded_receivers_for(receivers)
3895 for method_name in w.interface_method_names[iface_name] {
3896 w.mark_method_name(method_name, receivers)
3897 w.mark_method_name(method_name, embedded_receivers)
3898 }
3899 }
3900 w.mark_all_methods_for_receivers(receivers)
3901}
3902
3903// mark_string_interpolation_str_dependency_cursor is the cursor analogue of
3904// mark_string_interpolation_str_dependency.
3905fn (mut w Walker) mark_string_interpolation_str_dependency_cursor(value_c ast.Cursor, mod_name string) {
3906 receivers := w.receiver_candidates_for_cursor(value_c, mod_name)
3907 if receivers.len == 0 {
3908 return
3909 }
3910 w.mark_method_name('str', receivers)
3911 for receiver in receivers {
3912 w.mark_fn_name('${receiver}__str', mod_name)
3913 }
3914}
3915
3916fn (w &Walker) receiver_candidates_for_expr(expr ast.Expr, mod_name string) []string {
3917 mut out := []string{}
3918 if !expr_ok(expr) {
3919 return out
3920 }
3921 pos := expr.pos()
3922 if w.env != unsafe { nil } && pos.is_valid() {
3923 if receiver_type := w.env.get_expr_type(pos.id) {
3924 for name in type_name_candidates_from_type(mod_name, receiver_type) {
3925 add_unique_string(mut out, name)
3926 }
3927 }
3928 }
3929 match expr {
3930 ast.Ident {
3931 if w.env != unsafe { nil } && w.cur_fn_scope != unsafe { nil } {
3932 if local_type := w.env.lookup_local_var(w.cur_fn_scope, expr.name) {
3933 for type_name in type_name_candidates_from_type(mod_name, local_type) {
3934 add_unique_string(mut out, type_name)
3935 }
3936 }
3937 }
3938 name := sanitize_receiver_name(expr.name)
3939 for type_name in w.current_receiver_candidates(expr.name, mod_name) {
3940 add_unique_string(mut out, type_name)
3941 }
3942 for type_name in w.current_param_receiver_candidates(expr.name, mod_name) {
3943 add_unique_string(mut out, type_name)
3944 }
3945 add_unique_string(mut out, name)
3946 if mod_name != '' && mod_name != 'main' {
3947 add_unique_string(mut out, '${mod_name}__${name}')
3948 }
3949 }
3950 ast.PrefixExpr {
3951 for name in w.receiver_candidates_for_expr(expr.expr, mod_name) {
3952 add_unique_string(mut out, name)
3953 }
3954 }
3955 ast.ParenExpr {
3956 for name in w.receiver_candidates_for_expr(expr.expr, mod_name) {
3957 add_unique_string(mut out, name)
3958 }
3959 }
3960 ast.ModifierExpr {
3961 for name in w.receiver_candidates_for_expr(expr.expr, mod_name) {
3962 add_unique_string(mut out, name)
3963 }
3964 }
3965 ast.InitExpr {
3966 // InitExpr has a .typ that names the struct being constructed.
3967 for name in w.receiver_candidates_for_expr(expr.typ, mod_name) {
3968 add_unique_string(mut out, name)
3969 }
3970 }
3971 ast.CallExpr {
3972 if expr.lhs is ast.SelectorExpr {
3973 selector := expr.lhs as ast.SelectorExpr
3974 if selector.lhs is ast.Ident {
3975 type_name := selector.lhs.name
3976 if type_name in w.type_names {
3977 add_receiver_name_candidates(mut out, mod_name, type_name)
3978 }
3979 }
3980 }
3981 }
3982 ast.CallOrCastExpr {
3983 if expr.lhs is ast.SelectorExpr {
3984 selector := expr.lhs as ast.SelectorExpr
3985 if selector.lhs is ast.Ident {
3986 type_name := selector.lhs.name
3987 if type_name in w.type_names {
3988 add_receiver_name_candidates(mut out, mod_name, type_name)
3989 }
3990 }
3991 }
3992 }
3993 ast.SelectorExpr {
3994 lhs_receivers := w.receiver_candidates_for_expr(expr.lhs, mod_name)
3995 for lhs_receiver in lhs_receivers {
3996 key := '${lhs_receiver}:${expr.rhs.name}'
3997 if field_receivers := w.struct_field_receivers[key] {
3998 for field_receiver in field_receivers {
3999 add_unique_string(mut out, field_receiver)
4000 }
4001 }
4002 }
4003 }
4004 else {}
4005 }
4006
4007 return out
4008}
4009
4010fn (w &Walker) is_cast_type_name(name string) bool {
4011 if !string_ok(name) {
4012 return false
4013 }
4014 if name in builtin_cast_type_names {
4015 return true
4016 }
4017 return name in w.type_names
4018}
4019
4020fn (mut w Walker) mark_call_lhs(lhs ast.Expr, mod_name string) {
4021 if !expr_ok(lhs) {
4022 return
4023 }
4024 match lhs {
4025 ast.Ident {
4026 w.mark_fn_name(lhs.name, mod_name)
4027 w.mark_const_fn_value_alias(lhs.name, mod_name)
4028 }
4029 ast.GenericArgs {
4030 w.mark_call_lhs(lhs.lhs, mod_name)
4031 }
4032 ast.GenericArgOrIndexExpr {
4033 w.mark_call_lhs(lhs.lhs, mod_name)
4034 }
4035 ast.SelectorExpr {
4036 method_name := lhs.rhs.name
4037 if !string_ok(method_name) {
4038 return
4039 }
4040 if lhs.lhs is ast.Ident {
4041 left_name := lhs.lhs.name
4042 if !string_ok(left_name) {
4043 return
4044 }
4045 if left_name == 'C' {
4046 w.mark_fn_name(method_name, mod_name)
4047 return
4048 }
4049 if left_name in w.module_names {
4050 // Resolve import aliases (e.g., 'ssa_optimize' → 'optimize')
4051 // so lookup finds 'optimize__func' not 'ssa_optimize__func'.
4052 real_mod := w.module_alias_to_real[left_name] or { left_name }
4053 w.mark_fn_name('${real_mod}__${method_name}', mod_name)
4054 return
4055 }
4056 if left_name in w.type_names {
4057 w.mark_method_name(method_name, [left_name, '${mod_name}__${left_name}'])
4058 return
4059 }
4060 }
4061 if lhs.lhs is ast.SelectorExpr {
4062 type_sel := lhs.lhs as ast.SelectorExpr
4063 if type_sel.lhs is ast.Ident {
4064 mod_ident := type_sel.lhs.name
4065 type_name := type_sel.rhs.name
4066 if !string_ok(mod_ident) || !string_ok(type_name) {
4067 return
4068 }
4069 if mod_ident in w.module_names || mod_ident in w.type_names {
4070 candidates := [
4071 type_name,
4072 '${mod_name}__${type_name}',
4073 '${mod_ident}__${type_name}',
4074 ]
4075 w.mark_method_name(method_name, candidates)
4076 return
4077 }
4078 }
4079 }
4080 receivers := w.receiver_candidates_for_expr(lhs.lhs, mod_name)
4081 if receivers.len > 0 {
4082 prev_queue_len := w.queue.len
4083 w.mark_method_name(method_name, receivers)
4084 if w.queue.len > prev_queue_len {
4085 return
4086 }
4087 }
4088 w.mark_method_name_fallback(method_name)
4089 }
4090 else {}
4091 }
4092}
4093
4094fn (w &Walker) const_fn_value_alias_is_shadowed(name string) bool {
4095 if w.cur_fn_scope == unsafe { nil } {
4096 return false
4097 }
4098 if obj := w.cur_fn_scope.lookup_parent(name, 0) {
4099 return obj !is types.Const
4100 }
4101 return false
4102}
4103
4104fn (mut w Walker) mark_const_fn_value_alias(name string, mod_name string) bool {
4105 if name == '' || !string_ok(name) || !string_ok(mod_name)
4106 || w.const_fn_value_alias_is_shadowed(name) {
4107 return false
4108 }
4109 if target := w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, name)] {
4110 w.mark_fn_name(target, mod_name)
4111 return true
4112 }
4113 return false
4114}
4115
4116// mark_call_lhs_cursor is the cursor-input analogue of mark_call_lhs. It
4117// handles every case structurally via cursor accesses, and only falls back
4118// to decoding `lhs.lhs` for the SelectorExpr receiver_candidates_for_expr
4119// branch — which is itself only reached when the early-return cases (C.fn,
4120// module.fn, Type.method, mod.Type.method) all fail.
4121fn (mut w Walker) mark_call_lhs_cursor(c ast.Cursor, mod_name string) {
4122 if !c.is_valid() {
4123 return
4124 }
4125 match c.kind() {
4126 .expr_ident {
4127 w.mark_fn_name(c.name(), mod_name)
4128 w.mark_const_fn_value_alias(c.name(), mod_name)
4129 }
4130 .expr_generic_args {
4131 w.mark_call_lhs_cursor(c.edge(0), mod_name)
4132 }
4133 .expr_generic_arg_or_index {
4134 w.mark_call_lhs_cursor(c.edge(0), mod_name)
4135 }
4136 .expr_selector {
4137 rhs_c := c.edge(1)
4138 if !rhs_c.is_valid() {
4139 return
4140 }
4141 method_name := rhs_c.name()
4142 if !string_ok(method_name) {
4143 return
4144 }
4145 lhs_c := c.edge(0)
4146 if lhs_c.is_valid() && lhs_c.kind() == .expr_ident {
4147 left_name := lhs_c.name()
4148 if !string_ok(left_name) {
4149 return
4150 }
4151 if left_name == 'C' {
4152 w.mark_fn_name(method_name, mod_name)
4153 return
4154 }
4155 if left_name in w.module_names {
4156 real_mod := w.module_alias_to_real[left_name] or { left_name }
4157 w.mark_fn_name('${real_mod}__${method_name}', mod_name)
4158 return
4159 }
4160 if left_name in w.type_names {
4161 w.mark_method_name(method_name, [left_name, '${mod_name}__${left_name}'])
4162 return
4163 }
4164 }
4165 if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
4166 inner_lhs := lhs_c.edge(0)
4167 inner_rhs := lhs_c.edge(1)
4168 if inner_lhs.is_valid() && inner_lhs.kind() == .expr_ident && inner_rhs.is_valid() {
4169 mod_ident := inner_lhs.name()
4170 type_name := inner_rhs.name()
4171 if !string_ok(mod_ident) || !string_ok(type_name) {
4172 return
4173 }
4174 if mod_ident in w.module_names || mod_ident in w.type_names {
4175 candidates := [
4176 type_name,
4177 '${mod_name}__${type_name}',
4178 '${mod_ident}__${type_name}',
4179 ]
4180 w.mark_method_name(method_name, candidates)
4181 return
4182 }
4183 }
4184 }
4185 // Fallback: cursor-native receiver candidates — no decode.
4186 receivers := w.receiver_candidates_for_cursor(lhs_c, mod_name)
4187 if receivers.len > 0 {
4188 prev_queue_len := w.queue.len
4189 w.mark_method_name(method_name, receivers)
4190 if w.queue.len > prev_queue_len {
4191 return
4192 }
4193 }
4194 w.mark_method_name_fallback(method_name)
4195 }
4196 else {}
4197 }
4198}
4199
4200fn (mut w Walker) mark_selector_fn_value(expr ast.SelectorExpr, mod_name string) {
4201 w.mark_selector_fn_value_with_fallback(expr, mod_name, false)
4202}
4203
4204fn (mut w Walker) mark_selector_fn_value_with_fallback(expr ast.SelectorExpr, mod_name string, force_fallback bool) {
4205 target := w.selector_fn_value_target(expr)
4206 if target != '' {
4207 w.mark_fn_name(target, mod_name)
4208 return
4209 }
4210 if expr.rhs.name == '' {
4211 return
4212 }
4213 receivers := w.receiver_candidates_for_expr(expr.lhs, mod_name)
4214 if receivers.len > 0 {
4215 matched := w.method_lookup_count(expr.rhs.name, receivers) > 0
4216 w.mark_method_name(expr.rhs.name, receivers)
4217 if matched {
4218 return
4219 }
4220 }
4221 if force_fallback || w.expr_has_fn_value_type(ast.Expr(expr)) {
4222 w.mark_method_name_fallback(expr.rhs.name)
4223 }
4224}
4225
4226fn (mut w Walker) mark_selector_fn_value_cursor(c ast.Cursor, mod_name string) {
4227 w.mark_selector_fn_value_cursor_with_fallback(c, mod_name, false)
4228}
4229
4230fn (mut w Walker) mark_selector_fn_value_cursor_with_fallback(c ast.Cursor, mod_name string, force_fallback bool) {
4231 target := w.selector_fn_value_target_cursor(c)
4232 if target != '' {
4233 w.mark_fn_name(target, mod_name)
4234 return
4235 }
4236 rhs_c := c.edge(1)
4237 if !rhs_c.is_valid() {
4238 return
4239 }
4240 method_name := rhs_c.name()
4241 if method_name == '' {
4242 return
4243 }
4244 receivers := w.receiver_candidates_for_cursor(c.edge(0), mod_name)
4245 if receivers.len > 0 {
4246 matched := w.method_lookup_count(method_name, receivers) > 0
4247 w.mark_method_name(method_name, receivers)
4248 if matched {
4249 return
4250 }
4251 }
4252 if force_fallback || w.cursor_has_fn_value_type(c) {
4253 w.mark_method_name_fallback(method_name)
4254 }
4255}
4256
4257fn (w &Walker) selector_fn_value_target(expr ast.SelectorExpr) string {
4258 if expr.lhs !is ast.Ident {
4259 return ''
4260 }
4261 left := expr.lhs as ast.Ident
4262 left_name := left.name
4263 if left_name == 'C' || left_name !in w.module_names {
4264 return ''
4265 }
4266 real_mod := w.module_alias_to_real[left_name] or { left_name }
4267 target := '${real_mod}__${expr.rhs.name}'
4268 if w.lookup_count('fn:${target}') > 0 {
4269 return target
4270 }
4271 return ''
4272}
4273
4274fn (w &Walker) selector_fn_value_target_cursor(c ast.Cursor) string {
4275 if !c.is_valid() || c.kind() != .expr_selector {
4276 return ''
4277 }
4278 lhs_c := c.edge(0)
4279 rhs_c := c.edge(1)
4280 if !lhs_c.is_valid() || lhs_c.kind() != .expr_ident || !rhs_c.is_valid() {
4281 return ''
4282 }
4283 left_name := lhs_c.name()
4284 if left_name == 'C' || left_name !in w.module_names {
4285 return ''
4286 }
4287 real_mod := w.module_alias_to_real[left_name] or { left_name }
4288 target := '${real_mod}__${rhs_c.name()}'
4289 if w.lookup_count('fn:${target}') > 0 {
4290 return target
4291 }
4292 return ''
4293}
4294
4295fn (mut w Walker) mark_fn_value_expr(expr ast.Expr, mod_name string) {
4296 if !w.opts.minimal_runtime_roots || !expr_ok(expr) {
4297 return
4298 }
4299 match expr {
4300 ast.Ident {
4301 if w.mark_const_fn_value_alias(expr.name, mod_name) {
4302 return
4303 }
4304 if w.ident_resolves_to_fn_value(expr.name, mod_name) {
4305 w.mark_fn_name(expr.name, mod_name)
4306 }
4307 }
4308 ast.SelectorExpr {
4309 w.mark_selector_fn_value(expr, mod_name)
4310 }
4311 ast.GenericArgs {
4312 w.mark_fn_value_expr(expr.lhs, mod_name)
4313 }
4314 ast.GenericArgOrIndexExpr {
4315 w.mark_fn_value_expr(expr.lhs, mod_name)
4316 }
4317 ast.ModifierExpr {
4318 w.mark_fn_value_expr(expr.expr, mod_name)
4319 }
4320 ast.ParenExpr {
4321 w.mark_fn_value_expr(expr.expr, mod_name)
4322 }
4323 else {}
4324 }
4325}
4326
4327fn (mut w Walker) mark_fn_value_expr_cursor(c ast.Cursor, mod_name string) {
4328 if !w.opts.minimal_runtime_roots || !c.is_valid() {
4329 return
4330 }
4331 match c.kind() {
4332 .expr_ident {
4333 name := c.name()
4334 if w.mark_const_fn_value_alias(name, mod_name) {
4335 return
4336 }
4337 if w.ident_resolves_to_fn_value(name, mod_name) {
4338 w.mark_fn_name(name, mod_name)
4339 }
4340 }
4341 .expr_selector {
4342 w.mark_selector_fn_value_cursor(c, mod_name)
4343 }
4344 .expr_generic_args {
4345 w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
4346 }
4347 .expr_generic_arg_or_index {
4348 w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
4349 }
4350 .expr_modifier {
4351 w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
4352 }
4353 .expr_paren {
4354 w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
4355 }
4356 else {}
4357 }
4358}
4359
4360fn (mut w Walker) walk_stmts(stmts []ast.Stmt, mod_name string) {
4361 for stmt in stmts {
4362 w.walk_stmt(stmt, mod_name)
4363 }
4364}
4365
4366fn (mut w Walker) walk_stmt(stmt ast.Stmt, mod_name string) {
4367 if !stmt_ok(stmt) {
4368 return
4369 }
4370 match stmt {
4371 ast.AssertStmt {
4372 w.walk_expr(stmt.expr, mod_name)
4373 w.walk_expr(stmt.extra, mod_name)
4374 }
4375 ast.AssignStmt {
4376 for i, rhs in stmt.rhs {
4377 if i < stmt.lhs.len {
4378 w.mark_assignment_interface_conversion(stmt.lhs[i], rhs, mod_name)
4379 }
4380 }
4381 for expr in stmt.lhs {
4382 w.walk_expr(expr, mod_name)
4383 }
4384 for expr in stmt.rhs {
4385 w.mark_fn_value_expr(expr, mod_name)
4386 w.walk_expr(expr, mod_name)
4387 }
4388 }
4389 ast.BlockStmt {
4390 w.walk_stmts(stmt.stmts, mod_name)
4391 }
4392 ast.ComptimeStmt {
4393 w.mark_comptime_method_loop_receivers(stmt.stmt, mod_name)
4394 w.walk_stmt(stmt.stmt, mod_name)
4395 }
4396 ast.ConstDecl {
4397 for field in stmt.fields {
4398 w.mark_fn_value_expr(field.value, mod_name)
4399 w.walk_expr(field.value, mod_name)
4400 }
4401 }
4402 ast.DeferStmt {
4403 w.walk_stmts(stmt.stmts, mod_name)
4404 }
4405 ast.EnumDecl {
4406 for field in stmt.fields {
4407 w.walk_expr(field.value, mod_name)
4408 }
4409 }
4410 ast.ExprStmt {
4411 w.walk_expr(stmt.expr, mod_name)
4412 }
4413 ast.ForInStmt {
4414 w.walk_expr(stmt.key, mod_name)
4415 w.walk_expr(stmt.value, mod_name)
4416 w.walk_expr(stmt.expr, mod_name)
4417 }
4418 ast.ForStmt {
4419 w.walk_stmt(stmt.init, mod_name)
4420 w.walk_expr(stmt.cond, mod_name)
4421 w.walk_stmt(stmt.post, mod_name)
4422 w.walk_stmts(stmt.stmts, mod_name)
4423 }
4424 ast.GlobalDecl {
4425 for field in stmt.fields {
4426 w.walk_expr(field.typ, mod_name)
4427 w.walk_expr(field.value, mod_name)
4428 }
4429 }
4430 ast.LabelStmt {
4431 w.walk_stmt(stmt.stmt, mod_name)
4432 }
4433 ast.ReturnStmt {
4434 for expr in stmt.exprs {
4435 w.mark_fn_value_expr(expr, mod_name)
4436 w.walk_expr(expr, mod_name)
4437 // If the function returns an interface type and the return expr
4438 // is a concrete struct InitExpr, mark the concrete type's
4439 // interface methods so the vtable wrapper can call them.
4440 w.mark_interface_conversion_methods(w.cur_fn_decl.typ.return_type, expr, mod_name)
4441 w.mark_result_error_return_methods(w.cur_fn_decl.typ.return_type, expr, mod_name)
4442 }
4443 }
4444 ast.StructDecl {
4445 for field in stmt.fields {
4446 w.walk_expr(field.typ, mod_name)
4447 w.walk_expr(field.value, mod_name)
4448 }
4449 }
4450 ast.TypeDecl {
4451 w.walk_expr(stmt.base_type, mod_name)
4452 for variant in stmt.variants {
4453 w.walk_expr(variant, mod_name)
4454 }
4455 }
4456 []ast.Attribute {
4457 attrs := stmt as []ast.Attribute
4458 for attr in attrs {
4459 w.walk_expr(attr.value, mod_name)
4460 w.walk_expr(attr.comptime_cond, mod_name)
4461 }
4462 }
4463 else {}
4464 }
4465}
4466
4467fn (mut w Walker) walk_expr(expr ast.Expr, mod_name string) {
4468 if !expr_ok(expr) {
4469 return
4470 }
4471 match expr {
4472 ast.ArrayInitExpr {
4473 w.walk_expr(expr.typ, mod_name)
4474 for item in expr.exprs {
4475 w.mark_fn_value_expr(item, mod_name)
4476 w.walk_expr(item, mod_name)
4477 }
4478 w.mark_fn_value_expr(expr.init, mod_name)
4479 w.walk_expr(expr.init, mod_name)
4480 w.walk_expr(expr.cap, mod_name)
4481 w.walk_expr(expr.len, mod_name)
4482 w.walk_expr(expr.update_expr, mod_name)
4483 }
4484 ast.AsCastExpr {
4485 w.walk_expr(expr.expr, mod_name)
4486 w.walk_expr(expr.typ, mod_name)
4487 }
4488 ast.AssocExpr {
4489 w.walk_expr(expr.typ, mod_name)
4490 w.walk_expr(expr.expr, mod_name)
4491 for field in expr.fields {
4492 w.mark_fn_value_expr(field.value, mod_name)
4493 w.walk_expr(field.value, mod_name)
4494 }
4495 }
4496 ast.CallExpr {
4497 w.mark_call_lhs(expr.lhs, mod_name)
4498 w.mark_call_arg_interface_conversions(expr, mod_name)
4499 w.walk_expr(expr.lhs, mod_name)
4500 for arg in expr.args {
4501 w.mark_fn_value_expr(arg, mod_name)
4502 w.walk_expr(arg, mod_name)
4503 }
4504 }
4505 ast.CallOrCastExpr {
4506 if expr.lhs is ast.Ident && w.is_cast_type_name(expr.lhs.name) {
4507 w.mark_interface_conversion_methods(expr.lhs, expr.expr, mod_name)
4508 w.walk_expr(expr.expr, mod_name)
4509 return
4510 }
4511 w.mark_call_lhs(expr.lhs, mod_name)
4512 w.mark_call_or_cast_arg_interface_conversion(expr, mod_name)
4513 w.walk_expr(expr.lhs, mod_name)
4514 w.mark_fn_value_expr(expr.expr, mod_name)
4515 w.walk_expr(expr.expr, mod_name)
4516 }
4517 ast.CastExpr {
4518 w.mark_interface_conversion_methods(expr.typ, expr.expr, mod_name)
4519 w.walk_expr(expr.typ, mod_name)
4520 w.walk_expr(expr.expr, mod_name)
4521 }
4522 ast.ComptimeExpr {
4523 w.walk_expr(expr.expr, mod_name)
4524 }
4525 ast.FnLiteral {
4526 w.walk_stmts(expr.stmts, mod_name)
4527 }
4528 ast.GenericArgs {
4529 w.walk_expr(expr.lhs, mod_name)
4530 for arg in expr.args {
4531 w.walk_expr(arg, mod_name)
4532 }
4533 }
4534 ast.GenericArgOrIndexExpr {
4535 w.walk_expr(expr.lhs, mod_name)
4536 w.walk_expr(expr.expr, mod_name)
4537 }
4538 ast.Ident {
4539 w.mark_ierror_wrapper_dependencies(expr.name, mod_name)
4540 if (!w.opts.minimal_runtime_roots || should_mark_ident_as_fn(expr.name))
4541 && !w.is_cast_type_name(expr.name) {
4542 w.mark_fn_name(expr.name, mod_name)
4543 }
4544 }
4545 ast.FieldInit {
4546 if expr.value is ast.SelectorExpr && expr.value.lhs !is ast.EmptyExpr {
4547 w.mark_selector_fn_value_with_fallback(expr.value, mod_name, true)
4548 }
4549 w.mark_fn_value_expr(expr.value, mod_name)
4550 w.walk_expr(expr.value, mod_name)
4551 }
4552 ast.IfExpr {
4553 w.walk_expr(expr.cond, mod_name)
4554 w.walk_stmts(expr.stmts, mod_name)
4555 w.walk_expr(expr.else_expr, mod_name)
4556 }
4557 ast.IfGuardExpr {
4558 w.walk_stmt(ast.Stmt(expr.stmt), mod_name)
4559 }
4560 ast.IndexExpr {
4561 w.walk_expr(expr.lhs, mod_name)
4562 w.walk_expr(expr.expr, mod_name)
4563 }
4564 ast.InfixExpr {
4565 w.mark_infix_operator_method(expr, mod_name)
4566 w.walk_expr(expr.lhs, mod_name)
4567 w.walk_expr(expr.rhs, mod_name)
4568 }
4569 ast.InitExpr {
4570 w.mark_interface_conversion_methods(expr.typ, expr, mod_name)
4571 w.walk_expr(expr.typ, mod_name)
4572 for field in expr.fields {
4573 if field.value is ast.SelectorExpr
4574 && w.init_field_is_fn_value(expr.typ, field.name, mod_name) {
4575 w.mark_selector_fn_value_with_fallback(field.value, mod_name, true)
4576 }
4577 w.mark_fn_value_expr(field.value, mod_name)
4578 w.walk_expr(field.value, mod_name)
4579 }
4580 }
4581 ast.KeywordOperator {
4582 for value in expr.exprs {
4583 w.walk_expr(value, mod_name)
4584 }
4585 }
4586 ast.LambdaExpr {
4587 w.walk_expr(expr.expr, mod_name)
4588 }
4589 ast.LockExpr {
4590 for lock_expr in expr.lock_exprs {
4591 w.walk_expr(lock_expr, mod_name)
4592 }
4593 for lock_expr in expr.rlock_exprs {
4594 w.walk_expr(lock_expr, mod_name)
4595 }
4596 w.walk_stmts(expr.stmts, mod_name)
4597 }
4598 ast.MapInitExpr {
4599 w.walk_expr(expr.typ, mod_name)
4600 for key in expr.keys {
4601 w.mark_fn_value_expr(key, mod_name)
4602 w.walk_expr(key, mod_name)
4603 }
4604 for value in expr.vals {
4605 w.mark_fn_value_expr(value, mod_name)
4606 w.walk_expr(value, mod_name)
4607 }
4608 }
4609 ast.MatchExpr {
4610 w.walk_expr(expr.expr, mod_name)
4611 for branch in expr.branches {
4612 for cond in branch.cond {
4613 w.walk_expr(cond, mod_name)
4614 }
4615 w.walk_stmts(branch.stmts, mod_name)
4616 }
4617 }
4618 ast.ModifierExpr {
4619 w.walk_expr(expr.expr, mod_name)
4620 }
4621 ast.OrExpr {
4622 w.walk_expr(expr.expr, mod_name)
4623 w.walk_stmts(expr.stmts, mod_name)
4624 }
4625 ast.ParenExpr {
4626 w.walk_expr(expr.expr, mod_name)
4627 }
4628 ast.PostfixExpr {
4629 w.walk_expr(expr.expr, mod_name)
4630 }
4631 ast.PrefixExpr {
4632 w.walk_expr(expr.expr, mod_name)
4633 }
4634 ast.RangeExpr {
4635 w.walk_expr(expr.start, mod_name)
4636 w.walk_expr(expr.end, mod_name)
4637 }
4638 ast.SelectExpr {
4639 w.walk_stmt(expr.stmt, mod_name)
4640 w.walk_stmts(expr.stmts, mod_name)
4641 w.walk_expr(expr.next, mod_name)
4642 }
4643 ast.SelectorExpr {
4644 w.mark_selector_fn_value(expr, mod_name)
4645 w.walk_expr(expr.lhs, mod_name)
4646 }
4647 ast.SqlExpr {
4648 w.walk_expr(expr.expr, mod_name)
4649 }
4650 ast.StringInterLiteral {
4651 for inter in expr.inters {
4652 w.mark_string_interpolation_str_dependency(inter.expr, mod_name)
4653 w.walk_expr(inter.expr, mod_name)
4654 w.walk_expr(inter.format_expr, mod_name)
4655 }
4656 }
4657 ast.Tuple {
4658 for value in expr.exprs {
4659 w.mark_fn_value_expr(value, mod_name)
4660 w.walk_expr(value, mod_name)
4661 }
4662 }
4663 ast.UnsafeExpr {
4664 w.walk_stmts(expr.stmts, mod_name)
4665 }
4666 ast.Type {
4667 match expr {
4668 ast.ArrayFixedType {
4669 w.walk_expr(expr.len, mod_name)
4670 w.walk_expr(expr.elem_type, mod_name)
4671 }
4672 ast.ArrayType {
4673 w.walk_expr(expr.elem_type, mod_name)
4674 }
4675 ast.ChannelType {
4676 w.walk_expr(expr.cap, mod_name)
4677 w.walk_expr(expr.elem_type, mod_name)
4678 }
4679 ast.FnType {
4680 for param in expr.params {
4681 w.walk_expr(param.typ, mod_name)
4682 }
4683 w.walk_expr(expr.return_type, mod_name)
4684 }
4685 ast.GenericType {
4686 w.walk_expr(expr.name, mod_name)
4687 for param in expr.params {
4688 w.walk_expr(param, mod_name)
4689 }
4690 }
4691 ast.MapType {
4692 w.walk_expr(expr.key_type, mod_name)
4693 w.walk_expr(expr.value_type, mod_name)
4694 }
4695 ast.OptionType {
4696 w.walk_expr(expr.base_type, mod_name)
4697 }
4698 ast.PointerType {
4699 w.walk_expr(expr.base_type, mod_name)
4700 }
4701 ast.ResultType {
4702 w.walk_expr(expr.base_type, mod_name)
4703 }
4704 ast.ThreadType {
4705 w.walk_expr(expr.elem_type, mod_name)
4706 }
4707 ast.TupleType {
4708 for typ in expr.types {
4709 w.walk_expr(typ, mod_name)
4710 }
4711 }
4712 ast.AnonStructType {
4713 for embedded in expr.embedded {
4714 w.walk_expr(embedded, mod_name)
4715 }
4716 for field in expr.fields {
4717 w.walk_expr(field.typ, mod_name)
4718 w.walk_expr(field.value, mod_name)
4719 }
4720 }
4721 else {}
4722 }
4723 }
4724 else {}
4725 }
4726}
4727