module markused import v3.flat import v3.types const trace_markused = false pub fn mark_used(a &flat.FlatAst, tc &types.TypeChecker) map[string]bool { mut cur_module := '' mut imports := map[string]string{} mut fn_decls := map[string]FnDeclInfo{} mut struct_decls := map[string]StructDeclInfo{} mut const_decls := map[string]ConstDeclInfo{} // Reverse index: short name (after last '.') -> list of full qualified names mut suffix_map := map[string][]string{} mut fn_count := 0 mut fn_with_dot := 0 mut contains2_total := 0 for node_idx, node in a.nodes { if node.kind == .module_decl { cur_module = node.value continue } if node.kind == .import_decl { mod := if node.value.contains('.') { node.value.all_after_last('.') } else { node.value } imports[node.typ] = mod continue } if node.kind == .struct_decl { full_name := qualify_fn(cur_module, node.value) info := StructDeclInfo{ node_id: flat.NodeId(node_idx) module: cur_module } struct_decls[full_name] = info if node.value !in struct_decls { struct_decls[node.value] = info } continue } if node.kind == .const_decl { for i in 0 .. node.children_count { field_id := a.child(&node, i) field := a.node(field_id) if field.kind != .const_field || field.children_count == 0 { continue } info := ConstDeclInfo{ expr_id: a.child(field, 0) module: cur_module } const_decls[field.value] = info full_name := qualify_fn(cur_module, field.value) if full_name != field.value { const_decls[full_name] = info } } continue } if node.kind == .fn_decl { has_dot := node.value.contains('.') if trace_markused { fn_count++ if has_dot { fn_with_dot++ if fn_with_dot <= 5 { eprintln(' fn with dot: "${node.value}"') } } } info := FnDeclInfo{ node_id: flat.NodeId(node_idx) module: cur_module } fn_decls[node.value] = info lowered_name := markused_c_name(node.value) if lowered_name != node.value { fn_decls[lowered_name] = info } qname := qualify_fn(cur_module, node.value) if qname != node.value { fn_decls[qname] = info lowered_qname := markused_c_name(qname) if lowered_qname != qname { fn_decls[lowered_qname] = info } } // Build suffix_map entries if has_dot { if trace_markused { contains2_total++ } short := node.value.all_after_last('.') add_suffix_candidate(mut suffix_map, short, node.value) if qname != node.value { add_suffix_candidate(mut suffix_map, short, qname) } } if qname != node.value && qname.contains('.') { short := qname.all_after_last('.') add_suffix_candidate(mut suffix_map, short, qname) } } } // BFS from main mut used := map[string]bool{} mut queue := []string{} queue << 'main' used['main'] = true enqueue_main_module_roots(fn_decls, mut used, mut queue) enqueue_auto_roots(fn_decls, mut used, mut queue) queue << 'time.Time.new' used['time.Time.new'] = true used['Time.new'] = true queue << 'gen_expr_lvalue' used['gen_expr_lvalue'] = true queue << 'c.gen_expr_lvalue' used['c.gen_expr_lvalue'] = true queue << 'gen_assign' used['gen_assign'] = true queue << 'c.gen_assign' used['c.gen_assign'] = true for seed in ['__new_array', 'new_array_from_c_array', 'array.get', 'array.set', 'array.push', 'array.push_many', 'array.slice', 'array.clone', 'array.delete', 'array.ensure_cap', 'string.==', 'string.<', 'string.free', 'string.all_before', 'string.all_before_last', 'string.all_after', 'string.all_after_last', 'u8.vstring', '[]rune.string', 'map.set', 'map.exists', 'map.get', 'map.get_check', 'map.get_and_set', 'map.delete', 'map.clone', 'map.clear', 'memdup', 'strings.Builder.write_ptr', 'strings.Builder.write_runes', 'strings.Builder.free', 'strconv.format_int', 'strconv.format_uint', 'bool.str', 'ptr_str', 'strconv__f32_to_str_l', 'strconv__f64_to_str_l', 'sync.new_channel_st', 'sync.Channel.push', 'sync.Channel.pop', 'sync.Channel.close', 'new_channel_st', 'Channel.push', 'Channel.pop', 'Channel.close'] { queue << seed used[seed] = true } if trace_markused { eprintln('markused: fn_count:') eprintln(fn_count.str()) eprintln('fn_with_dot:') eprintln(fn_with_dot.str()) eprintln('contains2_total:') eprintln(contains2_total.str()) eprintln('markused: main in fn_decls: ${'main' in fn_decls}') eprintln('markused: fn_decls count: ${fn_decls.len}') eprintln('markused: suffix_map count: ${suffix_map.len}') mut total_suffix_entries := 0 for _, vals in suffix_map { total_suffix_entries += vals.len } eprintln('total suffix_map entries (sum of array lens):') eprintln(total_suffix_entries.str()) } mut suffix_hits := 0 // mut suffix_misses := 0 mut in_cg := 0 mut not_in_cg := 0 mut total_callees := 0 collector := CallCollector{ a: a tc: tc fn_decls: fn_decls struct_decls: struct_decls const_decls: const_decls } enqueue_detected_runtime_helpers(a, tc, mut used, mut queue) enqueue_function_value_selectors(a, fn_decls, mut used, mut queue) enqueue_initializer_calls(a, collector, imports, fn_decls, mut used, mut queue) // Interface dispatch reachability: calling an interface method `Foo.m` may // dispatch to any concrete `T.m` for a type `T` that implements `Foo`. Those // concrete methods are only referenced from the generated dispatch switch, so // without this they would be pruned and produce undefined-symbol errors. mut iface_impls := map[string][]string{} mut checked_iface_impls := map[string]bool{} mut processed_nodes := []bool{len: a.nodes.len} mut calls := []string{cap: 128} mut qi := 0 for qi < queue.len { name := queue[qi] qi++ prev_len := queue.len fn_info := fn_decls[name] or { not_in_cg++ if trace_markused && qi <= 10 { eprintln('BFS qi=${qi.str()} name="${name}" in_cg=false') } continue } if trace_markused && qi <= 10 { eprintln('BFS qi=${qi.str()} name="${name}" in_cg=true') } in_cg++ node_key := int(fn_info.node_id) if node_key < 0 || node_key >= processed_nodes.len { continue } if processed_nodes[node_key] { continue } processed_nodes[node_key] = true calls.clear() node := a.node(fn_info.node_id) receiver_name, receiver_struct := receiver_info(a, node) collector.collect_calls(node, fn_info.module, imports, receiver_name, receiver_struct, mut calls) total_callees += calls.len for callee in calls { if !valid_symbol_name(callee) { continue } mut found_direct := false if callee_info := fn_decls[callee] { found_direct = true if enqueue(callee, mut used, mut queue) { if trace_markused && qi == 1 { eprintln('main: all_fns hit: "${callee}"') } } alias := a.node(callee_info.node_id).value if alias != callee && alias !in used { used[alias] = true } } else if callee in tc.fn_ret_types { found_direct = true if enqueue(callee, mut used, mut queue) { if trace_markused && qi == 1 { eprintln('main: resolved hit: "${callee}"') } } } if !found_direct || !callee.contains('.') { short := callee.all_after_last('.') if suffix_candidates := suffix_map[short] { for candidate in suffix_candidates { if candidate in fn_decls || candidate in tc.fn_ret_types { if enqueue(candidate, mut used, mut queue) { suffix_hits++ } } } } } if callee.contains('.') { recv := callee.all_before_last('.') method := callee.all_after_last('.') ensure_iface_impls(recv, tc, mut iface_impls, mut checked_iface_impls) if impls := iface_impls[recv] { for impl in impls { impl_method := '${impl}.${method}' enqueue(impl_method, mut used, mut queue) short_impl := '${impl.all_after_last('.')}.${method}' if short_impl != impl_method { enqueue(short_impl, mut used, mut queue) } } } } } new_added := queue.len - prev_len if trace_markused && qi <= 10 { eprintln(' -> added ${new_added.str()} new entries, queue now ${queue.len.str()}') } } if trace_markused { eprintln('total_callees:') eprintln(total_callees.str()) eprintln('markused: in_cg:') eprintln(in_cg.str()) eprintln('not_in_cg:') eprintln(not_in_cg.str()) eprintln('queue.len:') eprintln(queue.len.str()) eprintln('markused: suffix_hits:') eprintln(suffix_hits.str()) eprintln('markused: total used: ${used.len}') } return used } fn ensure_iface_impls(recv string, tc &types.TypeChecker, mut iface_impls map[string][]string, mut checked map[string]bool) { if recv.len == 0 || recv in checked { return } checked[recv] = true mut iface_name := '' if recv in tc.interface_names { iface_name = recv } else { for name, _ in tc.interface_names { if name.all_after_last('.') == recv { iface_name = name break } } } if iface_name.len == 0 { return } mut impls := []string{} for struct_name, _ in tc.structs { if tc.named_type_implements_interface(struct_name, iface_name) { impls << struct_name } } iface_impls[recv] = impls if iface_name != recv { iface_impls[iface_name] = impls checked[iface_name] = true } } fn add_suffix_candidate(mut suffix_map map[string][]string, short string, name string) { if !valid_symbol_name(short) || !valid_symbol_name(name) { return } mut candidates := suffix_map[short] or { []string{} } candidates << name suffix_map[short] = candidates } fn valid_symbol_name(name string) bool { return name.len > 0 && name.len < 512 } fn enqueue_initializer_calls(a &flat.FlatAst, collector CallCollector, imports map[string]string, fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) { mut cur_module := '' mut calls := []string{cap: 32} for node in a.nodes { match node.kind { .module_decl { cur_module = node.value } .const_decl, .global_decl { for i in 0 .. node.children_count { field := a.child_node(&node, i) if field.children_count == 0 { continue } calls.clear() collector.collect_calls(field, cur_module, imports, '', '', mut calls) for callee in calls { if callee_info := fn_decls[callee] { enqueue(callee, mut used, mut queue) alias := a.node(callee_info.node_id).value if alias != callee && alias !in used { used[alias] = true } } else { enqueue(callee, mut used, mut queue) } } } } else {} } } } fn enqueue(name string, mut used map[string]bool, mut queue []string) bool { if !valid_symbol_name(name) { return false } if name in used { return false } used[name] = true queue << name return true } struct FnDeclInfo { node_id flat.NodeId module string } struct StructDeclInfo { node_id flat.NodeId module string } struct ConstDeclInfo { expr_id flat.NodeId module string } struct CallCollector { a &flat.FlatAst = unsafe { nil } tc &types.TypeChecker = unsafe { nil } fn_decls map[string]FnDeclInfo struct_decls map[string]StructDeclInfo const_decls map[string]ConstDeclInfo } fn enqueue_auto_roots(fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) { for name, info in fn_decls { if !is_auto_root_fn(name) { continue } enqueue(name, mut used, mut queue) short_name := name.all_after_last('.') if short_name != name { enqueue(short_name, mut used, mut queue) } qualified_name := qualify_fn(info.module, short_name) if qualified_name != name { enqueue(qualified_name, mut used, mut queue) } } } fn enqueue_main_module_roots(fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) { for name, info in fn_decls { if info.module != 'main' || name.contains('.') { continue } enqueue(name, mut used, mut queue) } } fn is_auto_root_fn(name string) bool { short_name := name.all_after_last('.') return short_name == 'init' || short_name == 'testsuite_begin' || short_name == 'testsuite_end' || short_name.starts_with('test_') } fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) { mut needs_optional_helpers := false mut needs_string_interp_helpers := false mut needs_string_plus_helper := false mut needs_string_membership_helpers := false mut needs_new_map := false mut cur_module := '' for node in a.nodes { match node.kind { .module_decl { cur_module = node.value } .fn_decl { ret_type := tc.parse_type(node.typ) if ret_type is types.OptionType || ret_type is types.ResultType { needs_optional_helpers = true } } .param, .field_decl, .field_init, .const_field { if type_string_needs_optional_helpers(node.typ) { needs_optional_helpers = true } } .none_expr { needs_optional_helpers = true } .or_expr { needs_optional_helpers = true if node.children_count > 0 { expr_id := a.child(&node, 0) expr_type := tc.expr_type(expr_id) or { tc.resolve_type(expr_id) } if type_needs_zero_map(expr_type) { needs_new_map = true } } } .call { if node.children_count > 0 { fn_node := a.child_node(&node, 0) if fn_node.kind == .ident && (fn_node.value == 'error' || fn_node.value == 'error_with_code') { needs_optional_helpers = true } if fn_node.kind == .ident && fn_node.value in ['print', 'println', 'eprint', 'eprintln'] && node.children_count >= 2 { enqueue_stringified_custom_str_method(a.child(&node, 1), cur_module, tc, mut used, mut queue) } } } .string_interp { needs_string_interp_helpers = true needs_string_plus_helper = true for i in 0 .. node.children_count { enqueue_stringified_custom_str_method(a.child(&node, i), cur_module, tc, mut used, mut queue) } } .assign { if node.op == .plus_assign && node.children_count == 2 { lhs_id := a.child(&node, 0) rhs_id := a.child(&node, 1) rhs := a.node(rhs_id) lhs_type := markused_membership_container_type(tc, tc.resolve_type(lhs_id)) rhs_type := markused_membership_container_type(tc, tc.resolve_type(rhs_id)) if lhs_type == 'string' || rhs_type == 'string' || rhs.kind in [.string_literal, .string_interp] { needs_string_plus_helper = true } } } .in_expr { if node.children_count >= 2 { rhs_id := a.child(&node, 1) rhs_type := markused_membership_container_type(tc, tc.resolve_type(rhs_id)) if rhs_type == 'string' { needs_string_membership_helpers = true } } } .map_init { needs_new_map = true } else {} } } if needs_optional_helpers { for helper in ['IError.str', 'error', 'error_with_code'] { enqueue(helper, mut used, mut queue) } } if needs_string_interp_helpers { for helper in ['strings.new_builder', 'strings.Builder.write_string', 'strings.Builder.str', 'string_plus_many'] { enqueue(helper, mut used, mut queue) } } if needs_string_plus_helper { enqueue('string__plus', mut used, mut queue) } if needs_string_membership_helpers { for helper in ['string__contains', 'string__contains_u8'] { enqueue(helper, mut used, mut queue) } } if needs_new_map { enqueue('new_map', mut used, mut queue) } } fn enqueue_stringified_custom_str_method(expr_id flat.NodeId, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) { mut typ := tc.expr_type(expr_id) or { tc.resolve_type(expr_id) } for _ in 0 .. 8 { if typ is types.Alias { typ = typ.base_type continue } if typ is types.OptionType { typ = typ.base_type continue } if typ is types.ResultType { typ = typ.base_type continue } break } match typ { types.Enum { enqueue_enum_str_method(typ.name, cur_module, tc, mut used, mut queue) } types.Struct { enqueue_structlike_str_method(typ.name, cur_module, tc, mut used, mut queue) } types.SumType { enqueue_structlike_str_method(typ.name, cur_module, tc, mut used, mut queue) } else {} } } fn enqueue_enum_str_method(type_name string, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) { for candidate in stringification_type_candidates(type_name, cur_module) { method := '${candidate}.str' if method in tc.fn_ret_types { enqueue(method, mut used, mut queue) lowered := markused_c_name(method) if lowered != method { enqueue(lowered, mut used, mut queue) } } } } fn enqueue_structlike_str_method(type_name string, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) { for candidate in stringification_type_candidates(type_name, cur_module) { lowered := '${markused_c_name(candidate)}__str' if lowered in tc.fn_ret_types { enqueue(lowered, mut used, mut queue) } method := '${candidate}.str' if method in tc.fn_ret_types { enqueue(method, mut used, mut queue) } } } fn stringification_type_candidates(type_name string, cur_module string) []string { if type_name.len == 0 { return []string{} } mut candidates := []string{cap: 2} candidates << type_name if !type_name.contains('.') && cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' { candidates << '${cur_module}.${type_name}' } return candidates } fn enqueue_function_value_selectors(a &flat.FlatAst, fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) { for node in a.nodes { if node.kind == .selector && node.children_count > 0 && node.value.len > 0 { base := a.child_node(&node, 0) if base.kind == .ident && base.value.len > 0 { name := '${base.value}.${node.value}' if name in fn_decls { enqueue(name, mut used, mut queue) } } } else if node.kind == .call && node.children_count > 0 { callee := a.child_node(&node, 0) if callee.kind == .ident && callee.value in fn_decls { enqueue(callee.value, mut used, mut queue) } } } } fn type_string_needs_optional_helpers(typ string) bool { return typ.len > 0 && (typ[0] == `?` || typ[0] == `!`) } fn type_needs_zero_map(typ types.Type) bool { mut clean := typ for _ in 0 .. 8 { if clean is types.Alias { clean = clean.base_type continue } if clean is types.OptionType { clean = clean.base_type continue } if clean is types.ResultType { clean = clean.base_type continue } break } return clean is types.Map } fn markused_membership_container_type(tc &types.TypeChecker, typ types.Type) string { mut clean := typ.name().trim_space() for { if clean.starts_with('shared ') { clean = clean[7..].trim_space() continue } if clean.starts_with('&') { clean = clean[1..].trim_space() continue } if clean in tc.type_aliases { alias := tc.type_aliases[clean].trim_space() if alias == clean { break } clean = alias continue } break } return clean } fn qualify_fn(mod string, name string) string { if mod.len == 0 || mod == 'main' || mod == 'builtin' { return name } return '${mod}.${name}' } fn receiver_info(a &flat.FlatAst, node &flat.Node) (string, string) { mut receiver_struct := '' if node.value.contains('.') { receiver_struct = node.value.all_before_last('.') } for pi in 0 .. node.children_count { pc := a.child_node(node, pi) if pc.kind == .param { if receiver_struct.len > 0 { return pc.value, receiver_struct } clean_type := pc.typ.trim_left('&') if pc.value.len > 0 && clean_type.len > 0 && clean_type[0] >= `A` && clean_type[0] <= `Z` { return pc.value, clean_type } } } if receiver_struct.len == 0 { return '', '' } return '', receiver_struct } fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports map[string]string, receiver_name string, receiver_struct string, mut calls []string) { mut stack := []flat.NodeId{cap: int(node.children_count)} for i in 0 .. node.children_count { child_id := c.a.child(node, i) if int(child_id) >= 0 { stack << child_id } } for stack.len > 0 { child_id := stack.pop() child := &c.a.nodes[int(child_id)] match child.kind { .ident { c.collect_fn_value_ident(child_id, child.value, cur_module, imports, mut calls) } .selector { c.collect_fn_value_selector(child_id, child, cur_module, imports, mut calls) } .call { if resolved := c.tc.resolved_call_name(child_id) { calls << resolved } if child.children_count > 0 { callee_id := c.a.child(child, 0) if int(callee_id) >= 0 { callee := c.a.nodes[int(callee_id)] if callee.kind == .ident && callee.value.len > 0 { calls << callee.value qcallee := qualify_fn(cur_module, callee.value) if qcallee != callee.value { calls << qcallee } } else if callee.kind == .selector && callee.value.len > 0 { if callee.children_count > 0 { base_id := c.a.child(&callee, 0) if int(base_id) >= 0 { base := c.a.nodes[int(base_id)] if base.kind == .ident && base.value.len > 0 { if receiver_name.len > 0 && base.value == receiver_name { calls << receiver_struct + '.' + callee.value qrecv := qualify_fn(cur_module, receiver_struct + '.' + callee.value) if qrecv != receiver_struct + '.' + callee.value { calls << qrecv } } mod_name := if base.value in imports { imports[base.value] } else { base.value } calls << mod_name + '.' + callee.value calls << qualify_fn(cur_module, base.value + '.' + callee.value) if base.value.len > 0 && base.value[0] >= `A` && base.value[0] <= `Z` { named_type := c.tc.parse_type(base.value) named_type_name := resolve_type_name(named_type) if named_type_name.len > 0 { calls << named_type_name + '.' + callee.value } } } else if base.kind == .selector && base.children_count > 0 { inner_id := c.a.child(&base, 0) if int(inner_id) >= 0 { inner := c.a.nodes[int(inner_id)] if inner.kind == .ident && inner.value.len > 0 { mod_name := if inner.value in imports { imports[inner.value] } else { inner.value } calls << mod_name + '.' + base.value + '.' + callee.value } } } base_type := c.node_type(base_id) type_name := resolve_type_name(base_type) if type_name.len > 0 { calls << type_name + '.' + callee.value } } } calls << callee.value } } } for ci in 1 .. child.children_count { arg_id := c.a.child(child, ci) if int(arg_id) >= 0 { arg := c.a.nodes[int(arg_id)] if arg.kind == .ident && arg.value.len > 0 { calls << arg.value } } } } .prefix { if child.op == .amp && child.children_count > 0 { inner_id := c.a.child(child, 0) if int(inner_id) >= 0 { inner := c.a.nodes[int(inner_id)] if inner.kind == .struct_init { calls << 'memdup' } } } } .string_interp { calls << 'string_plus_many' } .infix { if child.op == .plus { calls << 'string__plus' } if child.children_count >= 2 { lhs_id := c.a.child(child, 0) rhs_id := c.a.child(child, 1) if child.op in [.dot, .amp] { lhs_name := c.qualified_expr_name(lhs_id) if lhs_name.len > 0 { rhs := c.a.nodes[int(rhs_id)] if rhs.kind == .call && rhs.children_count > 0 { fn_node := c.a.child_node(&rhs, 0) if fn_node.kind == .ident && fn_node.value.len > 0 { mod_name := if lhs_name in imports { imports[lhs_name] } else { lhs_name } calls << mod_name + '.' + fn_node.value calls << qualify_fn(cur_module, lhs_name + '.' + fn_node.value) } } } } if int(lhs_id) >= 0 { c.collect_struct_operator_call(lhs_id, child.op, cur_module, mut calls) } } } .or_expr { if child.children_count > 0 { expr_id := c.a.child(child, 0) c.collect_zero_struct_default_calls(c.node_type(expr_id), cur_module, imports, mut calls) } } .struct_init { c.collect_struct_default_calls(child, cur_module, imports, mut calls) } else {} } if child.children_count > 0 { mut j := int(child.children_count) - 1 for j >= 0 { next_id := c.a.child(child, j) if int(next_id) >= 0 { stack << next_id } j-- } } } } fn (c &CallCollector) collect_struct_operator_call(lhs_id flat.NodeId, op flat.Op, cur_module string, mut calls []string) { lhs_type := c.node_type(lhs_id) lhs_name := resolve_type_name(lhs_type) struct_type := c.struct_lookup_name(lhs_name, cur_module) if struct_type.len == 0 { return } method_name := c.struct_operator_call_name(struct_type, op) or { return } c.add_operator_call_name(method_name, mut calls) } fn (c &CallCollector) struct_operator_call_name(struct_type string, op flat.Op) ?string { if op_name := markused_struct_operator_symbol(op) { if method_name := c.struct_operator_fn_name(struct_type, op_name) { return method_name } } match op { .gt, .ge, .le { if method_name := c.struct_operator_fn_name(struct_type, '<') { return method_name } } .ne { if method_name := c.struct_operator_fn_name(struct_type, '==') { return method_name } } else {} } return none } fn markused_struct_operator_symbol(op flat.Op) ?string { match op { .plus { return '+' } .minus { return '-' } .mul { return '*' } .div { return '/' } .mod { return '%' } .eq { return '==' } .ne { return '!=' } .lt { return '<' } .gt { return '>' } .le { return '<=' } .ge { return '>=' } else {} } return none } fn (c &CallCollector) struct_operator_fn_name(struct_type string, op_name string) ?string { method_name := '${struct_type}.${op_name}' if c.is_known_fn_name(method_name) { return method_name } cmethod_name := markused_c_name(method_name) if c.is_known_fn_name(cmethod_name) { return cmethod_name } return none } fn (c &CallCollector) is_known_fn_name(name string) bool { return name in c.fn_decls || name in c.tc.fn_ret_types || name in c.tc.fn_param_types } fn (c &CallCollector) struct_lookup_name(type_name string, cur_module string) string { if type_name.len == 0 { return '' } if type_name.contains('.') { if type_name in c.struct_decls { return type_name } short_type := type_name.all_after_last('.') type_mod := type_name.all_before_last('.') if info := c.struct_decls[short_type] { if info.module == type_mod { return short_type } } if info := c.struct_decls[type_name] { if info.module == type_mod { return type_name } } return '' } if info := c.struct_decls[type_name] { if info.module.len == 0 || info.module == cur_module { return type_name } } if cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' { qtype := '${cur_module}.${type_name}' if qtype in c.struct_decls { return qtype } } return '' } fn (c &CallCollector) add_operator_call_name(method_name string, mut calls []string) { calls << method_name lowered := markused_c_name(method_name) if lowered != method_name { calls << lowered } } fn (c &CallCollector) qualified_expr_name(id flat.NodeId) string { if int(id) < 0 { return '' } node := c.a.nodes[int(id)] match node.kind { .ident { return node.value } .selector { if node.children_count == 0 { return node.value } base := c.qualified_expr_name(c.a.child(&node, 0)) if base.len == 0 { return node.value } return base + '.' + node.value } else { return '' } } } fn (c &CallCollector) collect_fn_value_ident(id flat.NodeId, name string, cur_module string, imports map[string]string, mut calls []string) { if name.len == 0 || !c.name_may_reference_fn(name, cur_module, imports) { return } has_fn_decl := c.name_has_fn_decl(name, cur_module, imports) if !has_fn_decl && !c.node_is_fn_value(id) { return } c.add_fn_value_candidates(name, cur_module, imports, mut calls) c.add_const_alias_candidates(name, cur_module, imports, mut calls) } fn (c &CallCollector) collect_fn_value_selector(id flat.NodeId, node &flat.Node, cur_module string, imports map[string]string, mut calls []string) { if node.children_count == 0 { return } base := c.a.child_node(node, 0) if base.kind != .ident || base.value.len == 0 || node.value.len == 0 { return } name := '${base.value}.${node.value}' if !c.name_may_reference_fn(name, cur_module, imports) { return } has_fn_decl := c.name_has_fn_decl(name, cur_module, imports) if !has_fn_decl && !c.node_is_fn_value(id) { return } c.add_fn_value_candidates(name, cur_module, imports, mut calls) c.add_const_alias_candidates(name, cur_module, imports, mut calls) if base.value in imports { mod_name := imports[base.value] resolved_name := '${mod_name}.${node.value}' c.add_fn_value_candidates(resolved_name, cur_module, imports, mut calls) c.add_const_alias_candidates(resolved_name, cur_module, imports, mut calls) } } fn (c &CallCollector) name_may_reference_fn(name string, cur_module string, imports map[string]string) bool { return c.name_has_candidate_decl(name, cur_module, imports, true) } fn (c &CallCollector) name_has_fn_decl(name string, cur_module string, imports map[string]string) bool { return c.name_has_candidate_decl(name, cur_module, imports, false) } fn (c &CallCollector) name_has_candidate_decl(name string, cur_module string, imports map[string]string, include_consts bool) bool { if c.candidate_matches_decl(name, include_consts) { return true } qname := qualify_fn(cur_module, name) if qname != name && c.candidate_matches_decl(qname, include_consts) { return true } if name.contains('.') { base := name.all_before_last('.') member := name.all_after_last('.') if base in imports { imported_name := imports[base] + '.' + member if c.candidate_matches_decl(imported_name, include_consts) { return true } } } return false } fn (c &CallCollector) candidate_matches_decl(candidate string, include_consts bool) bool { if candidate in c.fn_decls { return true } return include_consts && candidate in c.const_decls } fn (c &CallCollector) node_is_fn_value(id flat.NodeId) bool { expr_type := c.node_type(id) return expr_type is types.FnType } fn (c &CallCollector) add_fn_value_candidates(name string, cur_module string, imports map[string]string, mut calls []string) { if name.len == 0 { return } calls << name qname := qualify_fn(cur_module, name) if qname != name { calls << qname } if name.contains('.') { base := name.all_before_last('.') member := name.all_after_last('.') if base in imports { calls << imports[base] + '.' + member } } } fn (c &CallCollector) add_const_alias_candidates(name string, cur_module string, imports map[string]string, mut calls []string) { c.add_const_alias_candidate(name, imports, mut calls) qname := qualify_fn(cur_module, name) if qname != name { c.add_const_alias_candidate(qname, imports, mut calls) } if name.contains('.') { base := name.all_before_last('.') member := name.all_after_last('.') if base in imports { c.add_const_alias_candidate(imports[base] + '.' + member, imports, mut calls) } } } fn (c &CallCollector) add_const_alias_candidate(const_name string, imports map[string]string, mut calls []string) { info := c.const_decls[const_name] or { return } expr := c.a.node(info.expr_id) match expr.kind { .ident { c.add_fn_value_candidates(expr.value, info.module, imports, mut calls) } .selector { if expr.children_count > 0 { base := c.a.child_node(expr, 0) if base.kind == .ident && base.value.len > 0 && expr.value.len > 0 { c.add_fn_value_candidates('${base.value}.${expr.value}', info.module, imports, mut calls) if base.value in imports { c.add_fn_value_candidates('${imports[base.value]}.${expr.value}', info.module, imports, mut calls) } } } } else {} } } fn (c &CallCollector) node_type(id flat.NodeId) types.Type { if t := c.tc.expr_type(id) { return t } return c.tc.resolve_type(id) } fn (c &CallCollector) collect_zero_struct_default_calls(typ types.Type, cur_module string, imports map[string]string, mut calls []string) { type_name := zero_value_struct_type_name(typ) if type_name.len == 0 { return } c.collect_struct_default_calls_for_type(type_name, cur_module, imports, mut calls) } fn zero_value_struct_type_name(typ types.Type) string { mut clean := typ for _ in 0 .. 8 { if clean is types.Alias { clean = clean.base_type continue } if clean is types.OptionType { clean = clean.base_type continue } if clean is types.ResultType { clean = clean.base_type continue } break } if clean is types.Struct { return clean.name } return '' } fn (c &CallCollector) collect_struct_default_calls(init &flat.Node, cur_module string, imports map[string]string, mut calls []string) { info := c.struct_decl_info(init.value, cur_module) or { return } mut set_fields := map[string]bool{} for i in 0 .. init.children_count { field := c.a.child_node(init, i) if field.kind == .field_init { set_fields[field.value] = true } } c.collect_struct_default_calls_from_info(info, set_fields, imports, mut calls) } fn (c &CallCollector) collect_struct_default_calls_for_type(type_name string, cur_module string, imports map[string]string, mut calls []string) { info := c.struct_decl_info(type_name, cur_module) or { return } c.collect_struct_default_calls_from_info(info, map[string]bool{}, imports, mut calls) } fn (c &CallCollector) struct_decl_info(type_name string, cur_module string) ?StructDeclInfo { struct_name := c.struct_lookup_name(type_name, cur_module) if struct_name.len == 0 { return none } return c.struct_decls[struct_name] or { none } } fn (c &CallCollector) collect_struct_default_calls_from_info(info StructDeclInfo, provided map[string]bool, imports map[string]string, mut calls []string) { node := c.a.node(info.node_id) for i in 0 .. node.children_count { field := c.a.child_node(node, i) if field.kind != .field_decl || field.children_count == 0 || field.value in provided { continue } c.collect_calls(field, info.module, imports, '', '', mut calls) } } fn resolve_type_name(t types.Type) string { if t is types.Alias { return t.name } else if t is types.Struct { return t.name } else if t is types.Interface { return t.name } else if t is types.SumType { return t.name } else if t is types.Enum { return t.name } else if t is types.String { return 'string' } else if t is types.Array { return 'Array' } else if t is types.Map { return 'map' } else if t is types.Pointer { return resolve_type_name(t.base_type) } else if t is types.Primitive { props := int(t.props) sz := int(t.size) if props & 1 > 0 { return 'bool' } if props & 4 > 0 { if props & 8 > 0 { return match sz { 8 { 'u8' } 16 { 'u16' } 32 { 'u32' } 64 { 'u64' } else { 'int' } } } return match sz { 0 { 'int' } 8 { 'i8' } 16 { 'i16' } 32 { 'i32' } 64 { 'i64' } else { 'int' } } } if props & 2 > 0 { return match sz { 32 { 'f32' } 64 { 'f64' } else { 'f64' } } } return 'int' } else if t is types.ISize { return 'isize' } else if t is types.USize { return 'usize' } else if t is types.Rune { return 'rune' } return '' } fn markused_c_name(name string) string { if name.starts_with('C.') { return name[2..] } if name == 'malloc' { return 'v_malloc' } if markused_c_name_is_plain(name) { return name } return name.replace('[]', 'Array_').replace('.-', '__minus').replace('.+', '__plus').replace('.==', '__eq').replace('.!=', '__ne').replace('.<=', '__le').replace('.>=', '__ge').replace('.<', '__lt').replace('.>', '__gt').replace('&', 'ptr').replace('[', '_').replace(']', '').replace(',', '_').replace(' ', '_').replace('.', '__') } fn markused_c_name_is_plain(name string) bool { for i in 0 .. name.len { c := name[i] if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`) || c == `_` { continue } return false } return true }