Files

4727 lines
133 KiB
V

// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module markused
import v2.ast
import v2.token
import v2.types
// Guard functions for ARM64 backend where default-initialized sum types have NULL data pointers.
fn sumtype_payload_word_is_valid(tag_word u64, data_word u64) bool {
if data_word == 0 {
return false
}
// Native v2 backends use `(tag, data_ptr)` for sumtypes. If the first word
// looks like a small tag, the payload must be a real pointer, not a leaked
// enum/default value like `3`.
if tag_word < 256 {
return data_word >= 0x10000 && data_word < 0x0000800000000000
}
return true
}
fn string_ok(s string) bool {
if s.len == 0 {
return true
}
if s.len < 0 || s.len > 1024 {
return false
}
ptr := unsafe { u64(s.str) }
return ptr >= 0x10000 && ptr < 0x0000800000000000
}
fn expr_ok(expr ast.Expr) bool {
tag_word := unsafe { (&u64(&expr))[0] }
data_word := unsafe { (&u64(&expr))[1] }
return sumtype_payload_word_is_valid(tag_word, data_word)
}
fn stmt_ok(stmt ast.Stmt) bool {
tag_word := unsafe { (&u64(&stmt))[0] }
data_word := unsafe { (&u64(&stmt))[1] }
return sumtype_payload_word_is_valid(tag_word, data_word)
}
fn type_ok(typ types.Type) bool {
tag_word := unsafe { (&u64(&typ))[0] }
data_word := unsafe { (&u64(&typ))[1] }
if data_word == 0 {
return false
}
return sumtype_payload_word_is_valid(tag_word, data_word)
}
const builtin_cast_type_names = [
'bool',
'byte',
'byteptr',
'char',
'charptr',
'f32',
'f64',
'i8',
'i16',
'i32',
'i64',
'int',
'isize',
'rune',
'u8',
'u16',
'u32',
'u64',
'usize',
'voidptr',
]
struct FnInfo {
key string
mod string
file string
decl ast.FnDecl
// decl_id is the FlatNodeId of the FnDecl in the source FlatAst, or
// -1 when the FnInfo was produced by the legacy []ast.File path that
// has no flat backing. When >= 0, walk_collected_from_flat iterates
// the body via Cursor instead of info.decl.stmts so future PRs can
// port walk_stmt arms to cursor input one at a time.
decl_id ast.FlatNodeId = -1
// has_body is true when the FnDecl has a non-empty body. In flat mode
// the body is not decoded into info.decl.stmts (signature-only
// decode), so we precompute the bit at collect time from the body
// edge's child count. In legacy mode it mirrors info.decl.stmts.len > 0.
has_body bool
}
struct Walker {
files []ast.File
env &types.Environment = unsafe { nil }
opts MarkUsedOptions
mut:
fns []FnInfo
queue []int
queued_fn_indices map[int]bool
used_keys map[string]bool
module_names map[string]bool
module_alias_to_real map[string]string
type_names map[string]bool
interface_type_names map[string]bool
interface_method_names map[string][]string
interface_embedded_names map[string][]string
methods_by_receiver map[string][]int
struct_field_receivers map[string][]string
struct_embedded_receivers map[string][]string
struct_fn_fields map[string]bool
global_interface_names map[string]string
const_fn_value_aliases map[string]string
selective_import_fn_targets map[string]string
called_fn_name_candidate_cache map[string][]string
lookup map[string][]int
cur_fn_scope &types.Scope = unsafe { nil }
cur_fn_decl ast.FnDecl
cur_fn_file string
}
pub struct MarkUsedOptions {
pub:
minimal_runtime_roots bool
}
// mark_used walks reachable function/method bodies and returns declaration keys
// for the functions that are used at least once.
pub fn mark_used(files []ast.File, env &types.Environment) map[string]bool {
mut w := new_walker(files, env, MarkUsedOptions{})
return w.walk()
}
pub fn mark_used_with_options(files []ast.File, env &types.Environment, opts MarkUsedOptions) map[string]bool {
mut w := new_walker(files, env, opts)
return w.walk()
}
// decl_key computes the stable key used by mark_used for a function declaration.
pub fn decl_key(module_name string, decl ast.FnDecl, env &types.Environment) string {
mod_name := normalize_module_name(module_name)
if is_method_decl(decl) {
receiver := receiver_primary_name(mod_name, decl, env)
return '${mod_name}|m|${receiver}|${normalize_method_name(decl.name)}'
}
return '${mod_name}|f|${decl.name}'
}
// decl_key_from_cursor computes the stable markused key for a flat FnDecl cursor.
pub fn decl_key_from_cursor(module_name string, c ast.Cursor, env &types.Environment) string {
decl := c.fn_decl_signature()
if decl.name == '' {
return ''
}
return decl_key(module_name, decl, env)
}
fn new_walker(files []ast.File, env &types.Environment, opts MarkUsedOptions) Walker {
return Walker{
files: files
env: unsafe { env }
opts: opts
used_keys: map[string]bool{}
queued_fn_indices: map[int]bool{}
module_names: map[string]bool{}
type_names: map[string]bool{}
interface_type_names: map[string]bool{}
interface_method_names: map[string][]string{}
interface_embedded_names: map[string][]string{}
methods_by_receiver: map[string][]int{}
struct_field_receivers: map[string][]string{}
struct_embedded_receivers: map[string][]string{}
struct_fn_fields: map[string]bool{}
global_interface_names: map[string]string{}
const_fn_value_aliases: map[string]string{}
selective_import_fn_targets: map[string]string{}
called_fn_name_candidate_cache: map[string][]string{}
lookup: map[string][]int{}
}
}
fn (mut w Walker) walk() map[string]bool {
w.collect_defs()
return w.walk_collected()
}
// walk_collected runs the body-walking phase after collect_defs (or
// collect_defs_from_flat) has populated w.fns. Split out so flat-input
// entry points can substitute their own collect step.
fn (mut w Walker) walk_collected() map[string]bool {
if w.fns.len == 0 {
return map[string]bool{}
}
if !w.seed_roots() {
mut all := map[string]bool{}
for info in w.fns {
all[info.key] = true
}
return all
}
mut qi := 0
for qi < w.queue.len {
idx := w.queue[qi]
qi++
info := w.fns[idx]
w.cur_fn_scope = w.lookup_fn_scope(info)
w.cur_fn_decl = info.decl
w.cur_fn_file = info.file
w.walk_stmts(info.decl.stmts, info.mod)
w.cur_fn_scope = unsafe { nil }
w.cur_fn_file = ''
}
return w.used_keys
}
// walk_collected_from_flat mirrors walk_collected but, for FnInfos that
// originated from flat input (decl_id >= 0), iterates the body via Cursor
// instead of the pre-rehydrated info.decl.stmts slice. For now each body
// stmt is decoded back to ast.Stmt before being dispatched to walk_stmt —
// later PRs port walk_stmt arms to accept cursor input directly so the
// per-stmt rehydration can drop out. FnInfos without a flat backing
// (decl_id < 0) fall back to the legacy slice walk.
fn (mut w Walker) walk_collected_from_flat(flat &ast.FlatAst) map[string]bool {
if w.fns.len == 0 {
return map[string]bool{}
}
if !w.seed_roots_from_flat(flat) {
mut all := map[string]bool{}
for info in w.fns {
all[info.key] = true
}
return all
}
mut qi := 0
for qi < w.queue.len {
idx := w.queue[qi]
qi++
info := w.fns[idx]
w.cur_fn_scope = w.lookup_fn_scope(info)
w.cur_fn_decl = info.decl
w.cur_fn_file = info.file
if info.decl_id >= 0 {
w.walk_fn_body_cursor(flat, info.decl_id, info.mod)
} else {
w.walk_stmts(info.decl.stmts, info.mod)
}
w.cur_fn_scope = unsafe { nil }
w.cur_fn_file = ''
}
return w.used_keys
}
// walk_fn_body_cursor iterates an FnDecl body (edge 3 of stmt_fn_decl) via
// CursorList and dispatches each child through walk_stmt_cursor. Arms that
// have been ported to cursor input avoid per-stmt rehydration; un-ported
// arms decode back to ast.Stmt and call walk_stmt.
fn (mut w Walker) walk_fn_body_cursor(flat &ast.FlatAst, decl_id ast.FlatNodeId, mod_name string) {
body := ast.Cursor{
flat: unsafe { flat }
id: decl_id
}.list_at(3)
for i in 0 .. body.len() {
w.walk_stmt_cursor(body.at(i), mod_name)
}
}
// walk_stmt_cursor is the cursor-input analogue of walk_stmt. Arms that have
// been ported operate directly on the FlatNode (no Stmt rehydration); the
// fallback decodes the stmt and delegates to walk_stmt so partial coverage
// stays bit-identical with the legacy walker.
//
// Ported arms: stmt_assert, stmt_assign, stmt_attributes, stmt_block,
// stmt_comptime, stmt_const_decl, stmt_defer, stmt_enum_decl, stmt_expr,
// stmt_for, stmt_for_in, stmt_global_decl, stmt_label, stmt_return,
// stmt_struct_decl, stmt_type_decl — every kind the legacy walk_stmt
// matched explicitly. Nested expressions are still routed through
// walk_expr (decoded per edge) until walk_expr itself gets a cursor-input
// port. The else arm catches stmt kinds the legacy walker ignored
// (stmt_asm/_directive/_empty/_flow_control/_fn_decl/_import/_interface_decl
// /_module) plus any future kinds; it stays in place as a safety net.
fn (mut w Walker) walk_stmt_cursor(c ast.Cursor, mod_name string) {
if !c.is_valid() {
return
}
match c.kind() {
.stmt_assert {
w.walk_expr_cursor_edge(c, 0, mod_name)
w.walk_expr_cursor_edge(c, 1, mod_name)
}
.stmt_attributes {
// edge 0 = aux_list of aux_attribute children; each attribute has
// edge 0 = value expr, edge 1 = comptime_cond expr.
attrs := c.list_at(0)
for i in 0 .. attrs.len() {
attr := attrs.at(i)
if !attr.is_valid() {
continue
}
w.walk_expr_cursor_edge(attr, 0, mod_name)
w.walk_expr_cursor_edge(attr, 1, mod_name)
}
}
.stmt_assign {
ec := c.edge_count()
lhs_len := c.extra_int()
// Mirror the legacy ordering: first the interface-conversion
// checks across paired (lhs[i], rhs[i]) edges, then walk all
// lhs exprs, then all rhs exprs.
pair_len := if lhs_len < ec - lhs_len { lhs_len } else { ec - lhs_len }
for i in 0 .. pair_len {
lhs_c := c.edge(i)
rhs_c := c.edge(lhs_len + i)
if !lhs_c.is_valid() || !rhs_c.is_valid() {
continue
}
w.mark_assignment_interface_conversion_cursor(lhs_c, rhs_c, mod_name)
}
for i in lhs_len .. ec {
w.mark_fn_value_expr_cursor(c.edge(i), mod_name)
}
for i in 0 .. ec {
w.walk_expr_cursor_edge(c, i, mod_name)
}
}
.stmt_block {
for i in 0 .. c.edge_count() {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
}
.stmt_comptime {
w.mark_comptime_method_loop_receivers_cursor(c.edge(0), mod_name)
w.walk_stmt_cursor(c.edge(0), mod_name)
}
.stmt_const_decl {
// edge 0 = fields (aux_field_init list); each field edge 0 = value expr.
w.walk_field_value_list(c, 0, mod_name)
}
.stmt_defer {
for i in 0 .. c.edge_count() {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
}
.stmt_enum_decl {
// edge 2 = fields (aux_field_decl list); each field edge 1 = value expr.
w.walk_field_decl_list(c, 2, mod_name, false)
}
.stmt_expr {
w.walk_expr_cursor_edge(c, 0, mod_name)
}
.stmt_for {
w.walk_stmt_cursor(c.edge(0), mod_name)
w.walk_expr_cursor_edge(c, 1, mod_name)
w.walk_stmt_cursor(c.edge(2), mod_name)
for i in 3 .. c.edge_count() {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
}
.stmt_for_in {
w.walk_expr_cursor_edge(c, 0, mod_name)
w.walk_expr_cursor_edge(c, 1, mod_name)
w.walk_expr_cursor_edge(c, 2, mod_name)
}
.stmt_global_decl {
// edge 1 = fields (aux_field_decl list); each field walks typ+value.
w.walk_field_decl_list(c, 1, mod_name, true)
}
.stmt_label {
w.walk_stmt_cursor(c.edge(0), mod_name)
}
.stmt_return {
for i in 0 .. c.edge_count() {
ec := c.edge(i)
if !ec.is_valid() {
continue
}
w.mark_fn_value_expr_cursor(ec, mod_name)
w.walk_expr_cursor(ec, mod_name)
w.mark_interface_conversion_methods_cursor(w.cur_fn_decl.typ.return_type, ec,
mod_name)
w.mark_result_error_return_methods_cursor(w.cur_fn_decl.typ.return_type, ec,
mod_name)
}
}
.stmt_struct_decl {
// edge 4 = fields (aux_field_decl list); each field walks typ+value.
w.walk_field_decl_list(c, 4, mod_name, true)
}
.stmt_type_decl {
// edge 0 = base_type (expr); edge 3 = variants (aux expr list).
w.walk_expr_cursor_edge(c, 0, mod_name)
variants := c.list_at(3)
for i in 0 .. variants.len() {
vc := variants.at(i)
if !vc.is_valid() {
continue
}
w.walk_expr_cursor(vc, mod_name)
}
}
// Kinds intentionally ignored by the walker (mirror of legacy
// walk_stmt's `else {}`): stmt_asm, stmt_directive, stmt_empty,
// stmt_flow_control (break/continue/goto — common in fn bodies,
// hence worth avoiding a decode_stmt), stmt_fn_decl,
// stmt_import, stmt_interface_decl, stmt_module. The legacy
// walker walks none of these so neither do we.
else {}
}
}
// walk_expr_cursor_edge dispatches child edge `edge_i` of `parent` through
// walk_expr_cursor. Kept as a tiny helper so callers don't have to do the
// edge-+-validity dance inline.
fn (mut w Walker) walk_expr_cursor_edge(parent ast.Cursor, edge_i int, mod_name string) {
w.walk_expr_cursor(parent.edge(edge_i), mod_name)
}
// walk_expr_cursor is the cursor-input analogue of walk_expr. Ported arms
// operate directly on the FlatNode; un-ported arms decode the expr and
// delegate to walk_expr so partial coverage stays bit-identical with the
// legacy walker.
//
// Ported arms (no per-node decoding):
// * leaves: expr_basic_literal, expr_empty, expr_ident, typ_nil, typ_none
// * single-edge recursers: expr_comptime, expr_lambda, expr_modifier,
// expr_paren, expr_postfix, expr_prefix, expr_sql, typ_array,
// typ_option, typ_pointer, typ_result, typ_thread
// * two-edge recursers: expr_as_cast, expr_generic_arg_or_index,
// expr_index, expr_range, typ_array_fixed, typ_channel, typ_map
// * N-edge recursers: expr_keyword_operator, expr_tuple, typ_tuple
// * nested-stmt / packed-shape arms: expr_array_init, expr_fn_literal,
// expr_if, expr_if_guard, expr_lock, expr_map_init, expr_match,
// expr_or, expr_select, expr_unsafe, typ_anon_struct, typ_fn,
// typ_generic
// * no-mark structural arms: expr_assoc, expr_generic_args
//
// The remaining un-ported arms (expr_call, expr_call_or_cast, expr_cast,
// expr_infix, expr_init, expr_selector, expr_string_inter) all invoke
// mark_* helpers that take decoded ast.Expr / ast.CallExpr / ast.InfixExpr
// today. Porting them buys little until those helpers grow cursor-input
// variants; the else fallback decodes + delegates and stays bit-identical.
fn (mut w Walker) walk_expr_cursor(c ast.Cursor, mod_name string) {
if !c.is_valid() {
return
}
match c.kind() {
// Leaves with no children and no walker side-effects. The legacy
// walk_expr arms for Keyword / StringLiteral / Lifetime hit its
// else {}, so they belong here too.
.expr_basic_literal, .expr_empty, .expr_keyword, .expr_lifetime, .expr_string, .typ_nil,
.typ_none {}
.expr_ident {
name := c.name()
w.mark_ierror_wrapper_dependencies(name, mod_name)
if (!w.opts.minimal_runtime_roots || should_mark_ident_as_fn(name))
&& !w.is_cast_type_name(name) {
w.mark_fn_name(name, mod_name)
}
}
// Single-edge recursers — child at edge 0.
.expr_comptime, .expr_lambda, .expr_modifier, .expr_paren, .expr_postfix, .expr_prefix,
.expr_sql, .typ_array, .typ_option, .typ_pointer, .typ_result, .typ_thread {
w.walk_expr_cursor(c.edge(0), mod_name)
}
// Two-edge recursers.
.expr_as_cast, .expr_generic_arg_or_index, .expr_index, .expr_range, .typ_array_fixed,
.typ_channel, .typ_map {
w.walk_expr_cursor(c.edge(0), mod_name)
w.walk_expr_cursor(c.edge(1), mod_name)
}
// N-edge recursers — every direct edge is an Expr to walk.
.expr_keyword_operator, .typ_tuple {
for i in 0 .. c.edge_count() {
w.walk_expr_cursor(c.edge(i), mod_name)
}
}
.expr_tuple {
for i in 0 .. c.edge_count() {
value_c := c.edge(i)
w.mark_fn_value_expr_cursor(value_c, mod_name)
w.walk_expr_cursor(value_c, mod_name)
}
}
// typ_generic packs name as edge 0 and generic params at edges 1..N;
// the legacy walker calls walk_expr on each of them in order.
.typ_generic {
for i in 0 .. c.edge_count() {
w.walk_expr_cursor(c.edge(i), mod_name)
}
}
// expr_array_init: edge 0 = typ, 1 = init, 2 = cap, 3 = len,
// 4 = update_expr, 5.. = exprs. Legacy walk order is typ, exprs,
// init, cap, len, update_expr.
.expr_array_init {
ec := c.edge_count()
w.walk_expr_cursor(c.edge(0), mod_name)
for i in 5 .. ec {
item_c := c.edge(i)
w.mark_fn_value_expr_cursor(item_c, mod_name)
w.walk_expr_cursor(item_c, mod_name)
}
w.mark_fn_value_expr_cursor(c.edge(1), mod_name)
w.walk_expr_cursor(c.edge(1), mod_name)
w.walk_expr_cursor(c.edge(2), mod_name)
w.walk_expr_cursor(c.edge(3), mod_name)
w.walk_expr_cursor(c.edge(4), mod_name)
}
// expr_map_init: edge 0 = typ, 1..1+keys_len = keys,
// 1+keys_len.. = vals. keys_len is packed in `extra`.
.expr_map_init {
keys_len := c.extra_int()
ec := c.edge_count()
w.walk_expr_cursor(c.edge(0), mod_name)
for i in 1 .. (1 + keys_len) {
key_c := c.edge(i)
w.mark_fn_value_expr_cursor(key_c, mod_name)
w.walk_expr_cursor(key_c, mod_name)
}
for i in (1 + keys_len) .. ec {
value_c := c.edge(i)
w.mark_fn_value_expr_cursor(value_c, mod_name)
w.walk_expr_cursor(value_c, mod_name)
}
}
// expr_lock: lock_len + rlock_len are packed in `extra` (low/high u16).
// Layout is lock_exprs, rlock_exprs, stmts.
.expr_lock {
packed := c.extra_int()
lock_len := packed & 0xFFFF
rlock_len := (packed >> 16) & 0xFFFF
ec := c.edge_count()
for i in 0 .. lock_len {
w.walk_expr_cursor(c.edge(i), mod_name)
}
for i in lock_len .. (lock_len + rlock_len) {
w.walk_expr_cursor(c.edge(i), mod_name)
}
for i in (lock_len + rlock_len) .. ec {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
}
// expr_if: edge 0 = cond, edge 1 = else_expr, edges 2.. = stmts.
// Legacy walks cond → stmts → else_expr.
.expr_if {
ec := c.edge_count()
w.walk_expr_cursor(c.edge(0), mod_name)
for i in 2 .. ec {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
w.walk_expr_cursor(c.edge(1), mod_name)
}
// expr_if_guard: edge 0 = the embedded AssignStmt.
.expr_if_guard {
w.walk_stmt_cursor(c.edge(0), mod_name)
}
// expr_or: edge 0 = expr, edges 1.. = stmts.
.expr_or {
w.walk_expr_cursor(c.edge(0), mod_name)
for i in 1 .. c.edge_count() {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
}
// expr_unsafe: all edges are stmts.
.expr_unsafe {
for i in 0 .. c.edge_count() {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
}
// expr_fn_literal: edge 0 = fn_typ, edges 1..1+cap_len = captured
// (not walked by the legacy walker), edges 1+cap_len.. = stmts.
// cap_len is packed in `extra`.
.expr_fn_literal {
cap_len := c.extra_int()
for i in (1 + cap_len) .. c.edge_count() {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
}
// expr_select: edge 0 = stmt, edge 1 = next (expr), edges 2.. = stmts.
// Legacy walks stmt → stmts → next.
.expr_select {
ec := c.edge_count()
w.walk_stmt_cursor(c.edge(0), mod_name)
for i in 2 .. ec {
w.walk_stmt_cursor(c.edge(i), mod_name)
}
w.walk_expr_cursor(c.edge(1), mod_name)
}
// expr_match: edge 0 = scrutinee expr, edges 1.. = aux_match_branch.
// Each match_branch has edge 0 = aux_list of cond exprs, edge 1 =
// aux_list of stmts.
.expr_match {
w.walk_expr_cursor(c.edge(0), mod_name)
for i in 1 .. c.edge_count() {
branch := c.edge(i)
if !branch.is_valid() {
continue
}
conds := branch.list_at(0)
for j in 0 .. conds.len() {
w.walk_expr_cursor(conds.at(j), mod_name)
}
stmts := branch.list_at(1)
for j in 0 .. stmts.len() {
w.walk_stmt_cursor(stmts.at(j), mod_name)
}
}
}
// typ_fn: edge 0 = generic_params (not walked by legacy), edge 1 =
// aux_list of aux_parameter, edge 2 = return_type. Each parameter
// node has edge 0 = its type.
.typ_fn {
params_list := c.list_at(1)
for i in 0 .. params_list.len() {
param := params_list.at(i)
if !param.is_valid() {
continue
}
w.walk_expr_cursor(param.edge(0), mod_name)
}
w.walk_expr_cursor(c.edge(2), mod_name)
}
// typ_anon_struct: edge 0 = generic_params (not walked by legacy),
// edge 1 = embedded (aux_list of exprs), edge 2 = fields
// (aux_list of aux_field_decl).
.typ_anon_struct {
embedded := c.list_at(1)
for i in 0 .. embedded.len() {
w.walk_expr_cursor(embedded.at(i), mod_name)
}
w.walk_field_decl_list(c, 2, mod_name, true)
}
// expr_assoc: edge 0 = typ, edge 1 = expr, edges 2.. = aux_field_init
// nodes (each with edge 0 = value expr). No mark side-effects, so the
// arm is purely structural.
.expr_assoc {
w.walk_expr_cursor(c.edge(0), mod_name)
w.walk_expr_cursor(c.edge(1), mod_name)
for i in 2 .. c.edge_count() {
field := c.edge(i)
if !field.is_valid() {
continue
}
value_c := field.edge(0)
w.mark_fn_value_expr_cursor(value_c, mod_name)
w.walk_expr_cursor(value_c, mod_name)
}
}
// expr_generic_args: edge 0 = lhs, edges 1.. = args. No mark
// side-effects — every edge is a walked expr.
.expr_generic_args {
for i in 0 .. c.edge_count() {
w.walk_expr_cursor(c.edge(i), mod_name)
}
}
// expr_call: edge 0 = lhs, edges 1.. = args. Cursor port uses
// mark_call_lhs_cursor + mark_call_arg_interface_conversions_cursor —
// both decode lhs.lhs (selector receiver fallback) and args (only
// when the matching param is an interface) on demand. Everything
// else is structural.
.expr_call {
lhs_c := c.edge(0)
w.mark_call_lhs_cursor(lhs_c, mod_name)
w.mark_call_arg_interface_conversions_cursor(c, mod_name)
w.walk_expr_cursor(lhs_c, mod_name)
for i in 1 .. c.edge_count() {
arg_c := c.edge(i)
if arg_c.is_valid() && arg_c.kind() == .aux_field_init {
value_c := arg_c.edge(0)
if value_c.is_valid() && value_c.kind() == .expr_selector
&& value_c.edge(0).kind() != .expr_empty {
w.mark_selector_fn_value_cursor_with_fallback(value_c, mod_name, true)
}
w.mark_fn_value_expr_cursor(value_c, mod_name)
w.walk_expr_cursor(value_c, mod_name)
continue
}
w.mark_fn_value_expr_cursor(arg_c, mod_name)
w.walk_expr_cursor(arg_c, mod_name)
}
}
// expr_call_or_cast: edge 0 = lhs, edge 1 = inner. When lhs is an
// Ident naming a cast type, behaves like a cast — call
// mark_interface_conversion_methods and walk only the inner expr.
// Otherwise behaves like a call (mark_call_lhs + the single-arg
// interface conversion check).
.expr_call_or_cast {
lhs_c := c.edge(0)
expr_c := c.edge(1)
if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && w.is_cast_type_name(lhs_c.name()) {
iface_name := w.interface_name_from_cursor(lhs_c, mod_name)
if iface_name != '' && expr_c.is_valid() {
w.mark_interface_conversion_methods_for_name_cursor(iface_name, expr_c,
mod_name)
}
w.walk_expr_cursor(expr_c, mod_name)
} else {
w.mark_call_lhs_cursor(lhs_c, mod_name)
w.mark_call_or_cast_arg_interface_conversion_cursor(c, mod_name)
w.walk_expr_cursor(lhs_c, mod_name)
w.mark_fn_value_expr_cursor(expr_c, mod_name)
w.walk_expr_cursor(expr_c, mod_name)
}
}
// expr_cast: edge 0 = typ, edge 1 = value. Legacy walker calls
// mark_interface_conversion_methods(typ, value) which is a no-op unless
// typ resolves to a known interface. We peek typ via
// interface_name_from_cursor (only matches Ident / SelectorExpr,
// both cheap), and only when it fires do we decode the value subtree
// to feed receiver_candidates_for_expr.
.expr_cast {
typ_c := c.edge(0)
expr_c := c.edge(1)
iface_name := w.interface_name_from_cursor(typ_c, mod_name)
if iface_name != '' && expr_c.is_valid() {
w.mark_interface_conversion_methods_for_name_cursor(iface_name, expr_c, mod_name)
}
w.walk_expr_cursor(typ_c, mod_name)
w.walk_expr_cursor(expr_c, mod_name)
}
// expr_init: edge 0 = typ, edges 1.. = aux_field_init (edge 0 = value).
// Legacy walker calls mark_interface_conversion_methods(typ, expr)
// where the value_expr is the whole InitExpr; receiver_candidates_for_
// cursor's expr_init branch reads pos + recurses into the typ cursor,
// so passing this cursor directly matches the legacy semantics.
.expr_init {
typ_c := c.edge(0)
iface_name := w.interface_name_from_cursor(typ_c, mod_name)
if iface_name != '' {
w.mark_interface_conversion_methods_for_name_cursor(iface_name, c, mod_name)
}
w.walk_expr_cursor(typ_c, mod_name)
for i in 1 .. c.edge_count() {
field := c.edge(i)
if !field.is_valid() {
continue
}
value_c := field.edge(0)
if value_c.is_valid() && value_c.kind() == .expr_selector
&& w.init_field_is_fn_value_cursor(typ_c, field.name(), mod_name) {
w.mark_selector_fn_value_cursor_with_fallback(value_c, mod_name, true)
}
w.mark_fn_value_expr_cursor(value_c, mod_name)
w.walk_expr_cursor(value_c, mod_name)
}
}
// expr_infix: aux = op (Token int), edge 0 = lhs, edge 1 = rhs.
// mark_infix_operator_method is a no-op for ops outside the method-
// dispatching set, so we gate the decode on the op kind. When the op
// does qualify, we decode just the lhs subtree to feed
// receiver_candidates_for_expr, then walk both children via cursor.
.expr_infix {
op := unsafe { token.Token(int(c.aux())) }
if infix_operator_may_use_method(op) {
method_name := op.str()
receivers := w.receiver_candidates_for_cursor(c.edge(0), mod_name)
if receivers.len > 0 {
w.mark_method_name(method_name, receivers)
} else {
w.mark_method_name_fallback(method_name)
}
}
w.walk_expr_cursor(c.edge(0), mod_name)
w.walk_expr_cursor(c.edge(1), mod_name)
}
// expr_string_inter: edge 0 = aux_list of value strings (not walked),
// edge 1 = aux_list of inter nodes. Each inter has edge 0 = expr,
// edge 1 = format_expr. Legacy walker calls
// mark_string_interpolation_str_dependency(inter.expr) which needs a
// decoded ast.Expr to feed receiver_candidates_for_expr. We decode
// just that subtree per inter and walk both children via cursor —
// format_expr's subtree never gets decoded.
.expr_string_inter {
inters := c.list_at(1)
for i in 0 .. inters.len() {
inter := inters.at(i)
if !inter.is_valid() {
continue
}
expr_c := inter.edge(0)
if expr_c.is_valid() {
w.mark_string_interpolation_str_dependency_cursor(expr_c, mod_name)
}
w.walk_expr_cursor(expr_c, mod_name)
w.walk_expr_cursor(inter.edge(1), mod_name)
}
}
// expr_selector: edge 0 = lhs, edge 1 = rhs (Ident). Legacy walker calls
// mark_selector_fn_value(expr), which only marks module function values
// that resolve to an indexed function.
.expr_selector {
lhs_c := c.edge(0)
w.mark_selector_fn_value_cursor(c, mod_name)
w.walk_expr_cursor(lhs_c, mod_name)
}
// Every expr/type FlatNodeKind has an explicit arm above; aux_*
// nodes never reach walk_expr_cursor (they are addressed via list_at
// / edge by their parents). Anything else is a schema mismatch.
else {}
}
}
// walk_field_value_list iterates the aux_field_init list at `parent.edge(list_edge)`
// and handles each field's value expr (aux_field_init edge 0). Used by
// stmt_const_decl, whose only walk-relevant per-field datum is the value.
fn (mut w Walker) walk_field_value_list(parent ast.Cursor, list_edge int, mod_name string) {
fields := parent.list_at(list_edge)
for i in 0 .. fields.len() {
field := fields.at(i)
if !field.is_valid() {
continue
}
w.mark_fn_value_expr_cursor(field.edge(0), mod_name)
w.walk_expr_cursor_edge(field, 0, mod_name)
}
}
// walk_field_decl_list iterates the aux_field_decl list at `parent.edge(list_edge)`.
// Each aux_field_decl has edge 0 = typ, edge 1 = value, edge 2 = attrs. When
// `walk_typ` is true the typ edge is also walked (struct/global decls);
// otherwise only the value edge is walked (enum decls).
fn (mut w Walker) walk_field_decl_list(parent ast.Cursor, list_edge int, mod_name string, walk_typ bool) {
fields := parent.list_at(list_edge)
for i in 0 .. fields.len() {
field := fields.at(i)
if !field.is_valid() {
continue
}
if walk_typ {
w.walk_expr_cursor_edge(field, 0, mod_name)
}
w.walk_expr_cursor_edge(field, 1, mod_name)
}
}
fn (mut w Walker) collect_defs() {
for file in w.files {
mod_name := normalize_module_name(file.mod)
w.add_module_name(mod_name)
w.collect_file_imports(file.name, file.imports)
for stmt in file.stmts {
w.collect_def_stmt(stmt, mod_name, file.name, ast.FlatNodeId(-1))
}
}
w.collect_const_fn_value_aliases()
}
// collect_defs_from_flat is the flat-input analogue of collect_defs. It
// walks each file's top-level statements through Cursor and only decodes
// the decls collect_def_stmt actually consumes (struct/enum/interface/
// type/global/fn). Imports are read directly from cursor fields
// (name / extra_str alias) so the []ImportStmt slice is never allocated.
// The shared collect_def_stmt dispatch keeps the flat path bit-identical
// to the legacy path on the same input.
fn (mut w Walker) collect_defs_from_flat(flat &ast.FlatAst) {
for fi in 0 .. flat.files.len {
fc := flat.file_cursor(fi)
mod_name := normalize_module_name(fc.mod())
w.add_module_name(mod_name)
imports_list := fc.imports()
for i in 0 .. imports_list.len() {
imp_cur := imports_list.at(i)
name := imp_cur.name()
w.add_module_name(name)
alias := imp_cur.extra_str()
import_mod := if imp_cur.flag(ast.flag_is_aliased) {
name.all_after_last('.')
} else {
alias
}
for si in 0 .. imp_cur.edge_count() {
symbol := imp_cur.edge(si)
if symbol.is_valid() && symbol.kind() == .expr_ident {
w.selective_import_fn_targets['${fc.name()}:${symbol.name()}'] = markused_imported_symbol_fn_name(import_mod,
symbol.name())
}
}
if alias != '' {
w.add_module_name(alias)
// Map alias to real module name for correct symbol resolution.
// e.g., 'import v2.ssa.optimize as ssa_optimize' → 'ssa_optimize' → 'optimize'
real_name := if name.contains('.') {
name.all_after_last('.')
} else {
name
}
w.module_alias_to_real[alias] = real_name
}
}
file_name := fc.name()
stmts_list := fc.stmts()
for i in 0 .. stmts_list.len() {
c := stmts_list.at(i)
match c.kind() {
.stmt_fn_decl {
fn_decl := c.fn_decl_signature()
if fn_decl.name == '' {
continue
}
has_body := c.list_at(3).len() > 0
info := FnInfo{
key: decl_key(mod_name, fn_decl, w.env)
mod: mod_name
file: file_name
decl: fn_decl
decl_id: c.id
has_body: has_body
}
w.fns << info
idx := w.fns.len - 1
w.index_fn(idx, info)
}
.stmt_struct_decl, .stmt_enum_decl, .stmt_interface_decl, .stmt_type_decl,
.stmt_global_decl {
w.collect_def_cursor(c, mod_name)
}
else {}
}
}
}
w.collect_const_fn_value_aliases_from_flat(flat)
}
fn const_fn_value_alias_key(mod_name string, name string) string {
return '${normalize_module_name(mod_name)}:${name}'
}
fn (mut w Walker) collect_const_fn_value_aliases() {
for file in w.files {
mod_name := normalize_module_name(file.mod)
prev_fn_file := w.cur_fn_file
w.cur_fn_file = file.name
for stmt in file.stmts {
if !stmt_ok(stmt) {
continue
}
if stmt is ast.ConstDecl {
for field in stmt.fields {
if field.name == '' {
continue
}
if field.value is ast.Ident
&& w.ident_resolves_to_fn_value(field.value.name, mod_name) {
w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, field.name)] = field.value.name
} else if field.value is ast.SelectorExpr {
target := w.selector_fn_value_target(field.value)
if target != '' {
w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, field.name)] = target
}
}
}
}
}
w.cur_fn_file = prev_fn_file
}
}
fn (mut w Walker) collect_const_fn_value_aliases_from_flat(flat &ast.FlatAst) {
for fi in 0 .. flat.files.len {
fc := flat.file_cursor(fi)
mod_name := normalize_module_name(fc.mod())
prev_fn_file := w.cur_fn_file
w.cur_fn_file = fc.name()
stmts_list := fc.stmts()
for i in 0 .. stmts_list.len() {
c := stmts_list.at(i)
if c.kind() != .stmt_const_decl {
continue
}
fields := c.list_at(0)
for j in 0 .. fields.len() {
field := fields.at(j)
name := field.name()
if name == '' {
continue
}
value_c := field.edge(0)
if !value_c.is_valid() {
continue
}
if value_c.kind() == .expr_ident
&& w.ident_resolves_to_fn_value(value_c.name(), mod_name) {
w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, name)] =
value_c.name()
} else if value_c.kind() == .expr_selector {
target := w.selector_fn_value_target_cursor(value_c)
if target != '' {
w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, name)] = target
}
}
}
}
w.cur_fn_file = prev_fn_file
}
}
fn (mut w Walker) collect_imports(imports []ast.ImportStmt) {
for imp in imports {
w.add_module_name(imp.name)
if imp.alias != '' {
w.add_module_name(imp.alias)
// Map alias to real module name for correct symbol resolution.
// e.g., 'import v2.ssa.optimize as ssa_optimize' → 'ssa_optimize' → 'optimize'
real_name := if imp.name.contains('.') {
imp.name.all_after_last('.')
} else {
imp.name
}
w.module_alias_to_real[imp.alias] = real_name
}
}
}
fn markused_imported_symbol_fn_name(module_name string, name string) string {
if module_name == '' || module_name == 'main' {
return name
}
return '${module_name}__${name}'
}
fn selective_import_fn_key(file_name string, name string) string {
return '${file_name}:${name}'
}
fn (mut w Walker) collect_file_imports(file_name string, imports []ast.ImportStmt) {
w.collect_imports(imports)
for imp in imports {
if imp.symbols.len == 0 {
continue
}
import_mod := if imp.is_aliased { imp.name.all_after_last('.') } else { imp.alias }
for symbol in imp.symbols {
if symbol is ast.Ident {
w.selective_import_fn_targets[selective_import_fn_key(file_name, symbol.name)] = markused_imported_symbol_fn_name(import_mod,
symbol.name)
}
}
}
}
fn (mut w Walker) collect_def_stmt(stmt ast.Stmt, mod_name string, file_name string, decl_id ast.FlatNodeId) {
if !stmt_ok(stmt) {
return
}
match stmt {
ast.StructDecl {
w.add_type_name(mod_name, stmt.name)
w.add_struct_field_receivers(mod_name, stmt)
w.add_struct_embedded_receivers(mod_name, stmt)
w.add_struct_fn_fields(mod_name, stmt)
}
ast.EnumDecl {
w.add_type_name(mod_name, stmt.name)
}
ast.InterfaceDecl {
w.add_type_name(mod_name, stmt.name)
w.add_interface_decl(mod_name, stmt)
}
ast.TypeDecl {
w.add_type_name(mod_name, stmt.name)
}
ast.GlobalDecl {
w.add_global_interface_names(mod_name, stmt)
}
ast.FnDecl {
if stmt.name == '' {
return
}
info := FnInfo{
key: decl_key(mod_name, stmt, w.env)
mod: mod_name
file: file_name
decl: stmt
decl_id: decl_id
has_body: stmt.stmts.len > 0
}
w.fns << info
idx := w.fns.len - 1
w.index_fn(idx, info)
}
else {}
}
}
fn (mut w Walker) collect_def_cursor(c ast.Cursor, mod_name string) {
if !c.is_valid() {
return
}
match c.kind() {
.stmt_struct_decl {
w.add_type_name(mod_name, c.name())
w.add_struct_field_receivers_cursor(mod_name, c)
w.add_struct_embedded_receivers_cursor(mod_name, c)
w.add_struct_fn_fields_cursor(mod_name, c)
}
.stmt_enum_decl, .stmt_type_decl {
w.add_type_name(mod_name, c.name())
}
.stmt_interface_decl {
w.add_interface_decl_cursor(mod_name, c)
}
.stmt_global_decl {
w.add_global_interface_names_cursor(mod_name, c)
}
else {}
}
}
fn (mut w Walker) seed_roots() bool {
mut has_root := false
for i, info in w.fns {
if is_main_root(info) {
w.mark_fn(i)
has_root = true
}
}
if has_root {
if w.opts.minimal_runtime_roots {
w.seed_minimal_runtime_roots()
} else {
w.seed_generic_specialization_roots()
w.seed_codegen_required_roots()
// Also seed module init() functions (called from synthesized main)
for i, info in w.fns {
if is_module_init(info) {
w.mark_fn(i)
}
}
w.seed_top_level_initializer_roots()
w.seed_drop_method_roots()
}
return true
}
for i, info in w.fns {
if is_test_root(info) || is_module_init(info) {
w.mark_fn(i)
has_root = true
}
}
if has_root {
if w.opts.minimal_runtime_roots {
w.seed_minimal_runtime_roots()
} else {
w.seed_generic_specialization_roots()
w.seed_codegen_required_roots()
w.seed_top_level_initializer_roots()
w.seed_drop_method_roots()
}
}
return has_root
}
fn (mut w Walker) seed_minimal_runtime_roots() {
}
fn (mut w Walker) seed_roots_from_flat(flat &ast.FlatAst) bool {
mut has_root := false
for i, info in w.fns {
if is_main_root(info) {
w.mark_fn(i)
has_root = true
}
}
if has_root {
if !w.opts.minimal_runtime_roots {
w.seed_generic_specialization_roots()
w.seed_codegen_required_roots()
// Also seed module init() functions (called from synthesized main)
for i, info in w.fns {
if is_module_init(info) {
w.mark_fn(i)
}
}
w.seed_top_level_initializer_roots_from_flat(flat)
w.seed_drop_method_roots()
}
return true
}
for i, info in w.fns {
if is_test_root(info) || is_module_init(info) {
w.mark_fn(i)
has_root = true
}
}
if has_root {
if !w.opts.minimal_runtime_roots {
w.seed_generic_specialization_roots()
w.seed_codegen_required_roots()
w.seed_top_level_initializer_roots_from_flat(flat)
w.seed_drop_method_roots()
}
}
return has_root
}
fn (mut w Walker) seed_codegen_required_roots() {
for i, info in w.fns {
if w.is_codegen_required_root(info) {
w.mark_fn(i)
}
}
}
// seed_drop_method_roots marks every `Type.drop` method as live for the
// types that the ownership checker recorded in `env.drop_at_fn_exit`. The
// cleanc backend will emit synthetic `Type__drop(&var)` calls at fn-exit
// for those bindings; without seeding the methods here, dead-code elim
// would strip the bodies and leave the linker with unresolved symbols.
fn (mut w Walker) seed_drop_method_roots() {
if w.env == unsafe { nil } {
return
}
mut seen := map[string]bool{}
for _, entries in w.env.drop_at_fn_exit {
for entry in entries {
if entry.type_name.len == 0 || entry.type_name in seen {
continue
}
seen[entry.type_name] = true
w.mark_method_name('drop', [entry.type_name])
}
}
}
fn (w &Walker) is_codegen_required_root(info FnInfo) bool {
decl := info.decl
if !w.opts.minimal_runtime_roots && should_always_emit_for_markused(info.file) {
return true
}
if !w.opts.minimal_runtime_roots && info.mod == 'builtin' && decl.name == 'print_backtrace' {
return true
}
if info.mod == 'json2' && decl.name == 'enum_uses_json_as_number' {
return true
}
if info.mod == 'time' && decl.is_method && decl.receiver.typ is ast.Ident
&& decl.receiver.typ.name == 'Duration' {
return true
}
if info.mod == 'sync' {
if decl.name in ['try_pop_priv', 'try_push_priv', 'new_channel_st', 'new_spin_lock'] {
return true
}
if decl.is_method && decl.name == 'try_wait' {
return true
}
}
if is_builtin_map_file(info.file) && should_keep_builtin_map_decl(decl) {
return true
}
if is_builtin_string_file(info.file) && should_keep_builtin_string_decl(decl) {
return true
}
if is_builtin_array_file(info.file) && should_keep_builtin_array_decl(decl) {
return true
}
if decl.name == 'str' || decl.name.ends_with('__str') {
return true
}
if decl.name.starts_with('__sort_cmp_') {
return true
}
if decl.is_method {
if decl.name in ['+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>='] {
return true
}
if decl.receiver.typ is ast.Type && decl.receiver.typ is ast.ArrayType {
return true
}
}
return false
}
pub fn should_always_emit_for_markused(path string) bool {
if path.ends_with('.vh') {
return true
}
return is_builtin_runtime_keep_file(path)
}
pub fn is_builtin_runtime_keep_file(path string) bool {
normalized := path.replace('\\', '/')
return normalized.ends_with('vlib/builtin/map.c.v')
|| normalized.ends_with('vlib/builtin/builtin.c.v')
|| normalized.ends_with('vlib/builtin/builtin.v')
|| normalized.ends_with('vlib/builtin/cfns_wrapper.c.v')
|| normalized.ends_with('vlib/builtin/allocation.c.v')
|| normalized.ends_with('vlib/builtin/prealloc.c.v')
|| normalized.ends_with('vlib/builtin/panicing.c.v')
|| normalized.ends_with('vlib/builtin/chan_option_result.v')
|| normalized.ends_with('vlib/builtin/int.v') || normalized.ends_with('vlib/builtin/rune.v')
|| normalized.ends_with('vlib/builtin/float.c.v')
|| normalized.ends_with('vlib/builtin/utf8.v')
|| normalized.ends_with('vlib/builtin/utf8.c.v')
|| normalized.ends_with('vlib/strings/builder.c.v')
|| normalized.ends_with('vlib/strconv/utilities.v')
|| normalized.ends_with('vlib/strconv/utilities.c.v')
|| normalized.ends_with('vlib/strconv/ftoa.c.v')
|| normalized.ends_with('vlib/strconv/f32_str.c.v')
|| normalized.ends_with('vlib/strconv/f64_str.c.v')
|| normalized.ends_with('vlib/math/bits/bits.v')
|| normalized.ends_with('vlib/math/bits/bits.c.v')
|| normalized.ends_with('vlib/sokol/memory/memory.c.v')
}
pub fn is_builtin_map_file(path string) bool {
normalized := path.replace('\\', '/')
return normalized.ends_with('vlib/builtin/map.v')
}
pub fn should_keep_builtin_map_decl(decl ast.FnDecl) bool {
base_keep := decl.name in ['new_map', 'move', 'clear', 'key_to_index', 'meta_less',
'meta_greater', 'ensure_extra_metas', 'ensure_extra_metas_grow', 'set', 'expand', 'rehash',
'reserve', 'cached_rehash', 'get_and_set', 'get', 'get_check', 'exists', 'delete', 'keys',
'values', 'clone', 'free', 'key', 'value', 'has_index', 'zeros_to_end', 'new_dense_array']
return base_keep || decl.name.starts_with('map_eq_') || decl.name.starts_with('map_clone_')
|| decl.name.starts_with('map_free_')
}
pub fn is_builtin_string_file(path string) bool {
normalized := path.replace('\\', '/')
return normalized.ends_with('vlib/builtin/string.v')
}
pub fn should_keep_builtin_string_decl(decl ast.FnDecl) bool {
return decl.name in ['eq', 'plus', 'plus_two', 'substr', 'substr_unsafe', 'repeat', 'free',
'vstring', 'vstring_with_len', 'vstring_literal', 'vstring_literal_with_len', 'runes',
'join', 'compare_strings', 'compare_strings_by_len', 'compare_lower_strings']
}
pub fn is_builtin_array_file(path string) bool {
normalized := path.replace('\\', '/')
return normalized.ends_with('vlib/builtin/array.v')
}
pub fn should_keep_builtin_array_decl(decl ast.FnDecl) bool {
return decl.name in [
'__new_array',
'__new_array_with_default',
'__new_array_with_multi_default',
'__new_array_with_array_default',
'__new_array_with_map_default',
'new_array_from_c_array',
'new_array_from_c_array_no_alloc',
'new_array_from_array_and_c_array',
'ensure_cap',
'repeat',
'repeat_to_depth',
'insert',
'insert_many',
'prepend',
'prepend_many',
'delete',
'delete_many',
'clear',
'reset',
'trim',
'drop',
'get_unsafe',
'get',
'get_with_check',
'first',
'last',
'pop_left',
'pop',
'delete_last',
'slice',
'slice_ni',
'contains',
'clone_static_to_depth',
'clone',
'clone_to_depth',
'set_unsafe',
'set',
'push',
'push_many',
'reverse_in_place',
'reverse',
'free',
'sort',
'sort_with_compare',
'sorted_with_compare',
'copy',
'write_u8',
'write_rune',
'write_string',
'write_ptr',
'grow_cap',
'grow_len',
'pointers',
'panic_on_negative_len',
'panic_on_negative_cap',
]
}
fn (mut w Walker) seed_top_level_initializer_roots() {
for file in w.files {
mod_name := normalize_module_name(file.mod)
for stmt in file.stmts {
if !stmt_ok(stmt) {
continue
}
match stmt {
ast.ConstDecl, ast.GlobalDecl {
w.walk_stmt(stmt, mod_name)
}
else {}
}
}
}
}
fn (mut w Walker) seed_top_level_initializer_roots_from_flat(flat &ast.FlatAst) {
for i in 0 .. flat.files.len {
fc := flat.file_cursor(i)
mod_name := normalize_module_name(fc.mod())
stmts := fc.stmts()
for j in 0 .. stmts.len() {
stmt_c := stmts.at(j)
match stmt_c.kind() {
.stmt_const_decl, .stmt_global_decl {
w.walk_stmt_cursor(stmt_c, mod_name)
}
else {}
}
}
}
}
fn generic_base_name_from_key(key string) string {
mut name := key
bracket_idx := name.index_u8(`[`)
if bracket_idx > 0 {
name = name[..bracket_idx]
}
dot_idx := name.last_index_u8(`.`)
if dot_idx > 0 && dot_idx < name.len - 1 {
name = name[dot_idx + 1..]
}
return strip_generic_specialization_suffix(name)
}
fn (mut w Walker) seed_generic_specialization_roots() {
if w.env == unsafe { nil } {
return
}
for key, _ in w.env.generic_types {
base_name := generic_base_name_from_key(key)
if base_name == '' {
continue
}
w.mark_lookup('fn:${base_name}')
w.mark_lookup('mname:${base_name}')
}
}
fn is_main_root(info FnInfo) bool {
return !is_method_decl(info.decl) && info.mod == 'main' && info.decl.name == 'main'
}
fn is_test_root(info FnInfo) bool {
return !is_method_decl(info.decl) && info.decl.name.starts_with('test_')
&& info.decl.typ.params.len == 0
}
fn is_method_decl(decl ast.FnDecl) bool {
return decl.is_method || decl.is_static
}
fn is_module_init(info FnInfo) bool {
return !is_method_decl(info.decl) && (info.decl.name == 'init'
|| info.decl.name == 'deinit'
|| info.decl.name.starts_with('__v_init_consts_'))
}
fn normalize_module_name(module_name string) string {
if module_name == '' {
return 'main'
}
return module_name
}
fn sanitize_receiver_name(name string) string {
if !string_ok(name) {
return ''
}
mut out := name.trim_space()
for out.len > 0 && (out[0] == `&` || out[0] == `?` || out[0] == `!`) {
out = out[1..]
}
return out
}
fn maybe_trim_module_prefix(mod_name string, name string) string {
if mod_name == '' || mod_name == 'main' {
return name
}
prefix := '${mod_name}__'
if name.starts_with(prefix) {
return name[prefix.len..]
}
return name
}
fn add_unique_string(mut out []string, value string) {
if value == '' {
return
}
for existing in out {
if existing == value {
return
}
}
out << value
}
fn add_unique_int(mut out []int, value int) {
for existing in out {
if existing == value {
return
}
}
out << value
}
fn add_unique_fn_name(mut out []string, value string) {
if value == '' {
return
}
for existing in out {
if existing == value {
return
}
}
out << value
}
fn type_name_candidates_from_type(mod_name string, typ types.Type) []string {
mut out := []string{}
names := [typ.name(), typ.base_type().name()]
for raw_name in names {
name := sanitize_receiver_name(raw_name)
add_unique_string(mut out, name)
add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
if name.contains('__') {
add_unique_string(mut out, name.all_after_last('__'))
} else if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, '${mod_name}__${name}')
}
// For array types like []Attribute, also add Array_Attribute form.
if name.starts_with('[]') {
elem := name[2..]
add_unique_string(mut out, 'Array_${elem}')
add_unique_string(mut out, maybe_trim_module_prefix(mod_name, 'Array_${elem}'))
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, 'Array_${mod_name}__${elem}')
}
}
}
return out
}
fn type_is_fn_value(typ types.Type) bool {
return type_is_fn_value_with_depth(typ, 0)
}
fn type_is_fn_value_with_depth(typ types.Type, depth int) bool {
if depth > 16 || !type_ok(typ) {
return false
}
match typ {
types.FnType {
return true
}
types.Alias {
return type_is_fn_value_with_depth(typ.base_type, depth + 1)
}
else {
return false
}
}
}
fn (w &Walker) type_name_is_fn_value(name string, mod_name string) bool {
if w.env == unsafe { nil } || name == '' || !string_ok(name) {
return false
}
mut type_names := []string{}
add_unique_string(mut type_names, name)
add_unique_string(mut type_names, maybe_trim_module_prefix(mod_name, name))
if name.contains('__') {
add_unique_string(mut type_names, name.all_after_last('__'))
} else if mod_name != '' && mod_name != 'main' {
add_unique_string(mut type_names, '${mod_name}__${name}')
}
mut scope_names := []string{}
add_unique_string(mut scope_names, mod_name)
if name.contains('__') {
add_unique_string(mut scope_names, name.all_before('__'))
}
add_unique_string(mut scope_names, 'builtin')
for scope_name in scope_names {
if scope_name == '' {
continue
}
if scope := w.env.get_scope(scope_name) {
for type_name in type_names {
if typ := scope.lookup_type_parent(type_name, 0) {
if type_is_fn_value(typ) {
return true
}
}
}
}
}
return false
}
fn (w &Walker) type_expr_is_fn_value(expr ast.Expr, mod_name string) bool {
match expr {
ast.Ident {
return w.type_name_is_fn_value(expr.name, mod_name)
}
ast.SelectorExpr {
if expr.lhs is ast.Ident {
lhs_name := expr.lhs.name
real_mod := w.module_alias_to_real[lhs_name] or { lhs_name }
return w.type_name_is_fn_value(expr.rhs.name, real_mod)
|| w.type_name_is_fn_value('${real_mod}__${expr.rhs.name}', real_mod)
}
}
ast.PrefixExpr {
if expr.op == .amp {
return w.type_expr_is_fn_value(expr.expr, mod_name)
}
}
ast.ModifierExpr {
return w.type_expr_is_fn_value(expr.expr, mod_name)
}
ast.GenericArgs {
return w.type_expr_is_fn_value(expr.lhs, mod_name)
}
ast.GenericArgOrIndexExpr {
return w.type_expr_is_fn_value(expr.lhs, mod_name)
}
ast.Type {
match expr {
ast.FnType {
return true
}
ast.PointerType {
return w.type_expr_is_fn_value(expr.base_type, mod_name)
}
ast.GenericType {
return w.type_expr_is_fn_value(expr.name, mod_name)
}
else {}
}
}
else {}
}
return false
}
fn (w &Walker) expr_has_fn_value_type(expr ast.Expr) bool {
if w.env == unsafe { nil } || !expr_ok(expr) {
return false
}
pos := expr.pos()
if !pos.is_valid() {
return false
}
if typ := w.env.get_expr_type(pos.id) {
return type_is_fn_value(typ)
}
return false
}
fn (w &Walker) cursor_has_fn_value_type(c ast.Cursor) bool {
if w.env == unsafe { nil } || !c.is_valid() {
return false
}
pos := c.pos()
if !pos.is_valid() {
return false
}
if typ := w.env.get_expr_type(pos.id) {
return type_is_fn_value(typ)
}
return false
}
fn (w &Walker) type_expr_is_fn_value_cursor(c ast.Cursor, mod_name string) bool {
if !c.is_valid() {
return false
}
if w.cursor_has_fn_value_type(c) {
return true
}
match c.kind() {
.expr_ident {
return w.type_name_is_fn_value(c.name(), mod_name)
}
.expr_selector {
lhs_c := c.edge(0)
rhs_c := c.edge(1)
if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && rhs_c.is_valid() {
real_mod := w.module_alias_to_real[lhs_c.name()] or { lhs_c.name() }
return w.type_name_is_fn_value(rhs_c.name(), real_mod)
|| w.type_name_is_fn_value('${real_mod}__${rhs_c.name()}', real_mod)
}
}
.expr_prefix, .expr_modifier, .typ_pointer, .typ_generic {
return w.type_expr_is_fn_value_cursor(c.edge(0), mod_name)
}
.typ_fn {
return true
}
else {}
}
return false
}
fn add_receiver_name_candidates(mut out []string, mod_name string, raw_name string) {
name := sanitize_receiver_name(raw_name)
if name == '' {
return
}
add_unique_string(mut out, name)
add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
if name.contains('__') {
add_unique_string(mut out, name.all_after_last('__'))
} else if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, '${mod_name}__${name}')
}
}
fn receiver_type_expr_name(expr ast.Expr) string {
match expr {
ast.Ident {
return expr.name
}
ast.SelectorExpr {
return expr.rhs.name
}
ast.PrefixExpr {
if expr.op == .amp {
return receiver_type_expr_name(expr.expr)
}
}
ast.ModifierExpr {
return receiver_type_expr_name(expr.expr)
}
ast.GenericArgs {
return receiver_type_expr_name(expr.lhs)
}
ast.GenericArgOrIndexExpr {
return receiver_type_expr_name(expr.lhs)
}
ast.Type {
match expr {
ast.PointerType {
return receiver_type_expr_name(expr.base_type)
}
ast.ArrayType {
elem_name := receiver_type_expr_name(expr.elem_type)
if elem_name != '' {
return 'Array_${elem_name}'
}
}
ast.ArrayFixedType {
elem_name := receiver_type_expr_name(expr.elem_type)
len_name := receiver_fixed_array_len_name(expr.len)
if elem_name != '' && len_name != '' {
return 'Array_fixed_${elem_name}_${len_name}'
}
}
ast.GenericType {
return receiver_type_expr_name(expr.name)
}
else {}
}
}
else {}
}
return ''
}
fn receiver_fixed_array_len_name(expr ast.Expr) string {
match expr {
ast.BasicLiteral {
return expr.value
}
ast.Ident {
return expr.name
}
else {
return expr.name()
}
}
}
fn type_expr_receiver_candidates(mod_name string, expr ast.Expr) []string {
mut out := []string{}
match expr {
ast.SelectorExpr {
if expr.lhs is ast.Ident {
add_receiver_name_candidates(mut out, mod_name, expr.rhs.name)
add_unique_string(mut out, '${expr.lhs.name}__${expr.rhs.name}')
}
}
ast.PrefixExpr {
if expr.op == .amp {
return type_expr_receiver_candidates(mod_name, expr.expr)
}
}
ast.ModifierExpr {
return type_expr_receiver_candidates(mod_name, expr.expr)
}
ast.GenericArgs {
return type_expr_receiver_candidates(mod_name, expr.lhs)
}
ast.GenericArgOrIndexExpr {
return type_expr_receiver_candidates(mod_name, expr.lhs)
}
ast.Type {
match expr {
ast.PointerType {
return type_expr_receiver_candidates(mod_name, expr.base_type)
}
ast.GenericType {
return type_expr_receiver_candidates(mod_name, expr.name)
}
else {}
}
}
else {}
}
name := receiver_type_expr_name(expr)
if name != '' {
add_receiver_name_candidates(mut out, mod_name, name)
}
return out
}
fn type_expr_receiver_candidates_cursor(c ast.Cursor, mod_name string) []string {
mut out := []string{}
if !c.is_valid() {
return out
}
match c.kind() {
.expr_selector {
lhs_c := c.edge(0)
rhs_c := c.edge(1)
if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && rhs_c.is_valid() {
add_receiver_name_candidates(mut out, mod_name, rhs_c.name())
add_unique_string(mut out, '${lhs_c.name()}__${rhs_c.name()}')
}
}
.expr_prefix, .expr_modifier, .expr_generic_args, .expr_generic_arg_or_index, .typ_pointer,
.typ_generic {
return type_expr_receiver_candidates_cursor(c.edge(0), mod_name)
}
.expr_ident {
add_receiver_name_candidates(mut out, mod_name, c.name())
}
else {}
}
name := receiver_type_expr_name_cursor(c)
if name != '' {
add_receiver_name_candidates(mut out, mod_name, name)
}
return out
}
fn receiver_type_expr_name_cursor(c ast.Cursor) string {
if !c.is_valid() {
return ''
}
match c.kind() {
.expr_ident {
return c.name()
}
.expr_selector {
rhs := c.edge(1)
if rhs.is_valid() {
return rhs.name()
}
}
.expr_prefix, .expr_modifier, .expr_generic_args, .expr_generic_arg_or_index, .typ_pointer,
.typ_generic {
return receiver_type_expr_name_cursor(c.edge(0))
}
.typ_array {
elem_name := receiver_type_expr_name_cursor(c.edge(0))
if elem_name != '' {
return 'Array_${elem_name}'
}
}
.typ_array_fixed {
elem_name := receiver_type_expr_name_cursor(c.edge(1))
len_name := receiver_fixed_array_len_name_cursor(c.edge(0))
if elem_name != '' && len_name != '' {
return 'Array_fixed_${elem_name}_${len_name}'
}
}
else {}
}
return ''
}
fn receiver_fixed_array_len_name_cursor(c ast.Cursor) string {
if !c.is_valid() {
return ''
}
if c.kind() == .expr_basic_literal || c.kind() == .expr_ident {
return c.name()
}
return c.name()
}
fn receiver_names_from_decl(mod_name string, decl ast.FnDecl, env &types.Environment) []string {
mut out := []string{}
add_receiver_name_candidates(mut out, mod_name, receiver_type_expr_name(decl.receiver.typ))
match decl.receiver.typ {
ast.Ident {
name := sanitize_receiver_name(decl.receiver.typ.name)
add_unique_string(mut out, name)
add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, '${mod_name}__${name}')
}
}
ast.PrefixExpr {
if decl.receiver.typ.expr is ast.Ident {
name := sanitize_receiver_name(decl.receiver.typ.expr.name)
add_unique_string(mut out, name)
add_unique_string(mut out, maybe_trim_module_prefix(mod_name, name))
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, '${mod_name}__${name}')
}
}
}
else {}
}
// Method on []ElemType — receiver name is Array_ElemType.
// Use the string name which includes [] prefix for array types.
recv_name := decl.receiver.typ.name()
if recv_name.starts_with('[]') {
elem_name := recv_name[2..]
if elem_name.len > 0 {
arr_name := 'Array_${elem_name}'
add_unique_string(mut out, arr_name)
add_unique_string(mut out, maybe_trim_module_prefix(mod_name, arr_name))
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, 'Array_${mod_name}__${elem_name}')
}
}
}
pos := decl.receiver.typ.pos()
if env != unsafe { nil } && pos.is_valid() {
if receiver_type := env.get_expr_type(pos.id) {
for name in type_name_candidates_from_type(mod_name, receiver_type) {
add_unique_string(mut out, name)
}
}
}
return out
}
fn receiver_primary_name(mod_name string, decl ast.FnDecl, env &types.Environment) string {
names := receiver_names_from_decl(mod_name, decl, env)
if names.len > 0 {
return names[0]
}
return 'unknown'
}
fn normalize_method_name(name string) string {
if !string_ok(name) {
return ''
}
return match name {
'+' { 'plus' }
'-' { 'minus' }
'*' { 'mul' }
'/' { 'div' }
'%' { 'mod' }
'==' { 'eq' }
'!=' { 'ne' }
'<' { 'lt' }
'>' { 'gt' }
'<=' { 'le' }
'>=' { 'ge' }
else { name }
}
}
fn (w &Walker) lookup_fn_scope(info FnInfo) &types.Scope {
if w.env == unsafe { nil } {
return unsafe { nil }
}
decl := info.decl
mod_name := info.mod
if !is_method_decl(decl) {
if scope := w.env.get_fn_scope(mod_name, decl.name) {
return scope
}
return unsafe { nil }
}
receivers := receiver_names_from_decl(mod_name, decl, w.env)
for recv in receivers {
if scope := w.env.get_fn_scope(mod_name, '${recv}__${decl.name}') {
return scope
}
}
return unsafe { nil }
}
fn (mut w Walker) add_lookup(key string, idx int) {
if key == '' {
return
}
if key !in w.lookup {
w.lookup[key] = [idx]
return
}
mut values := w.lookup[key]
add_unique_int(mut values, idx)
w.lookup[key] = values
}
fn (mut w Walker) index_fn(idx int, info FnInfo) {
decl := info.decl
mod_name := info.mod
if is_method_decl(decl) {
mut method_names := []string{}
add_unique_string(mut method_names, decl.name)
add_unique_string(mut method_names, normalize_method_name(decl.name))
receivers := receiver_names_from_decl(mod_name, decl, w.env)
for method_name in method_names {
w.add_lookup('mname:${method_name}', idx)
for receiver_name in receivers {
w.add_method_receiver(receiver_name, idx)
if receiver_name.contains('__') {
w.add_method_receiver(receiver_name.all_after_last('__'), idx)
}
w.add_lookup('meth:${receiver_name}:${method_name}', idx)
w.add_lookup('fn:${receiver_name}__${method_name}', idx)
w.add_lookup('mod:${mod_name}:${receiver_name}__${method_name}', idx)
}
}
return
}
w.add_lookup('fn:${decl.name}', idx)
w.add_lookup('mod:${mod_name}:${decl.name}', idx)
if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
w.add_lookup('fn:${mod_name}__${decl.name}', idx)
}
}
fn (mut w Walker) add_module_name(name string) {
if name == '' {
return
}
w.module_names[name] = true
if name.contains('.') {
w.module_names[name.all_after_last('.')] = true
}
}
fn (mut w Walker) add_type_name(mod_name string, name string) {
if name == '' {
return
}
w.type_names[name] = true
trimmed := maybe_trim_module_prefix(mod_name, name)
w.type_names[trimmed] = true
if mod_name != '' && mod_name != 'main' {
w.type_names['${mod_name}__${trimmed}'] = true
}
}
fn (mut w Walker) add_interface_name(mod_name string, name string) {
if name == '' {
return
}
w.interface_type_names[name] = true
trimmed := maybe_trim_module_prefix(mod_name, name)
w.interface_type_names[trimmed] = true
if mod_name != '' && mod_name != 'main' {
w.interface_type_names['${mod_name}__${trimmed}'] = true
}
}
fn (mut w Walker) add_interface_decl(mod_name string, decl ast.InterfaceDecl) {
w.add_interface_name(mod_name, decl.name)
mut method_names := []string{}
for field in decl.fields {
if field.is_interface_method && field.typ is ast.Type && field.typ is ast.FnType {
add_unique_fn_name(mut method_names, field.name)
add_unique_fn_name(mut method_names, normalize_method_name(field.name))
}
}
mut embedded_names := []string{}
for embedded in decl.embedded {
for embedded_name in type_expr_receiver_candidates(mod_name, embedded) {
add_unique_string(mut embedded_names, embedded_name)
}
}
mut iface_names := []string{}
add_unique_string(mut iface_names, decl.name)
add_unique_string(mut iface_names, maybe_trim_module_prefix(mod_name, decl.name))
if mod_name != '' && mod_name != 'main' {
trimmed := maybe_trim_module_prefix(mod_name, decl.name)
add_unique_string(mut iface_names, '${mod_name}__${trimmed}')
}
for iface_name in iface_names {
if method_names.len > 0 {
if iface_name in w.interface_method_names {
mut existing := w.interface_method_names[iface_name]
for method_name in method_names {
add_unique_fn_name(mut existing, method_name)
}
w.interface_method_names[iface_name] = existing
} else {
w.interface_method_names[iface_name] = method_names.clone()
}
}
if embedded_names.len > 0 {
mut existing := w.interface_embedded_names[iface_name] or { []string{} }
for embedded_name in embedded_names {
add_unique_string(mut existing, embedded_name)
}
w.interface_embedded_names[iface_name] = existing
}
}
}
fn (mut w Walker) add_interface_decl_cursor(mod_name string, decl ast.Cursor) {
w.add_interface_name(mod_name, decl.name())
mut method_names := []string{}
fields := decl.list_at(3)
for i in 0 .. fields.len() {
field := fields.at(i)
typ := field.edge(0)
if field.flag(ast.flag_field_is_interface_method) && typ.is_valid() && typ.kind() == .typ_fn {
add_unique_fn_name(mut method_names, field.name())
add_unique_fn_name(mut method_names, normalize_method_name(field.name()))
}
}
mut embedded_names := []string{}
embedded := decl.list_at(2)
for i in 0 .. embedded.len() {
for embedded_name in type_expr_receiver_candidates_cursor(embedded.at(i), mod_name) {
add_unique_string(mut embedded_names, embedded_name)
}
}
mut iface_names := []string{}
add_unique_string(mut iface_names, decl.name())
add_unique_string(mut iface_names, maybe_trim_module_prefix(mod_name, decl.name()))
if mod_name != '' && mod_name != 'main' {
trimmed := maybe_trim_module_prefix(mod_name, decl.name())
add_unique_string(mut iface_names, '${mod_name}__${trimmed}')
}
for iface_name in iface_names {
if method_names.len > 0 {
if iface_name in w.interface_method_names {
mut existing := w.interface_method_names[iface_name]
for method_name in method_names {
add_unique_fn_name(mut existing, method_name)
}
w.interface_method_names[iface_name] = existing
} else {
w.interface_method_names[iface_name] = method_names.clone()
}
}
if embedded_names.len > 0 {
mut existing := w.interface_embedded_names[iface_name] or { []string{} }
for embedded_name in embedded_names {
add_unique_string(mut existing, embedded_name)
}
w.interface_embedded_names[iface_name] = existing
}
}
}
fn (mut w Walker) add_struct_field_receivers(mod_name string, decl ast.StructDecl) {
mut struct_names := []string{}
add_receiver_name_candidates(mut struct_names, mod_name, decl.name)
for field in decl.fields {
field_receivers := type_expr_receiver_candidates(mod_name, field.typ)
if field_receivers.len == 0 {
continue
}
for struct_name in struct_names {
key := '${struct_name}:${field.name}'
mut existing := w.struct_field_receivers[key] or { []string{} }
for receiver in field_receivers {
add_unique_string(mut existing, receiver)
}
w.struct_field_receivers[key] = existing
}
}
}
fn (mut w Walker) add_struct_field_receivers_cursor(mod_name string, decl ast.Cursor) {
mut struct_names := []string{}
add_receiver_name_candidates(mut struct_names, mod_name, decl.name())
fields := decl.list_at(4)
for i in 0 .. fields.len() {
field := fields.at(i)
field_receivers := type_expr_receiver_candidates_cursor(field.edge(0), mod_name)
if field_receivers.len == 0 {
continue
}
for struct_name in struct_names {
key := '${struct_name}:${field.name()}'
mut existing := w.struct_field_receivers[key] or { []string{} }
for receiver in field_receivers {
add_unique_string(mut existing, receiver)
}
w.struct_field_receivers[key] = existing
}
}
}
fn (mut w Walker) add_struct_embedded_receivers(mod_name string, decl ast.StructDecl) {
if decl.embedded.len == 0 {
return
}
mut struct_names := []string{}
add_receiver_name_candidates(mut struct_names, mod_name, decl.name)
for embedded in decl.embedded {
embedded_receivers := type_expr_receiver_candidates(mod_name, embedded)
if embedded_receivers.len == 0 {
continue
}
for struct_name in struct_names {
mut existing := w.struct_embedded_receivers[struct_name] or { []string{} }
for receiver in embedded_receivers {
add_unique_string(mut existing, receiver)
}
w.struct_embedded_receivers[struct_name] = existing
}
}
}
fn (mut w Walker) add_struct_embedded_receivers_cursor(mod_name string, decl ast.Cursor) {
embedded := decl.list_at(2)
if embedded.len() == 0 {
return
}
mut struct_names := []string{}
add_receiver_name_candidates(mut struct_names, mod_name, decl.name())
for i in 0 .. embedded.len() {
embedded_receivers := type_expr_receiver_candidates_cursor(embedded.at(i), mod_name)
if embedded_receivers.len == 0 {
continue
}
for struct_name in struct_names {
mut existing := w.struct_embedded_receivers[struct_name] or { []string{} }
for receiver in embedded_receivers {
add_unique_string(mut existing, receiver)
}
w.struct_embedded_receivers[struct_name] = existing
}
}
}
fn (mut w Walker) add_struct_fn_fields(mod_name string, decl ast.StructDecl) {
mut struct_names := []string{}
add_receiver_name_candidates(mut struct_names, mod_name, decl.name)
for field in decl.fields {
if !w.type_expr_is_fn_value(field.typ, mod_name) {
continue
}
for struct_name in struct_names {
w.struct_fn_fields['${struct_name}:${field.name}'] = true
}
}
}
fn (mut w Walker) add_struct_fn_fields_cursor(mod_name string, decl ast.Cursor) {
mut struct_names := []string{}
add_receiver_name_candidates(mut struct_names, mod_name, decl.name())
fields := decl.list_at(4)
for i in 0 .. fields.len() {
field := fields.at(i)
if !w.type_expr_is_fn_value_cursor(field.edge(0), mod_name) {
continue
}
for struct_name in struct_names {
w.struct_fn_fields['${struct_name}:${field.name()}'] = true
}
}
}
fn (w &Walker) init_field_is_fn_value(init_typ ast.Expr, field_name string, mod_name string) bool {
if field_name == '' {
return false
}
for struct_name in type_expr_receiver_candidates(mod_name, init_typ) {
if w.struct_fn_fields['${struct_name}:${field_name}'] {
return true
}
}
return false
}
fn (w &Walker) init_field_is_fn_value_cursor(init_typ ast.Cursor, field_name string, mod_name string) bool {
if field_name == '' {
return false
}
for struct_name in type_expr_receiver_candidates_cursor(init_typ, mod_name) {
if w.struct_fn_fields['${struct_name}:${field_name}'] {
return true
}
}
return false
}
fn (mut w Walker) add_global_interface_names(mod_name string, decl ast.GlobalDecl) {
for field in decl.fields {
iface_name := w.interface_name_from_type_expr(field.typ, mod_name)
if iface_name == '' {
continue
}
w.global_interface_names[field.name] = iface_name
if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
w.global_interface_names['${mod_name}__${field.name}'] = iface_name
}
}
}
fn (mut w Walker) add_global_interface_names_cursor(mod_name string, decl ast.Cursor) {
fields := decl.list_at(1)
for i in 0 .. fields.len() {
field := fields.at(i)
iface_name := w.interface_name_from_cursor(field.edge(0), mod_name)
if iface_name == '' {
continue
}
w.global_interface_names[field.name()] = iface_name
if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
w.global_interface_names['${mod_name}__${field.name()}'] = iface_name
}
}
}
fn (mut w Walker) add_method_receiver(receiver_name string, idx int) {
if receiver_name == '' {
return
}
if receiver_name !in w.methods_by_receiver {
w.methods_by_receiver[receiver_name] = [idx]
return
}
mut values := w.methods_by_receiver[receiver_name]
add_unique_int(mut values, idx)
w.methods_by_receiver[receiver_name] = values
}
fn (mut w Walker) mark_fn(idx int) {
if idx < 0 || idx >= w.fns.len {
return
}
info := w.fns[idx]
key := info.key
was_used := key in w.used_keys
if !was_used {
w.used_keys[key] = true
w.mark_generic_binding_receiver_methods(info)
}
if idx in w.queued_fn_indices {
return
}
w.queued_fn_indices[idx] = true
if info.has_body {
w.queue << idx
}
}
fn (mut w Walker) mark_generic_binding_receiver_methods(info FnInfo) {
if w.env == unsafe { nil } || info.decl.typ.generic_params.len == 0 {
return
}
for key, bindings_list in w.env.generic_types {
if generic_base_name_from_key(key) != info.decl.name {
continue
}
for bindings in bindings_list {
for _, concrete in bindings {
receivers := type_name_candidates_from_type(info.mod, concrete)
w.mark_all_methods_for_receivers(receivers)
}
}
}
}
fn (w &Walker) lookup_count(key string) int {
if !string_ok(key) {
return 0
}
if values := w.lookup[key] {
return values.len
}
return 0
}
fn (mut w Walker) mark_lookup(key string) {
if !string_ok(key) {
return
}
if values := w.lookup[key] {
for idx in values {
w.mark_fn(idx)
}
}
}
fn called_fn_name_candidates(name string) []string {
mut out := []string{}
if name == '' || !string_ok(name) {
return out
}
add_unique_string(mut out, name)
generic_base := strip_generic_specialization_suffix(name)
if generic_base != name {
add_unique_string(mut out, generic_base)
}
if name == 'builtin__new_array_from_c_array_noscan' {
add_unique_string(mut out, 'new_array_from_c_array')
return out
}
if name == 'builtin__new_array_from_array_and_c_array' {
add_unique_string(mut out, 'new_array_from_array_and_c_array')
return out
}
if name == 'builtin__array_push_noscan' || name == 'builtin__array__push_noscan' {
add_unique_string(mut out, 'array__push_noscan')
return out
}
if name.starts_with('builtin__') {
base := name['builtin__'.len..]
add_unique_string(mut out, base)
}
if name == 'gen_v__new_gen' {
add_unique_string(mut out, 'v__new_gen')
}
if name.starts_with('strings__Builder__') {
method_name := name.all_after_last('__')
add_unique_string(mut out, 'array__${method_name}')
}
return out
}
fn (mut w Walker) cached_called_fn_name_candidates(name string) []string {
if candidates := w.called_fn_name_candidate_cache[name] {
return candidates
}
candidates := called_fn_name_candidates(name)
w.called_fn_name_candidate_cache[name] = candidates
return candidates
}
fn strip_generic_specialization_suffix(name string) string {
if idx := name.index('_T_') {
if idx > 0 {
return name[..idx]
}
}
return name
}
fn should_mark_ident_as_fn(name string) bool {
if name == '' || !string_ok(name) {
return false
}
return name.starts_with('map_') || name.starts_with('map__') || name.starts_with('new_map')
|| name.starts_with('array__') || name.starts_with('IError_') || name.starts_with('Error__')
|| name.starts_with('MessageError__') || name.starts_with('_option_')
|| name.starts_with('_result_')
}
fn (mut w Walker) ident_resolves_to_fn_value(name string, mod_name string) bool {
if name == '' || !string_ok(name) || name == 'C' || name in w.module_names
|| w.is_cast_type_name(name) {
return false
}
if w.cur_fn_scope != unsafe { nil } {
if obj := w.cur_fn_scope.lookup_parent(name, 0) {
return obj is types.Fn
}
}
candidates := w.cached_called_fn_name_candidates(name)
if _ := w.exact_mangled_fn_lookup_key(name) {
return true
}
for candidate in candidates {
if _ := w.current_module_fn_lookup_key(candidate, mod_name) {
return true
}
}
for candidate in candidates {
if _ := w.selective_import_fn_lookup_key(candidate) {
return true
}
}
for candidate in candidates {
if w.lookup_count('mod:${mod_name}:${candidate}') > 0
|| w.lookup_count('fn:${candidate}') > 0 {
return true
}
if !candidate.contains('__') && mod_name != '' && mod_name != 'main'
&& mod_name != 'builtin' && w.lookup_count('fn:${mod_name}__${candidate}') > 0 {
return true
}
}
return false
}
fn (w &Walker) current_module_fn_lookup_key(name string, mod_name string) ?string {
key := 'mod:${mod_name}:${name}'
if w.lookup_count(key) > 0 {
return key
}
return none
}
fn (w &Walker) exact_mangled_fn_lookup_key(name string) ?string {
if !name.contains('__') {
return none
}
key := 'fn:${name}'
if w.lookup_count(key) > 0 {
return key
}
return none
}
fn (w &Walker) selective_import_fn_lookup_key(name string) ?string {
if w.cur_fn_file == '' {
return none
}
if target := w.selective_import_fn_targets[selective_import_fn_key(w.cur_fn_file, name)] {
key := 'fn:${target}'
if w.lookup_count(key) > 0 {
return key
}
}
return none
}
fn (mut w Walker) mark_fn_name(name string, mod_name string) {
if name == '' || !string_ok(name) || !string_ok(mod_name) {
return
}
if w.opts.minimal_runtime_roots && name in ['array__eq', 'builtin__array__eq'] {
w.mark_fn_name('map_map_eq', 'builtin')
}
candidates := w.cached_called_fn_name_candidates(name)
if key := w.exact_mangled_fn_lookup_key(name) {
w.mark_lookup(key)
return
}
for candidate in candidates {
if key := w.current_module_fn_lookup_key(candidate, mod_name) {
w.mark_lookup(key)
return
}
}
for candidate in candidates {
if key := w.selective_import_fn_lookup_key(candidate) {
w.mark_lookup(key)
return
}
}
for candidate in candidates {
w.mark_lookup('mod:${mod_name}:${candidate}')
w.mark_lookup('fn:${candidate}')
if !candidate.contains('__') && mod_name != '' && mod_name != 'main'
&& mod_name != 'builtin' {
w.mark_lookup('fn:${mod_name}__${candidate}')
}
}
}
fn (mut w Walker) mark_method_name(name string, receivers []string) {
if name == '' || !string_ok(name) {
return
}
normalized := normalize_method_name(name)
for receiver in receivers {
if !string_ok(receiver) {
continue
}
w.mark_method_receiver_candidate(receiver, name, normalized)
}
}
fn (w &Walker) method_lookup_count(name string, receivers []string) int {
if name == '' || !string_ok(name) {
return 0
}
normalized := normalize_method_name(name)
mut count := 0
for receiver in receivers {
if !string_ok(receiver) {
continue
}
count += w.method_receiver_candidate_count(receiver, name, normalized)
}
return count
}
fn (mut w Walker) mark_method_lookup_candidate(candidate string, name string, normalized string) {
w.mark_lookup('meth:${candidate}:${name}')
if normalized != name {
w.mark_lookup('meth:${candidate}:${normalized}')
}
}
fn (mut w Walker) mark_method_receiver_candidate(receiver string, name string, normalized string) {
w.mark_method_lookup_candidate(receiver, name, normalized)
mut short_name := ''
if receiver.contains('__') {
short_name = receiver.all_after_last('__')
if short_name != '' && short_name != receiver {
w.mark_method_lookup_candidate(short_name, name, normalized)
}
}
if receiver.starts_with('Array_') || receiver.starts_with('Array_fixed_') {
if receiver != 'array' && short_name != 'array' {
w.mark_method_lookup_candidate('array', name, normalized)
}
}
if receiver.starts_with('Map_') {
if receiver != 'map' && short_name != 'map' {
w.mark_method_lookup_candidate('map', name, normalized)
}
}
if receiver.starts_with('_option_') {
if receiver != '_option' && short_name != '_option' {
w.mark_method_lookup_candidate('_option', name, normalized)
}
}
if receiver.starts_with('_result_') {
if receiver != '_result' && short_name != '_result' {
w.mark_method_lookup_candidate('_result', name, normalized)
}
}
}
fn (w &Walker) method_lookup_candidate_count(candidate string, name string, normalized string) int {
mut count := w.lookup_count('meth:${candidate}:${name}')
if normalized != name {
count += w.lookup_count('meth:${candidate}:${normalized}')
}
return count
}
fn (w &Walker) method_receiver_candidate_count(receiver string, name string, normalized string) int {
mut count := w.method_lookup_candidate_count(receiver, name, normalized)
mut short_name := ''
if receiver.contains('__') {
short_name = receiver.all_after_last('__')
if short_name != '' && short_name != receiver {
count += w.method_lookup_candidate_count(short_name, name, normalized)
}
}
if receiver.starts_with('Array_') || receiver.starts_with('Array_fixed_') {
if receiver != 'array' && short_name != 'array' {
count += w.method_lookup_candidate_count('array', name, normalized)
}
}
if receiver.starts_with('Map_') {
if receiver != 'map' && short_name != 'map' {
count += w.method_lookup_candidate_count('map', name, normalized)
}
}
if receiver.starts_with('_option_') {
if receiver != '_option' && short_name != '_option' {
count += w.method_lookup_candidate_count('_option', name, normalized)
}
}
if receiver.starts_with('_result_') {
if receiver != '_result' && short_name != '_result' {
count += w.method_lookup_candidate_count('_result', name, normalized)
}
}
return count
}
fn (mut w Walker) mark_method_name_fallback(name string) {
if name == '' || !string_ok(name) {
return
}
normalized := normalize_method_name(name)
// Check count first to avoid pulling in everything for common names.
count := w.lookup_count('mname:${name}') +
if normalized != name { w.lookup_count('mname:${normalized}') } else { 0 }
if count == 0 || count > 64 {
return
}
w.mark_lookup('mname:${name}')
if normalized != name {
w.mark_lookup('mname:${normalized}')
}
}
fn infix_operator_may_use_method(op token.Token) bool {
return op in [.plus, .minus, .mul, .div, .mod, .eq, .ne, .lt, .gt, .le, .ge, .pipe, .xor]
}
fn (mut w Walker) mark_infix_operator_method(expr ast.InfixExpr, mod_name string) {
if !infix_operator_may_use_method(expr.op) {
return
}
method_name := expr.op.str()
receivers := w.receiver_candidates_for_expr(expr.lhs, mod_name)
if receivers.len > 0 {
w.mark_method_name(method_name, receivers)
return
}
w.mark_method_name_fallback(method_name)
}
fn (mut w Walker) mark_all_methods_for_receivers(receivers []string) {
for receiver in receivers {
if receiver in w.methods_by_receiver {
for idx in w.methods_by_receiver[receiver] {
w.mark_fn(idx)
}
}
if receiver.contains('__') {
short_name := receiver.all_after_last('__')
if short_name in w.methods_by_receiver {
for idx in w.methods_by_receiver[short_name] {
w.mark_fn(idx)
}
}
}
}
}
fn (mut w Walker) mark_comptime_method_loop_receivers(stmt ast.Stmt, mod_name string) {
if stmt !is ast.ForStmt {
return
}
if stmt.init !is ast.ForInStmt {
return
}
for_in := stmt.init as ast.ForInStmt
if for_in.expr !is ast.SelectorExpr {
return
}
sel := for_in.expr as ast.SelectorExpr
if sel.rhs.name != 'methods' {
return
}
mut receivers := []string{}
for receiver in type_expr_receiver_candidates(mod_name, sel.lhs) {
add_unique_string(mut receivers, receiver)
}
for receiver in w.receiver_candidates_for_expr(sel.lhs, mod_name) {
add_unique_string(mut receivers, receiver)
}
w.mark_all_methods_for_receivers(receivers)
}
fn (mut w Walker) mark_comptime_method_loop_receivers_cursor(stmt ast.Cursor, mod_name string) {
if !stmt.is_valid() || stmt.kind() != .stmt_for {
return
}
init := stmt.edge(0)
if !init.is_valid() || init.kind() != .stmt_for_in {
return
}
expr := init.edge(2)
if !expr.is_valid() || expr.kind() != .expr_selector {
return
}
rhs := expr.edge(1)
if !rhs.is_valid() || rhs.name() != 'methods' {
return
}
lhs := expr.edge(0)
mut receivers := []string{}
for receiver in type_expr_receiver_candidates_cursor(lhs, mod_name) {
add_unique_string(mut receivers, receiver)
}
for receiver in w.receiver_candidates_for_cursor(lhs, mod_name) {
add_unique_string(mut receivers, receiver)
}
w.mark_all_methods_for_receivers(receivers)
}
fn (w &Walker) embedded_receivers_for(receivers []string) []string {
mut out := []string{}
mut seen := map[string]bool{}
mut stack := []string{}
for receiver in receivers {
if receiver == '' || receiver in seen {
continue
}
seen[receiver] = true
stack << receiver
}
for stack.len > 0 {
receiver := stack[stack.len - 1]
stack.delete(stack.len - 1)
embedded := w.struct_embedded_receivers[receiver] or { continue }
for embedded_receiver in embedded {
add_unique_string(mut out, embedded_receiver)
if embedded_receiver !in seen {
seen[embedded_receiver] = true
stack << embedded_receiver
}
}
}
return out
}
fn ierror_wrapper_base_from_ident(name string) string {
if !name.starts_with('IError_') {
return ''
}
prefix_len := 'IError_'.len
if name.ends_with('_type_name_wrapper') {
return name[prefix_len..name.len - '_type_name_wrapper'.len]
}
if name.ends_with('_msg_wrapper') {
return name[prefix_len..name.len - '_msg_wrapper'.len]
}
if name.ends_with('_code_wrapper') {
return name[prefix_len..name.len - '_code_wrapper'.len]
}
return ''
}
fn (mut w Walker) mark_ierror_wrapper_dependencies(name string, mod_name string) {
base := ierror_wrapper_base_from_ident(name)
if base == '' {
return
}
w.mark_fn_name('${base}__msg', mod_name)
w.mark_fn_name('${base}__code', mod_name)
w.mark_fn_name('Error__code', mod_name)
}
fn (w &Walker) interface_name_from_expr(expr ast.Expr, mod_name string) string {
match expr {
ast.Ident {
name := sanitize_receiver_name(expr.name)
if name in w.interface_type_names {
return name
}
if mod_name != '' && mod_name != 'main' {
qualified := '${mod_name}__${name}'
if qualified in w.interface_type_names {
return qualified
}
}
}
ast.SelectorExpr {
if expr.lhs is ast.Ident {
mut candidates := []string{}
add_unique_string(mut candidates, expr.rhs.name)
add_unique_string(mut candidates, '${expr.lhs.name}__${expr.rhs.name}')
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut candidates, '${mod_name}__${expr.rhs.name}')
}
for candidate in candidates {
if candidate in w.interface_type_names {
return candidate
}
}
}
}
else {}
}
return ''
}
// interface_name_from_cursor mirrors interface_name_from_expr but reads its
// inputs from a Cursor instead of a decoded ast.Expr. interface_name_from_expr
// only ever inspects Ident and SelectorExpr variants, so the cursor version
// only needs to dispatch on .expr_ident / .expr_selector.
fn (w &Walker) interface_name_from_cursor(c ast.Cursor, mod_name string) string {
if !c.is_valid() {
return ''
}
match c.kind() {
.expr_ident {
name := sanitize_receiver_name(c.name())
if name in w.interface_type_names {
return name
}
if mod_name != '' && mod_name != 'main' {
qualified := '${mod_name}__${name}'
if qualified in w.interface_type_names {
return qualified
}
}
}
.expr_selector {
lhs_c := c.edge(0)
rhs_c := c.edge(1)
if lhs_c.is_valid() && lhs_c.kind() == .expr_ident && rhs_c.is_valid() {
lhs_name := lhs_c.name()
rhs_name := rhs_c.name()
mut candidates := []string{}
add_unique_string(mut candidates, rhs_name)
add_unique_string(mut candidates, '${lhs_name}__${rhs_name}')
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut candidates, '${mod_name}__${rhs_name}')
}
for candidate in candidates {
if candidate in w.interface_type_names {
return candidate
}
}
}
}
else {}
}
return ''
}
fn (mut w Walker) mark_interface_conversion_methods(target_expr ast.Expr, value_expr ast.Expr, mod_name string) {
iface_name := w.interface_name_from_expr(target_expr, mod_name)
if iface_name == '' {
return
}
w.mark_interface_conversion_methods_for_name(iface_name, value_expr, mod_name)
}
fn (w &Walker) interface_name_from_type_expr(expr ast.Expr, mod_name string) string {
match expr {
ast.Ident, ast.SelectorExpr {
return w.interface_name_from_expr(expr, mod_name)
}
ast.PrefixExpr {
if expr.op == .amp {
return w.interface_name_from_type_expr(expr.expr, mod_name)
}
}
ast.ModifierExpr {
return w.interface_name_from_type_expr(expr.expr, mod_name)
}
ast.Type {
match expr {
ast.PointerType {
return w.interface_name_from_type_expr(expr.base_type, mod_name)
}
ast.GenericType {
return w.interface_name_from_type_expr(expr.name, mod_name)
}
else {}
}
}
else {}
}
return ''
}
fn (w &Walker) interface_name_from_type(typ types.Type) string {
if !type_ok(typ) {
return ''
}
match typ {
types.Interface {
return typ.name
}
types.Pointer {
if !type_ok(typ.base_type) {
return ''
}
return w.interface_name_from_type(typ.base_type)
}
types.Alias {
if !type_ok(typ.base_type) {
return ''
}
return w.interface_name_from_type(typ.base_type)
}
else {}
}
return ''
}
fn (w &Walker) interface_name_from_expr_type(expr ast.Expr, mod_name string) string {
if expr is ast.Ident {
if iface_name := w.global_interface_names[expr.name] {
return iface_name
}
if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
qualified := '${mod_name}__${expr.name}'
if iface_name := w.global_interface_names[qualified] {
return iface_name
}
}
}
if w.env != unsafe { nil } {
pos := expr.pos()
if pos.is_valid() {
if typ := w.env.get_expr_type(pos.id) {
return w.interface_name_from_type(typ)
}
}
}
return ''
}
fn (w &Walker) interface_method_names_for(iface_name string) []string {
mut out := []string{}
mut seen := map[string]bool{}
mut stack := []string{}
add_unique_string(mut stack, iface_name)
for stack.len > 0 {
current := stack[stack.len - 1]
stack.delete(stack.len - 1)
if current == '' || current in seen {
continue
}
seen[current] = true
method_names := w.interface_method_names[current] or { []string{} }
for method_name in method_names {
add_unique_fn_name(mut out, method_name)
}
embedded_names := w.interface_embedded_names[current] or { []string{} }
for embedded_name in embedded_names {
if embedded_name !in seen {
add_unique_string(mut stack, embedded_name)
}
}
}
return out
}
fn (mut w Walker) mark_assignment_interface_conversion(lhs ast.Expr, rhs ast.Expr, mod_name string) {
iface_name := w.interface_name_from_expr_type(lhs, mod_name)
if iface_name == '' {
return
}
w.mark_interface_conversion_methods_for_name(iface_name, rhs, mod_name)
}
// interface_name_from_expr_type_cursor mirrors interface_name_from_expr_type
// but reads from a Cursor without decoding the underlying Expr. Most lhs
// nodes are Idents, so the global_interface_names lookup short-circuits
// before any env call.
fn (w &Walker) interface_name_from_expr_type_cursor(c ast.Cursor, mod_name string) string {
if !c.is_valid() {
return ''
}
if c.kind() == .expr_ident {
name := c.name()
if iface_name := w.global_interface_names[name] {
return iface_name
}
if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
qualified := '${mod_name}__${name}'
if iface_name := w.global_interface_names[qualified] {
return iface_name
}
}
}
if w.env != unsafe { nil } {
pos := c.pos()
if pos.is_valid() {
if typ := w.env.get_expr_type(pos.id) {
return w.interface_name_from_type(typ)
}
}
}
return ''
}
// mark_assignment_interface_conversion_cursor is the cursor analogue of
// mark_assignment_interface_conversion. The lhs cursor is inspected for
// the interface-type fast path; only when iface_name is non-empty does
// the rhs cursor get handed to the by-cursor receiver lookup.
fn (mut w Walker) mark_assignment_interface_conversion_cursor(lhs_c ast.Cursor, rhs_c ast.Cursor, mod_name string) {
iface_name := w.interface_name_from_expr_type_cursor(lhs_c, mod_name)
if iface_name == '' {
return
}
w.mark_interface_conversion_methods_for_name_cursor(iface_name, rhs_c, mod_name)
}
// mark_interface_conversion_methods_cursor mirrors mark_interface_conversion_methods
// but accepts the value as a Cursor. target_expr stays a legacy Expr because
// callers (e.g. stmt_return) already hold one — cur_fn_decl.typ.return_type.
fn (mut w Walker) mark_interface_conversion_methods_cursor(target_expr ast.Expr, value_c ast.Cursor, mod_name string) {
iface_name := w.interface_name_from_expr(target_expr, mod_name)
if iface_name == '' {
return
}
w.mark_interface_conversion_methods_for_name_cursor(iface_name, value_c, mod_name)
}
// mark_result_error_return_methods_cursor is the cursor analogue of
// mark_result_error_return_methods. Only fires when the function's return
// type is ResultType — in that case the receiver candidates for the
// returned value are pulled from the cursor, and 'msg' / 'code' are marked
// on each.
fn (mut w Walker) mark_result_error_return_methods_cursor(return_type ast.Expr, value_c ast.Cursor, mod_name string) {
if return_type is ast.Type && return_type is ast.ResultType {
receivers := w.receiver_candidates_for_cursor(value_c, mod_name)
if receivers.len == 0 {
return
}
w.mark_method_name('msg', receivers)
w.mark_method_name('code', receivers)
}
}
fn (mut w Walker) mark_interface_conversion_methods_for_name(iface_name string, value_expr ast.Expr, mod_name string) {
receivers := w.receiver_candidates_for_expr(value_expr, mod_name)
if receivers.len == 0 {
return
}
method_names := w.interface_method_names_for(iface_name)
if method_names.len > 0 {
embedded_receivers := w.embedded_receivers_for(receivers)
for method_name in method_names {
w.mark_method_name(method_name, receivers)
w.mark_method_name(method_name, embedded_receivers)
}
}
}
fn (mut w Walker) mark_call_arg_interface_conversions(expr ast.CallExpr, mod_name string) {
mut marked := false
if w.env == unsafe { nil } {
w.mark_call_arg_interface_conversions_from_decls(expr.lhs, expr.args, mod_name)
return
}
if lhs_type := w.call_lhs_fn_type(expr.lhs, mod_name) {
param_types := lhs_type.get_param_types()
param_offset := if expr.lhs is ast.SelectorExpr { 1 } else { 0 }
for i, arg in expr.args {
param_idx := i + param_offset
if param_idx >= param_types.len {
break
}
param_type := param_types[param_idx]
if param_type is types.Interface {
w.mark_interface_conversion_methods_for_name((param_type as types.Interface).name,
arg, mod_name)
marked = true
}
}
}
if !marked {
w.mark_call_arg_interface_conversions_from_decls(expr.lhs, expr.args, mod_name)
}
}
fn (mut w Walker) mark_call_or_cast_arg_interface_conversion(expr ast.CallOrCastExpr, mod_name string) {
mut marked := false
if w.env == unsafe { nil } {
w.mark_call_arg_interface_conversions_from_decls(expr.lhs, [expr.expr], mod_name)
return
}
if lhs_type := w.call_lhs_fn_type(expr.lhs, mod_name) {
param_types := lhs_type.get_param_types()
param_offset := if expr.lhs is ast.SelectorExpr { 1 } else { 0 }
if param_offset < param_types.len {
param_type := param_types[param_offset]
if param_type is types.Interface {
w.mark_interface_conversion_methods_for_name((param_type as types.Interface).name,
expr.expr, mod_name)
marked = true
}
}
}
if !marked {
w.mark_call_arg_interface_conversions_from_decls(expr.lhs, [expr.expr], mod_name)
}
}
fn (w &Walker) add_lookup_indices(key string, mut out []int) {
if !string_ok(key) {
return
}
if values := w.lookup[key] {
for idx in values {
add_unique_int(mut out, idx)
}
}
}
fn (mut w Walker) add_fn_name_indices(name string, mod_name string, mut out []int) {
if name == '' || !string_ok(name) || !string_ok(mod_name) {
return
}
candidates := w.cached_called_fn_name_candidates(name)
if key := w.exact_mangled_fn_lookup_key(name) {
w.add_lookup_indices(key, mut out)
return
}
for candidate in candidates {
if key := w.current_module_fn_lookup_key(candidate, mod_name) {
w.add_lookup_indices(key, mut out)
return
}
}
for candidate in candidates {
if key := w.selective_import_fn_lookup_key(candidate) {
w.add_lookup_indices(key, mut out)
return
}
}
for candidate in candidates {
w.add_lookup_indices('mod:${mod_name}:${candidate}', mut out)
w.add_lookup_indices('fn:${candidate}', mut out)
if !candidate.contains('__') && mod_name != '' && mod_name != 'main'
&& mod_name != 'builtin' {
w.add_lookup_indices('fn:${mod_name}__${candidate}', mut out)
}
}
}
fn (w &Walker) add_method_name_indices(name string, receivers []string, mut out []int) {
if name == '' || !string_ok(name) {
return
}
normalized := normalize_method_name(name)
for receiver in receivers {
if !string_ok(receiver) {
continue
}
w.add_method_receiver_candidate_indices(receiver, name, normalized, mut out)
}
}
fn (w &Walker) add_method_candidate_indices(candidate string, name string, normalized string, mut out []int) {
w.add_lookup_indices('meth:${candidate}:${name}', mut out)
if normalized != name {
w.add_lookup_indices('meth:${candidate}:${normalized}', mut out)
}
}
fn (w &Walker) add_method_receiver_candidate_indices(receiver string, name string, normalized string, mut out []int) {
w.add_method_candidate_indices(receiver, name, normalized, mut out)
mut short_name := ''
if receiver.contains('__') {
short_name = receiver.all_after_last('__')
if short_name != '' && short_name != receiver {
w.add_method_candidate_indices(short_name, name, normalized, mut out)
}
}
if receiver.starts_with('Array_') || receiver.starts_with('Array_fixed_') {
if receiver != 'array' && short_name != 'array' {
w.add_method_candidate_indices('array', name, normalized, mut out)
}
}
if receiver.starts_with('Map_') {
if receiver != 'map' && short_name != 'map' {
w.add_method_candidate_indices('map', name, normalized, mut out)
}
}
if receiver.starts_with('_option_') {
if receiver != '_option' && short_name != '_option' {
w.add_method_candidate_indices('_option', name, normalized, mut out)
}
}
if receiver.starts_with('_result_') {
if receiver != '_result' && short_name != '_result' {
w.add_method_candidate_indices('_result', name, normalized, mut out)
}
}
}
fn (mut w Walker) call_lhs_decl_indices(lhs ast.Expr, mod_name string) []int {
mut out := []int{}
if !expr_ok(lhs) {
return out
}
match lhs {
ast.Ident {
w.add_fn_name_indices(lhs.name, mod_name, mut out)
}
ast.GenericArgs {
return w.call_lhs_decl_indices(lhs.lhs, mod_name)
}
ast.GenericArgOrIndexExpr {
return w.call_lhs_decl_indices(lhs.lhs, mod_name)
}
ast.SelectorExpr {
method_name := lhs.rhs.name
if !string_ok(method_name) {
return out
}
if lhs.lhs is ast.Ident {
left_name := lhs.lhs.name
if !string_ok(left_name) {
return out
}
if left_name == 'C' {
w.add_fn_name_indices(method_name, mod_name, mut out)
return out
}
if left_name in w.module_names {
real_mod := w.module_alias_to_real[left_name] or { left_name }
w.add_fn_name_indices('${real_mod}__${method_name}', mod_name, mut out)
return out
}
if left_name in w.type_names {
w.add_method_name_indices(method_name,
[left_name, '${mod_name}__${left_name}'], mut out)
return out
}
}
if lhs.lhs is ast.SelectorExpr {
type_sel := lhs.lhs as ast.SelectorExpr
if type_sel.lhs is ast.Ident {
mod_ident := type_sel.lhs.name
type_name := type_sel.rhs.name
if !string_ok(mod_ident) || !string_ok(type_name) {
return out
}
if mod_ident in w.module_names || mod_ident in w.type_names {
mut candidates := []string{cap: 3}
candidates << type_name
candidates << '${mod_name}__${type_name}'
candidates << '${mod_ident}__${type_name}'
w.add_method_name_indices(method_name, candidates, mut out)
return out
}
}
}
receivers := w.receiver_candidates_for_expr(lhs.lhs, mod_name)
if receivers.len > 0 {
w.add_method_name_indices(method_name, receivers, mut out)
if out.len > 0 {
return out
}
}
normalized := normalize_method_name(method_name)
w.add_lookup_indices('mname:${method_name}', mut out)
if normalized != method_name {
w.add_lookup_indices('mname:${normalized}', mut out)
}
}
else {}
}
return out
}
fn (mut w Walker) mark_call_arg_interface_conversions_from_decls(lhs ast.Expr, args []ast.Expr, mod_name string) {
decl_indices := w.call_lhs_decl_indices(lhs, mod_name)
if decl_indices.len == 0 {
return
}
for idx in decl_indices {
if idx < 0 || idx >= w.fns.len {
continue
}
info := w.fns[idx]
params := info.decl.typ.params
arg_offset := if lhs is ast.Ident && info.decl.is_method && !info.decl.is_static {
1
} else {
0
}
for i, param in params {
arg_idx := i + arg_offset
if arg_idx >= args.len {
break
}
iface_name := w.interface_name_from_expr(param.typ, info.mod)
if iface_name == '' {
continue
}
w.mark_interface_conversion_methods_for_name(iface_name, args[arg_idx], mod_name)
}
}
}
fn (mut w Walker) mark_string_interpolation_str_dependency(expr ast.Expr, mod_name string) {
receivers := w.receiver_candidates_for_expr(expr, mod_name)
if receivers.len == 0 {
return
}
w.mark_method_name('str', receivers)
for receiver in receivers {
w.mark_fn_name('${receiver}__str', mod_name)
}
}
fn (mut w Walker) mark_result_error_return_methods(return_type ast.Expr, expr ast.Expr, mod_name string) {
if return_type is ast.Type && return_type is ast.ResultType {
receivers := w.receiver_candidates_for_expr(expr, mod_name)
if receivers.len == 0 {
return
}
w.mark_method_name('msg', receivers)
w.mark_method_name('code', receivers)
}
}
// call_lhs_fn_type_cursor mirrors call_lhs_fn_type but reads from a Cursor.
// receiver_candidates_for_expr still needs a decoded Expr for the
// SelectorExpr branch — only that subtree is decoded, and only when env
// lookup falls through to it.
fn (w &Walker) call_lhs_fn_type_cursor(c ast.Cursor, mod_name string) ?types.FnType {
if !c.is_valid() {
return none
}
if w.env != unsafe { nil } {
if lhs_type := w.env.get_expr_type(c.pos().id) {
if lhs_type is types.FnType {
return lhs_type as types.FnType
}
}
match c.kind() {
.expr_ident {
if fn_type := w.env.lookup_fn(mod_name, c.name()) {
return fn_type
}
}
.expr_selector {
inner_lhs := c.edge(0)
rhs_c := c.edge(1)
if inner_lhs.is_valid() && rhs_c.is_valid() {
receivers := w.receiver_candidates_for_cursor(inner_lhs, mod_name)
for receiver in receivers {
if fn_type := w.env.lookup_method(receiver, rhs_c.name()) {
return fn_type
}
}
}
}
else {}
}
}
if c.kind() == .expr_ident && w.cur_fn_scope != unsafe { nil } {
if obj := w.cur_fn_scope.lookup_parent(c.name(), 0) {
typ := obj.typ()
if typ is types.FnType {
return typ as types.FnType
}
}
}
return none
}
// call_lhs_decl_indices_cursor mirrors call_lhs_decl_indices with cursor
// input. Same shape: Ident / GenericArgs / GenericArgOrIndexExpr / Selector
// — the only ast-decoded subtree is lhs.lhs for SelectorExpr's
// receiver_candidates_for_expr fallback.
fn (mut w Walker) call_lhs_decl_indices_cursor(c ast.Cursor, mod_name string) []int {
mut out := []int{}
if !c.is_valid() {
return out
}
match c.kind() {
.expr_ident {
w.add_fn_name_indices(c.name(), mod_name, mut out)
}
.expr_generic_args {
return w.call_lhs_decl_indices_cursor(c.edge(0), mod_name)
}
.expr_generic_arg_or_index {
return w.call_lhs_decl_indices_cursor(c.edge(0), mod_name)
}
.expr_selector {
rhs_c := c.edge(1)
if !rhs_c.is_valid() {
return out
}
method_name := rhs_c.name()
if !string_ok(method_name) {
return out
}
lhs_c := c.edge(0)
if lhs_c.is_valid() && lhs_c.kind() == .expr_ident {
left_name := lhs_c.name()
if !string_ok(left_name) {
return out
}
if left_name == 'C' {
w.add_fn_name_indices(method_name, mod_name, mut out)
return out
}
if left_name in w.module_names {
real_mod := w.module_alias_to_real[left_name] or { left_name }
w.add_fn_name_indices('${real_mod}__${method_name}', mod_name, mut out)
return out
}
if left_name in w.type_names {
w.add_method_name_indices(method_name,
[left_name, '${mod_name}__${left_name}'], mut out)
return out
}
}
if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
inner_lhs := lhs_c.edge(0)
inner_rhs := lhs_c.edge(1)
if inner_lhs.is_valid() && inner_lhs.kind() == .expr_ident && inner_rhs.is_valid() {
mod_ident := inner_lhs.name()
type_name := inner_rhs.name()
if !string_ok(mod_ident) || !string_ok(type_name) {
return out
}
if mod_ident in w.module_names || mod_ident in w.type_names {
mut candidates := []string{cap: 3}
candidates << type_name
candidates << '${mod_name}__${type_name}'
candidates << '${mod_ident}__${type_name}'
w.add_method_name_indices(method_name, candidates, mut out)
return out
}
}
}
receivers := w.receiver_candidates_for_cursor(lhs_c, mod_name)
if receivers.len > 0 {
w.add_method_name_indices(method_name, receivers, mut out)
if out.len > 0 {
return out
}
}
normalized := normalize_method_name(method_name)
w.add_lookup_indices('mname:${method_name}', mut out)
if normalized != method_name {
w.add_lookup_indices('mname:${normalized}', mut out)
}
}
else {}
}
return out
}
// mark_call_arg_interface_conversions_cursor / _from_decls_cursor are the
// cursor analogues of mark_call_arg_interface_conversions /
// mark_call_arg_interface_conversions_from_decls. Args are only decoded on
// demand (when the param type is an interface), so non-interface params do
// not pay any decode cost.
fn (mut w Walker) mark_call_arg_interface_conversions_cursor(call_c ast.Cursor, mod_name string) {
lhs_c := call_c.edge(0)
ec := call_c.edge_count()
args_len := ec - 1
mut marked := false
if w.env != unsafe { nil } {
if lhs_type := w.call_lhs_fn_type_cursor(lhs_c, mod_name) {
param_types := lhs_type.get_param_types()
param_offset := if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
1
} else {
0
}
for i in 0 .. args_len {
param_idx := i + param_offset
if param_idx >= param_types.len {
break
}
param_type := param_types[param_idx]
if param_type is types.Interface {
arg_c := call_c.edge(1 + i)
w.mark_interface_conversion_methods_for_name_cursor((param_type as types.Interface).name,
arg_c, mod_name)
marked = true
}
}
}
} else {
w.mark_call_arg_interface_conversions_from_decls_cursor(call_c, mod_name)
return
}
if !marked {
w.mark_call_arg_interface_conversions_from_decls_cursor(call_c, mod_name)
}
}
fn (mut w Walker) mark_call_arg_interface_conversions_from_decls_cursor(call_c ast.Cursor, mod_name string) {
lhs_c := call_c.edge(0)
decl_indices := w.call_lhs_decl_indices_cursor(lhs_c, mod_name)
if decl_indices.len == 0 {
return
}
args_len := call_c.edge_count() - 1
lhs_is_ident := lhs_c.is_valid() && lhs_c.kind() == .expr_ident
for idx in decl_indices {
if idx < 0 || idx >= w.fns.len {
continue
}
info := w.fns[idx]
params := info.decl.typ.params
arg_offset := if lhs_is_ident && info.decl.is_method && !info.decl.is_static {
1
} else {
0
}
for i, param in params {
arg_idx := i + arg_offset
if arg_idx >= args_len {
break
}
iface_name := w.interface_name_from_expr(param.typ, info.mod)
if iface_name == '' {
continue
}
arg_c := call_c.edge(1 + arg_idx)
w.mark_interface_conversion_methods_for_name_cursor(iface_name, arg_c, mod_name)
}
}
}
// mark_call_or_cast_arg_interface_conversion_cursor is the cursor analogue
// of mark_call_or_cast_arg_interface_conversion. The "expr" edge (call_c.edge(1))
// is treated as a single-arg list; it is decoded only when the param type
// is an interface.
fn (mut w Walker) mark_call_or_cast_arg_interface_conversion_cursor(call_c ast.Cursor, mod_name string) {
lhs_c := call_c.edge(0)
expr_c := call_c.edge(1)
mut marked := false
if w.env != unsafe { nil } {
if lhs_type := w.call_lhs_fn_type_cursor(lhs_c, mod_name) {
param_types := lhs_type.get_param_types()
param_offset := if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
1
} else {
0
}
if param_offset < param_types.len {
param_type := param_types[param_offset]
if param_type is types.Interface {
w.mark_interface_conversion_methods_for_name_cursor((param_type as types.Interface).name,
expr_c, mod_name)
marked = true
}
}
}
} else {
decl_indices := w.call_lhs_decl_indices_cursor(lhs_c, mod_name)
w.mark_call_or_cast_arg_interface_from_decl_indices_cursor(decl_indices, lhs_c, expr_c,
mod_name)
return
}
if !marked {
decl_indices := w.call_lhs_decl_indices_cursor(lhs_c, mod_name)
w.mark_call_or_cast_arg_interface_from_decl_indices_cursor(decl_indices, lhs_c, expr_c,
mod_name)
}
}
fn (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) {
if decl_indices.len == 0 || !expr_c.is_valid() {
return
}
lhs_is_ident := lhs_c.is_valid() && lhs_c.kind() == .expr_ident
for idx in decl_indices {
if idx < 0 || idx >= w.fns.len {
continue
}
info := w.fns[idx]
params := info.decl.typ.params
arg_offset := if lhs_is_ident && info.decl.is_method && !info.decl.is_static {
1
} else {
0
}
if arg_offset >= params.len {
continue
}
param := params[arg_offset]
iface_name := w.interface_name_from_expr(param.typ, info.mod)
if iface_name == '' {
continue
}
w.mark_interface_conversion_methods_for_name_cursor(iface_name, expr_c, mod_name)
}
}
fn (w &Walker) call_lhs_fn_type(lhs ast.Expr, mod_name string) ?types.FnType {
if !expr_ok(lhs) {
return none
}
if w.env != unsafe { nil } {
if lhs_type := w.env.get_expr_type(lhs.pos().id) {
if lhs_type is types.FnType {
return lhs_type as types.FnType
}
}
if lhs is ast.Ident {
if fn_type := w.env.lookup_fn(mod_name, lhs.name) {
return fn_type
}
}
if lhs is ast.SelectorExpr {
receivers := w.receiver_candidates_for_expr(lhs.lhs, mod_name)
for receiver in receivers {
if fn_type := w.env.lookup_method(receiver, lhs.rhs.name) {
return fn_type
}
}
}
}
if lhs is ast.Ident && w.cur_fn_scope != unsafe { nil } {
if obj := w.cur_fn_scope.lookup_parent(lhs.name, 0) {
typ := obj.typ()
if typ is types.FnType {
return typ as types.FnType
}
}
}
return none
}
fn (w &Walker) current_param_receiver_candidates(name string, mod_name string) []string {
mut out := []string{}
if name == '' || w.cur_fn_decl.name == '' {
return out
}
for param in w.cur_fn_decl.typ.params {
if param.name != name {
continue
}
for receiver in type_expr_receiver_candidates(mod_name, param.typ) {
add_unique_string(mut out, receiver)
}
generic_name := receiver_type_expr_name(param.typ)
if generic_name == '' || w.env == unsafe { nil } {
continue
}
for bindings in w.current_fn_generic_bindings() {
if concrete := bindings[generic_name] {
for receiver in type_name_candidates_from_type(mod_name, concrete) {
add_unique_string(mut out, receiver)
}
}
}
}
return out
}
fn (w &Walker) current_receiver_candidates(name string, mod_name string) []string {
mut out := []string{}
if name == '' || w.cur_fn_decl.name == '' || !is_method_decl(w.cur_fn_decl)
|| w.cur_fn_decl.receiver.name != name {
return out
}
for receiver in type_expr_receiver_candidates(mod_name, w.cur_fn_decl.receiver.typ) {
add_unique_string(mut out, receiver)
}
generic_name := receiver_type_expr_name(w.cur_fn_decl.receiver.typ)
if generic_name == '' || w.env == unsafe { nil } {
return out
}
for bindings in w.current_fn_generic_bindings() {
if concrete := bindings[generic_name] {
for receiver in type_name_candidates_from_type(mod_name, concrete) {
add_unique_string(mut out, receiver)
}
}
}
return out
}
fn (w &Walker) current_fn_generic_bindings() []map[string]types.Type {
if w.env == unsafe { nil } || w.cur_fn_decl.name == '' {
return []map[string]types.Type{}
}
mut out := []map[string]types.Type{}
for key, bindings_list in w.env.generic_types {
if generic_base_name_from_key(key) != w.cur_fn_decl.name {
continue
}
for bindings in bindings_list {
out << bindings
}
}
return out
}
// receiver_candidates_for_cursor mirrors receiver_candidates_for_expr but
// reads inputs from a Cursor instead of a decoded ast.Expr. Same shape:
// env-type lookup by pos.id first, then a structural match on cursor kind.
// PrefixExpr / ParenExpr / ModifierExpr / SelectorExpr recurse through
// edge(0); InitExpr through its typ at edge(0); CallExpr / CallOrCastExpr
// look at edge(0)'s selector.lhs without recursion.
fn (w &Walker) receiver_candidates_for_cursor(c ast.Cursor, mod_name string) []string {
mut out := []string{}
if !c.is_valid() {
return out
}
pos := c.pos()
if w.env != unsafe { nil } && pos.is_valid() {
if receiver_type := w.env.get_expr_type(pos.id) {
for name in type_name_candidates_from_type(mod_name, receiver_type) {
add_unique_string(mut out, name)
}
}
}
match c.kind() {
.expr_ident {
ident_name := c.name()
if w.env != unsafe { nil } && w.cur_fn_scope != unsafe { nil } {
if local_type := w.env.lookup_local_var(w.cur_fn_scope, ident_name) {
for type_name in type_name_candidates_from_type(mod_name, local_type) {
add_unique_string(mut out, type_name)
}
}
}
name := sanitize_receiver_name(ident_name)
for type_name in w.current_receiver_candidates(ident_name, mod_name) {
add_unique_string(mut out, type_name)
}
for type_name in w.current_param_receiver_candidates(ident_name, mod_name) {
add_unique_string(mut out, type_name)
}
add_unique_string(mut out, name)
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, '${mod_name}__${name}')
}
}
.expr_prefix, .expr_paren, .expr_modifier {
for name in w.receiver_candidates_for_cursor(c.edge(0), mod_name) {
add_unique_string(mut out, name)
}
}
.expr_init {
for name in w.receiver_candidates_for_cursor(c.edge(0), mod_name) {
add_unique_string(mut out, name)
}
}
.expr_call, .expr_call_or_cast {
lhs_c := c.edge(0)
if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
inner_lhs := lhs_c.edge(0)
if inner_lhs.is_valid() && inner_lhs.kind() == .expr_ident {
type_name := inner_lhs.name()
if type_name in w.type_names {
add_receiver_name_candidates(mut out, mod_name, type_name)
}
}
}
}
.expr_selector {
lhs_c := c.edge(0)
rhs_c := c.edge(1)
if rhs_c.is_valid() {
rhs_name := rhs_c.name()
lhs_receivers := w.receiver_candidates_for_cursor(lhs_c, mod_name)
for lhs_receiver in lhs_receivers {
key := '${lhs_receiver}:${rhs_name}'
if field_receivers := w.struct_field_receivers[key] {
for field_receiver in field_receivers {
add_unique_string(mut out, field_receiver)
}
}
}
}
}
else {}
}
return out
}
// mark_interface_conversion_methods_for_name_cursor is the cursor analogue
// of mark_interface_conversion_methods_for_name. The value_expr is replaced
// by a value cursor, so receiver_candidates_for_cursor avoids decoding.
fn (mut w Walker) mark_interface_conversion_methods_for_name_cursor(iface_name string, value_c ast.Cursor, mod_name string) {
receivers := w.receiver_candidates_for_cursor(value_c, mod_name)
if receivers.len == 0 {
return
}
if iface_name in w.interface_method_names {
embedded_receivers := w.embedded_receivers_for(receivers)
for method_name in w.interface_method_names[iface_name] {
w.mark_method_name(method_name, receivers)
w.mark_method_name(method_name, embedded_receivers)
}
}
w.mark_all_methods_for_receivers(receivers)
}
// mark_string_interpolation_str_dependency_cursor is the cursor analogue of
// mark_string_interpolation_str_dependency.
fn (mut w Walker) mark_string_interpolation_str_dependency_cursor(value_c ast.Cursor, mod_name string) {
receivers := w.receiver_candidates_for_cursor(value_c, mod_name)
if receivers.len == 0 {
return
}
w.mark_method_name('str', receivers)
for receiver in receivers {
w.mark_fn_name('${receiver}__str', mod_name)
}
}
fn (w &Walker) receiver_candidates_for_expr(expr ast.Expr, mod_name string) []string {
mut out := []string{}
if !expr_ok(expr) {
return out
}
pos := expr.pos()
if w.env != unsafe { nil } && pos.is_valid() {
if receiver_type := w.env.get_expr_type(pos.id) {
for name in type_name_candidates_from_type(mod_name, receiver_type) {
add_unique_string(mut out, name)
}
}
}
match expr {
ast.Ident {
if w.env != unsafe { nil } && w.cur_fn_scope != unsafe { nil } {
if local_type := w.env.lookup_local_var(w.cur_fn_scope, expr.name) {
for type_name in type_name_candidates_from_type(mod_name, local_type) {
add_unique_string(mut out, type_name)
}
}
}
name := sanitize_receiver_name(expr.name)
for type_name in w.current_receiver_candidates(expr.name, mod_name) {
add_unique_string(mut out, type_name)
}
for type_name in w.current_param_receiver_candidates(expr.name, mod_name) {
add_unique_string(mut out, type_name)
}
add_unique_string(mut out, name)
if mod_name != '' && mod_name != 'main' {
add_unique_string(mut out, '${mod_name}__${name}')
}
}
ast.PrefixExpr {
for name in w.receiver_candidates_for_expr(expr.expr, mod_name) {
add_unique_string(mut out, name)
}
}
ast.ParenExpr {
for name in w.receiver_candidates_for_expr(expr.expr, mod_name) {
add_unique_string(mut out, name)
}
}
ast.ModifierExpr {
for name in w.receiver_candidates_for_expr(expr.expr, mod_name) {
add_unique_string(mut out, name)
}
}
ast.InitExpr {
// InitExpr has a .typ that names the struct being constructed.
for name in w.receiver_candidates_for_expr(expr.typ, mod_name) {
add_unique_string(mut out, name)
}
}
ast.CallExpr {
if expr.lhs is ast.SelectorExpr {
selector := expr.lhs as ast.SelectorExpr
if selector.lhs is ast.Ident {
type_name := selector.lhs.name
if type_name in w.type_names {
add_receiver_name_candidates(mut out, mod_name, type_name)
}
}
}
}
ast.CallOrCastExpr {
if expr.lhs is ast.SelectorExpr {
selector := expr.lhs as ast.SelectorExpr
if selector.lhs is ast.Ident {
type_name := selector.lhs.name
if type_name in w.type_names {
add_receiver_name_candidates(mut out, mod_name, type_name)
}
}
}
}
ast.SelectorExpr {
lhs_receivers := w.receiver_candidates_for_expr(expr.lhs, mod_name)
for lhs_receiver in lhs_receivers {
key := '${lhs_receiver}:${expr.rhs.name}'
if field_receivers := w.struct_field_receivers[key] {
for field_receiver in field_receivers {
add_unique_string(mut out, field_receiver)
}
}
}
}
else {}
}
return out
}
fn (w &Walker) is_cast_type_name(name string) bool {
if !string_ok(name) {
return false
}
if name in builtin_cast_type_names {
return true
}
return name in w.type_names
}
fn (mut w Walker) mark_call_lhs(lhs ast.Expr, mod_name string) {
if !expr_ok(lhs) {
return
}
match lhs {
ast.Ident {
w.mark_fn_name(lhs.name, mod_name)
w.mark_const_fn_value_alias(lhs.name, mod_name)
}
ast.GenericArgs {
w.mark_call_lhs(lhs.lhs, mod_name)
}
ast.GenericArgOrIndexExpr {
w.mark_call_lhs(lhs.lhs, mod_name)
}
ast.SelectorExpr {
method_name := lhs.rhs.name
if !string_ok(method_name) {
return
}
if lhs.lhs is ast.Ident {
left_name := lhs.lhs.name
if !string_ok(left_name) {
return
}
if left_name == 'C' {
w.mark_fn_name(method_name, mod_name)
return
}
if left_name in w.module_names {
// Resolve import aliases (e.g., 'ssa_optimize' → 'optimize')
// so lookup finds 'optimize__func' not 'ssa_optimize__func'.
real_mod := w.module_alias_to_real[left_name] or { left_name }
w.mark_fn_name('${real_mod}__${method_name}', mod_name)
return
}
if left_name in w.type_names {
w.mark_method_name(method_name, [left_name, '${mod_name}__${left_name}'])
return
}
}
if lhs.lhs is ast.SelectorExpr {
type_sel := lhs.lhs as ast.SelectorExpr
if type_sel.lhs is ast.Ident {
mod_ident := type_sel.lhs.name
type_name := type_sel.rhs.name
if !string_ok(mod_ident) || !string_ok(type_name) {
return
}
if mod_ident in w.module_names || mod_ident in w.type_names {
candidates := [
type_name,
'${mod_name}__${type_name}',
'${mod_ident}__${type_name}',
]
w.mark_method_name(method_name, candidates)
return
}
}
}
receivers := w.receiver_candidates_for_expr(lhs.lhs, mod_name)
if receivers.len > 0 {
prev_queue_len := w.queue.len
w.mark_method_name(method_name, receivers)
if w.queue.len > prev_queue_len {
return
}
}
w.mark_method_name_fallback(method_name)
}
else {}
}
}
fn (w &Walker) const_fn_value_alias_is_shadowed(name string) bool {
if w.cur_fn_scope == unsafe { nil } {
return false
}
if obj := w.cur_fn_scope.lookup_parent(name, 0) {
return obj !is types.Const
}
return false
}
fn (mut w Walker) mark_const_fn_value_alias(name string, mod_name string) bool {
if name == '' || !string_ok(name) || !string_ok(mod_name)
|| w.const_fn_value_alias_is_shadowed(name) {
return false
}
if target := w.const_fn_value_aliases[const_fn_value_alias_key(mod_name, name)] {
w.mark_fn_name(target, mod_name)
return true
}
return false
}
// mark_call_lhs_cursor is the cursor-input analogue of mark_call_lhs. It
// handles every case structurally via cursor accesses, and only falls back
// to decoding `lhs.lhs` for the SelectorExpr receiver_candidates_for_expr
// branch — which is itself only reached when the early-return cases (C.fn,
// module.fn, Type.method, mod.Type.method) all fail.
fn (mut w Walker) mark_call_lhs_cursor(c ast.Cursor, mod_name string) {
if !c.is_valid() {
return
}
match c.kind() {
.expr_ident {
w.mark_fn_name(c.name(), mod_name)
w.mark_const_fn_value_alias(c.name(), mod_name)
}
.expr_generic_args {
w.mark_call_lhs_cursor(c.edge(0), mod_name)
}
.expr_generic_arg_or_index {
w.mark_call_lhs_cursor(c.edge(0), mod_name)
}
.expr_selector {
rhs_c := c.edge(1)
if !rhs_c.is_valid() {
return
}
method_name := rhs_c.name()
if !string_ok(method_name) {
return
}
lhs_c := c.edge(0)
if lhs_c.is_valid() && lhs_c.kind() == .expr_ident {
left_name := lhs_c.name()
if !string_ok(left_name) {
return
}
if left_name == 'C' {
w.mark_fn_name(method_name, mod_name)
return
}
if left_name in w.module_names {
real_mod := w.module_alias_to_real[left_name] or { left_name }
w.mark_fn_name('${real_mod}__${method_name}', mod_name)
return
}
if left_name in w.type_names {
w.mark_method_name(method_name, [left_name, '${mod_name}__${left_name}'])
return
}
}
if lhs_c.is_valid() && lhs_c.kind() == .expr_selector {
inner_lhs := lhs_c.edge(0)
inner_rhs := lhs_c.edge(1)
if inner_lhs.is_valid() && inner_lhs.kind() == .expr_ident && inner_rhs.is_valid() {
mod_ident := inner_lhs.name()
type_name := inner_rhs.name()
if !string_ok(mod_ident) || !string_ok(type_name) {
return
}
if mod_ident in w.module_names || mod_ident in w.type_names {
candidates := [
type_name,
'${mod_name}__${type_name}',
'${mod_ident}__${type_name}',
]
w.mark_method_name(method_name, candidates)
return
}
}
}
// Fallback: cursor-native receiver candidates — no decode.
receivers := w.receiver_candidates_for_cursor(lhs_c, mod_name)
if receivers.len > 0 {
prev_queue_len := w.queue.len
w.mark_method_name(method_name, receivers)
if w.queue.len > prev_queue_len {
return
}
}
w.mark_method_name_fallback(method_name)
}
else {}
}
}
fn (mut w Walker) mark_selector_fn_value(expr ast.SelectorExpr, mod_name string) {
w.mark_selector_fn_value_with_fallback(expr, mod_name, false)
}
fn (mut w Walker) mark_selector_fn_value_with_fallback(expr ast.SelectorExpr, mod_name string, force_fallback bool) {
target := w.selector_fn_value_target(expr)
if target != '' {
w.mark_fn_name(target, mod_name)
return
}
if expr.rhs.name == '' {
return
}
receivers := w.receiver_candidates_for_expr(expr.lhs, mod_name)
if receivers.len > 0 {
matched := w.method_lookup_count(expr.rhs.name, receivers) > 0
w.mark_method_name(expr.rhs.name, receivers)
if matched {
return
}
}
if force_fallback || w.expr_has_fn_value_type(ast.Expr(expr)) {
w.mark_method_name_fallback(expr.rhs.name)
}
}
fn (mut w Walker) mark_selector_fn_value_cursor(c ast.Cursor, mod_name string) {
w.mark_selector_fn_value_cursor_with_fallback(c, mod_name, false)
}
fn (mut w Walker) mark_selector_fn_value_cursor_with_fallback(c ast.Cursor, mod_name string, force_fallback bool) {
target := w.selector_fn_value_target_cursor(c)
if target != '' {
w.mark_fn_name(target, mod_name)
return
}
rhs_c := c.edge(1)
if !rhs_c.is_valid() {
return
}
method_name := rhs_c.name()
if method_name == '' {
return
}
receivers := w.receiver_candidates_for_cursor(c.edge(0), mod_name)
if receivers.len > 0 {
matched := w.method_lookup_count(method_name, receivers) > 0
w.mark_method_name(method_name, receivers)
if matched {
return
}
}
if force_fallback || w.cursor_has_fn_value_type(c) {
w.mark_method_name_fallback(method_name)
}
}
fn (w &Walker) selector_fn_value_target(expr ast.SelectorExpr) string {
if expr.lhs !is ast.Ident {
return ''
}
left := expr.lhs as ast.Ident
left_name := left.name
if left_name == 'C' || left_name !in w.module_names {
return ''
}
real_mod := w.module_alias_to_real[left_name] or { left_name }
target := '${real_mod}__${expr.rhs.name}'
if w.lookup_count('fn:${target}') > 0 {
return target
}
return ''
}
fn (w &Walker) selector_fn_value_target_cursor(c ast.Cursor) string {
if !c.is_valid() || c.kind() != .expr_selector {
return ''
}
lhs_c := c.edge(0)
rhs_c := c.edge(1)
if !lhs_c.is_valid() || lhs_c.kind() != .expr_ident || !rhs_c.is_valid() {
return ''
}
left_name := lhs_c.name()
if left_name == 'C' || left_name !in w.module_names {
return ''
}
real_mod := w.module_alias_to_real[left_name] or { left_name }
target := '${real_mod}__${rhs_c.name()}'
if w.lookup_count('fn:${target}') > 0 {
return target
}
return ''
}
fn (mut w Walker) mark_fn_value_expr(expr ast.Expr, mod_name string) {
if !w.opts.minimal_runtime_roots || !expr_ok(expr) {
return
}
match expr {
ast.Ident {
if w.mark_const_fn_value_alias(expr.name, mod_name) {
return
}
if w.ident_resolves_to_fn_value(expr.name, mod_name) {
w.mark_fn_name(expr.name, mod_name)
}
}
ast.SelectorExpr {
w.mark_selector_fn_value(expr, mod_name)
}
ast.GenericArgs {
w.mark_fn_value_expr(expr.lhs, mod_name)
}
ast.GenericArgOrIndexExpr {
w.mark_fn_value_expr(expr.lhs, mod_name)
}
ast.ModifierExpr {
w.mark_fn_value_expr(expr.expr, mod_name)
}
ast.ParenExpr {
w.mark_fn_value_expr(expr.expr, mod_name)
}
else {}
}
}
fn (mut w Walker) mark_fn_value_expr_cursor(c ast.Cursor, mod_name string) {
if !w.opts.minimal_runtime_roots || !c.is_valid() {
return
}
match c.kind() {
.expr_ident {
name := c.name()
if w.mark_const_fn_value_alias(name, mod_name) {
return
}
if w.ident_resolves_to_fn_value(name, mod_name) {
w.mark_fn_name(name, mod_name)
}
}
.expr_selector {
w.mark_selector_fn_value_cursor(c, mod_name)
}
.expr_generic_args {
w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
}
.expr_generic_arg_or_index {
w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
}
.expr_modifier {
w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
}
.expr_paren {
w.mark_fn_value_expr_cursor(c.edge(0), mod_name)
}
else {}
}
}
fn (mut w Walker) walk_stmts(stmts []ast.Stmt, mod_name string) {
for stmt in stmts {
w.walk_stmt(stmt, mod_name)
}
}
fn (mut w Walker) walk_stmt(stmt ast.Stmt, mod_name string) {
if !stmt_ok(stmt) {
return
}
match stmt {
ast.AssertStmt {
w.walk_expr(stmt.expr, mod_name)
w.walk_expr(stmt.extra, mod_name)
}
ast.AssignStmt {
for i, rhs in stmt.rhs {
if i < stmt.lhs.len {
w.mark_assignment_interface_conversion(stmt.lhs[i], rhs, mod_name)
}
}
for expr in stmt.lhs {
w.walk_expr(expr, mod_name)
}
for expr in stmt.rhs {
w.mark_fn_value_expr(expr, mod_name)
w.walk_expr(expr, mod_name)
}
}
ast.BlockStmt {
w.walk_stmts(stmt.stmts, mod_name)
}
ast.ComptimeStmt {
w.mark_comptime_method_loop_receivers(stmt.stmt, mod_name)
w.walk_stmt(stmt.stmt, mod_name)
}
ast.ConstDecl {
for field in stmt.fields {
w.mark_fn_value_expr(field.value, mod_name)
w.walk_expr(field.value, mod_name)
}
}
ast.DeferStmt {
w.walk_stmts(stmt.stmts, mod_name)
}
ast.EnumDecl {
for field in stmt.fields {
w.walk_expr(field.value, mod_name)
}
}
ast.ExprStmt {
w.walk_expr(stmt.expr, mod_name)
}
ast.ForInStmt {
w.walk_expr(stmt.key, mod_name)
w.walk_expr(stmt.value, mod_name)
w.walk_expr(stmt.expr, mod_name)
}
ast.ForStmt {
w.walk_stmt(stmt.init, mod_name)
w.walk_expr(stmt.cond, mod_name)
w.walk_stmt(stmt.post, mod_name)
w.walk_stmts(stmt.stmts, mod_name)
}
ast.GlobalDecl {
for field in stmt.fields {
w.walk_expr(field.typ, mod_name)
w.walk_expr(field.value, mod_name)
}
}
ast.LabelStmt {
w.walk_stmt(stmt.stmt, mod_name)
}
ast.ReturnStmt {
for expr in stmt.exprs {
w.mark_fn_value_expr(expr, mod_name)
w.walk_expr(expr, mod_name)
// If the function returns an interface type and the return expr
// is a concrete struct InitExpr, mark the concrete type's
// interface methods so the vtable wrapper can call them.
w.mark_interface_conversion_methods(w.cur_fn_decl.typ.return_type, expr, mod_name)
w.mark_result_error_return_methods(w.cur_fn_decl.typ.return_type, expr, mod_name)
}
}
ast.StructDecl {
for field in stmt.fields {
w.walk_expr(field.typ, mod_name)
w.walk_expr(field.value, mod_name)
}
}
ast.TypeDecl {
w.walk_expr(stmt.base_type, mod_name)
for variant in stmt.variants {
w.walk_expr(variant, mod_name)
}
}
[]ast.Attribute {
attrs := stmt as []ast.Attribute
for attr in attrs {
w.walk_expr(attr.value, mod_name)
w.walk_expr(attr.comptime_cond, mod_name)
}
}
else {}
}
}
fn (mut w Walker) walk_expr(expr ast.Expr, mod_name string) {
if !expr_ok(expr) {
return
}
match expr {
ast.ArrayInitExpr {
w.walk_expr(expr.typ, mod_name)
for item in expr.exprs {
w.mark_fn_value_expr(item, mod_name)
w.walk_expr(item, mod_name)
}
w.mark_fn_value_expr(expr.init, mod_name)
w.walk_expr(expr.init, mod_name)
w.walk_expr(expr.cap, mod_name)
w.walk_expr(expr.len, mod_name)
w.walk_expr(expr.update_expr, mod_name)
}
ast.AsCastExpr {
w.walk_expr(expr.expr, mod_name)
w.walk_expr(expr.typ, mod_name)
}
ast.AssocExpr {
w.walk_expr(expr.typ, mod_name)
w.walk_expr(expr.expr, mod_name)
for field in expr.fields {
w.mark_fn_value_expr(field.value, mod_name)
w.walk_expr(field.value, mod_name)
}
}
ast.CallExpr {
w.mark_call_lhs(expr.lhs, mod_name)
w.mark_call_arg_interface_conversions(expr, mod_name)
w.walk_expr(expr.lhs, mod_name)
for arg in expr.args {
w.mark_fn_value_expr(arg, mod_name)
w.walk_expr(arg, mod_name)
}
}
ast.CallOrCastExpr {
if expr.lhs is ast.Ident && w.is_cast_type_name(expr.lhs.name) {
w.mark_interface_conversion_methods(expr.lhs, expr.expr, mod_name)
w.walk_expr(expr.expr, mod_name)
return
}
w.mark_call_lhs(expr.lhs, mod_name)
w.mark_call_or_cast_arg_interface_conversion(expr, mod_name)
w.walk_expr(expr.lhs, mod_name)
w.mark_fn_value_expr(expr.expr, mod_name)
w.walk_expr(expr.expr, mod_name)
}
ast.CastExpr {
w.mark_interface_conversion_methods(expr.typ, expr.expr, mod_name)
w.walk_expr(expr.typ, mod_name)
w.walk_expr(expr.expr, mod_name)
}
ast.ComptimeExpr {
w.walk_expr(expr.expr, mod_name)
}
ast.FnLiteral {
w.walk_stmts(expr.stmts, mod_name)
}
ast.GenericArgs {
w.walk_expr(expr.lhs, mod_name)
for arg in expr.args {
w.walk_expr(arg, mod_name)
}
}
ast.GenericArgOrIndexExpr {
w.walk_expr(expr.lhs, mod_name)
w.walk_expr(expr.expr, mod_name)
}
ast.Ident {
w.mark_ierror_wrapper_dependencies(expr.name, mod_name)
if (!w.opts.minimal_runtime_roots || should_mark_ident_as_fn(expr.name))
&& !w.is_cast_type_name(expr.name) {
w.mark_fn_name(expr.name, mod_name)
}
}
ast.FieldInit {
if expr.value is ast.SelectorExpr && expr.value.lhs !is ast.EmptyExpr {
w.mark_selector_fn_value_with_fallback(expr.value, mod_name, true)
}
w.mark_fn_value_expr(expr.value, mod_name)
w.walk_expr(expr.value, mod_name)
}
ast.IfExpr {
w.walk_expr(expr.cond, mod_name)
w.walk_stmts(expr.stmts, mod_name)
w.walk_expr(expr.else_expr, mod_name)
}
ast.IfGuardExpr {
w.walk_stmt(ast.Stmt(expr.stmt), mod_name)
}
ast.IndexExpr {
w.walk_expr(expr.lhs, mod_name)
w.walk_expr(expr.expr, mod_name)
}
ast.InfixExpr {
w.mark_infix_operator_method(expr, mod_name)
w.walk_expr(expr.lhs, mod_name)
w.walk_expr(expr.rhs, mod_name)
}
ast.InitExpr {
w.mark_interface_conversion_methods(expr.typ, expr, mod_name)
w.walk_expr(expr.typ, mod_name)
for field in expr.fields {
if field.value is ast.SelectorExpr
&& w.init_field_is_fn_value(expr.typ, field.name, mod_name) {
w.mark_selector_fn_value_with_fallback(field.value, mod_name, true)
}
w.mark_fn_value_expr(field.value, mod_name)
w.walk_expr(field.value, mod_name)
}
}
ast.KeywordOperator {
for value in expr.exprs {
w.walk_expr(value, mod_name)
}
}
ast.LambdaExpr {
w.walk_expr(expr.expr, mod_name)
}
ast.LockExpr {
for lock_expr in expr.lock_exprs {
w.walk_expr(lock_expr, mod_name)
}
for lock_expr in expr.rlock_exprs {
w.walk_expr(lock_expr, mod_name)
}
w.walk_stmts(expr.stmts, mod_name)
}
ast.MapInitExpr {
w.walk_expr(expr.typ, mod_name)
for key in expr.keys {
w.mark_fn_value_expr(key, mod_name)
w.walk_expr(key, mod_name)
}
for value in expr.vals {
w.mark_fn_value_expr(value, mod_name)
w.walk_expr(value, mod_name)
}
}
ast.MatchExpr {
w.walk_expr(expr.expr, mod_name)
for branch in expr.branches {
for cond in branch.cond {
w.walk_expr(cond, mod_name)
}
w.walk_stmts(branch.stmts, mod_name)
}
}
ast.ModifierExpr {
w.walk_expr(expr.expr, mod_name)
}
ast.OrExpr {
w.walk_expr(expr.expr, mod_name)
w.walk_stmts(expr.stmts, mod_name)
}
ast.ParenExpr {
w.walk_expr(expr.expr, mod_name)
}
ast.PostfixExpr {
w.walk_expr(expr.expr, mod_name)
}
ast.PrefixExpr {
w.walk_expr(expr.expr, mod_name)
}
ast.RangeExpr {
w.walk_expr(expr.start, mod_name)
w.walk_expr(expr.end, mod_name)
}
ast.SelectExpr {
w.walk_stmt(expr.stmt, mod_name)
w.walk_stmts(expr.stmts, mod_name)
w.walk_expr(expr.next, mod_name)
}
ast.SelectorExpr {
w.mark_selector_fn_value(expr, mod_name)
w.walk_expr(expr.lhs, mod_name)
}
ast.SqlExpr {
w.walk_expr(expr.expr, mod_name)
}
ast.StringInterLiteral {
for inter in expr.inters {
w.mark_string_interpolation_str_dependency(inter.expr, mod_name)
w.walk_expr(inter.expr, mod_name)
w.walk_expr(inter.format_expr, mod_name)
}
}
ast.Tuple {
for value in expr.exprs {
w.mark_fn_value_expr(value, mod_name)
w.walk_expr(value, mod_name)
}
}
ast.UnsafeExpr {
w.walk_stmts(expr.stmts, mod_name)
}
ast.Type {
match expr {
ast.ArrayFixedType {
w.walk_expr(expr.len, mod_name)
w.walk_expr(expr.elem_type, mod_name)
}
ast.ArrayType {
w.walk_expr(expr.elem_type, mod_name)
}
ast.ChannelType {
w.walk_expr(expr.cap, mod_name)
w.walk_expr(expr.elem_type, mod_name)
}
ast.FnType {
for param in expr.params {
w.walk_expr(param.typ, mod_name)
}
w.walk_expr(expr.return_type, mod_name)
}
ast.GenericType {
w.walk_expr(expr.name, mod_name)
for param in expr.params {
w.walk_expr(param, mod_name)
}
}
ast.MapType {
w.walk_expr(expr.key_type, mod_name)
w.walk_expr(expr.value_type, mod_name)
}
ast.OptionType {
w.walk_expr(expr.base_type, mod_name)
}
ast.PointerType {
w.walk_expr(expr.base_type, mod_name)
}
ast.ResultType {
w.walk_expr(expr.base_type, mod_name)
}
ast.ThreadType {
w.walk_expr(expr.elem_type, mod_name)
}
ast.TupleType {
for typ in expr.types {
w.walk_expr(typ, mod_name)
}
}
ast.AnonStructType {
for embedded in expr.embedded {
w.walk_expr(embedded, mod_name)
}
for field in expr.fields {
w.walk_expr(field.typ, mod_name)
w.walk_expr(field.value, mod_name)
}
}
else {}
}
}
else {}
}
}