vq / vlib / v3 / markused / markused.v
3166 lines · 3010 sloc · 94.6 KB · 2b81918e62db9ab38787518603a7f93bee80868c
Raw
1module markused
2
3import v3.flat
4import v3.types
5
6const trace_markused = false
7
8// mark_used updates mark used state for markused.
9pub fn mark_used(a &flat.FlatAst, tc &types.TypeChecker) map[string]bool {
10 return mark_used_with_test_files(a, tc, map[string]bool{})
11}
12
13pub fn mark_used_for_tests(a &flat.FlatAst, tc &types.TypeChecker, test_files []string) map[string]bool {
14 mut file_map := map[string]bool{}
15 for file in test_files {
16 file_map[file] = true
17 }
18 return mark_used_with_test_files(a, tc, file_map)
19}
20
21fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files map[string]bool) map[string]bool {
22 mut cur_module := ''
23 mut imports := map[string]string{}
24 mut fn_decls := map[string]FnDeclInfo{}
25 mut fn_decl_lists := map[string][]FnDeclInfo{}
26 mut struct_decls := map[string]StructDeclInfo{}
27 mut const_decls := map[string]ConstDeclInfo{}
28
29 // Reverse index: short name (after last '.') -> list of full qualified names
30 mut suffix_map := map[string][]string{}
31
32 mut fn_count := 0
33 mut fn_with_dot := 0
34 mut contains2_total := 0
35 for node_idx, node in a.nodes {
36 if node.kind == .file {
37 cur_module = ''
38 continue
39 }
40 if node.kind == .module_decl {
41 cur_module = node.value
42 continue
43 }
44 if node.kind == .import_decl {
45 imports[node.typ] = node.value
46 continue
47 }
48 if node.kind == .struct_decl {
49 full_name := qualify_fn(cur_module, node.value)
50 info := StructDeclInfo{
51 node_id: flat.NodeId(node_idx)
52 module: cur_module
53 }
54 struct_decls[full_name] = info
55 if node.value !in struct_decls {
56 struct_decls[node.value] = info
57 }
58 continue
59 }
60 if node.kind == .const_decl {
61 for i in 0 .. node.children_count {
62 field_id := a.child(&node, i)
63 field := a.node(field_id)
64 if field.kind != .const_field || field.children_count == 0 {
65 continue
66 }
67 info := ConstDeclInfo{
68 expr_id: a.child(field, 0)
69 module: cur_module
70 }
71 const_decls[field.value] = info
72 full_name := qualify_fn(cur_module, field.value)
73 if full_name != field.value {
74 const_decls[full_name] = info
75 }
76 }
77 continue
78 }
79 if node.kind == .fn_decl {
80 has_dot := node.value.contains('.')
81 if trace_markused {
82 fn_count++
83 if has_dot {
84 fn_with_dot++
85 if fn_with_dot <= 5 {
86 eprintln(' fn with dot: "${node.value}"')
87 }
88 }
89 }
90 info := FnDeclInfo{
91 node_id: flat.NodeId(node_idx)
92 module: cur_module
93 }
94 add_fn_decl_info(mut fn_decls, mut fn_decl_lists, node.value, info)
95 lowered_name := markused_c_name(node.value)
96 if lowered_name != node.value {
97 add_fn_decl_info(mut fn_decls, mut fn_decl_lists, lowered_name, info)
98 }
99 qname := qualify_fn(cur_module, node.value)
100 if qname != node.value {
101 add_fn_decl_info(mut fn_decls, mut fn_decl_lists, qname, info)
102 lowered_qname := markused_c_name(qname)
103 if lowered_qname != qname {
104 add_fn_decl_info(mut fn_decls, mut fn_decl_lists, lowered_qname, info)
105 }
106 }
107 // Build suffix_map entries
108 if has_dot {
109 if trace_markused {
110 contains2_total++
111 }
112 short := node.value.all_after_last('.')
113 add_suffix_candidate(mut suffix_map, short, node.value)
114 if qname != node.value {
115 add_suffix_candidate(mut suffix_map, short, qname)
116 }
117 }
118 if qname != node.value && qname.contains('.') {
119 short := qname.all_after_last('.')
120 add_suffix_candidate(mut suffix_map, short, qname)
121 }
122 }
123 }
124
125 // BFS from main
126 mut used := map[string]bool{}
127 mut queue := []string{}
128 queue << 'main'
129 used['main'] = true
130 enqueue_main_module_roots(fn_decls, mut used, mut queue)
131 enqueue_auto_roots(fn_decls, mut used, mut queue)
132 enqueue_export_roots(a, mut used, mut queue)
133 enqueue_veb_handler_roots(a, tc, mut used, mut queue)
134 enqueue_test_file_roots(a, test_files, mut used, mut queue)
135 queue << 'time.Time.new'
136 used['time.Time.new'] = true
137 used['Time.new'] = true
138 queue << 'gen_expr_lvalue'
139 used['gen_expr_lvalue'] = true
140 queue << 'c.gen_expr_lvalue'
141 used['c.gen_expr_lvalue'] = true
142 queue << 'gen_assign'
143 used['gen_assign'] = true
144 queue << 'c.gen_assign'
145 used['c.gen_assign'] = true
146 for seed in ['__new_array', 'new_array_from_c_array', 'array.get', 'array.set', 'array.push',
147 'array.push_many', 'array.insert', 'array.insert_many', 'array.prepend', 'array.reverse',
148 'array.slice', 'array.pop_left', 'array.clone', 'array.delete', 'array.ensure_cap',
149 'string.==', 'string.<', 'string.free', 'string.all_before', 'string.all_before_last',
150 'string.all_after', 'string.all_after_last', 'string.substr', 'string__substr', 'u8.vstring',
151 'u8.vstring_with_len', 'charptr.vstring', 'charptr.vstring_with_len', 'byteptr.vstring',
152 'byteptr.vstring_with_len', 'byteptr.vbytes', 'voidptr.vbytes', '[]rune.string', 'map.set',
153 'map.exists', 'map.get', 'map.get_check', 'map.get_and_set', 'map.delete', 'map.clone',
154 'map.clear', 'map.keys', 'map.values', 'map.reserve', 'map_map_eq', 'memdup',
155 'strings.Builder.write_ptr', 'strings.Builder.write_runes', 'strings.Builder.free',
156 'strconv.format_int', 'strconv.format_uint', 'bool.str', 'int.str', 'u64.str', 'f64.str',
157 'rune.str', 'string.+', 'ptr_str', 'strconv__f32_to_str_l', 'strconv__f64_to_str_l',
158 'sync.new_channel_st', 'sync.Channel.push', 'sync.Channel.pop', 'sync.Channel.close',
159 'sync.Channel.len', 'sync.Channel.closed', 'new_channel_st', 'Channel.push', 'Channel.pop',
160 'Channel.close', 'Channel.len', 'Channel.closed', 'os.join_path_single', 'panic',
161 'u8.is_letter', 'u8.is_capital', 'string.is_capital', 'string.to_lower_ascii',
162 'rune.to_lower', 'Array_u8__bytestr', 'Array_u8__hex', 'data_to_hex_string',
163 'map_hash_string', 'map_hash_int_1', 'map_hash_int_2', 'map_hash_int_4', 'map_hash_int_8',
164 'map_eq_string', 'map_eq_int_1', 'map_eq_int_2', 'map_eq_int_4', 'map_eq_int_8',
165 'map_clone_string', 'map_clone_int_1', 'map_clone_int_2', 'map_clone_int_4',
166 'map_clone_int_8', 'map_free_string', 'map_free_nop', '[]string.join', 'Array_string__join',
167 'exit', 'v_exit'] {
168 queue << seed
169 used[seed] = true
170 }
171 queue << 'array.delete_last'
172 used['array.delete_last'] = true
173 for seed in ['i8.str', 'i16.str', 'i32.str', 'i64.str'] {
174 queue << seed
175 used[seed] = true
176 }
177
178 if trace_markused {
179 eprintln('markused: fn_count:')
180 eprintln(fn_count.str())
181 eprintln('fn_with_dot:')
182 eprintln(fn_with_dot.str())
183 eprintln('contains2_total:')
184 eprintln(contains2_total.str())
185 eprintln('markused: main in fn_decls: ${'main' in fn_decls}')
186 eprintln('markused: fn_decls count: ${fn_decls.len}')
187 eprintln('markused: suffix_map count: ${suffix_map.len}')
188 mut total_suffix_entries := 0
189 for _, vals in suffix_map {
190 total_suffix_entries += vals.len
191 }
192 eprintln('total suffix_map entries (sum of array lens):')
193 eprintln(total_suffix_entries.str())
194 }
195
196 mut suffix_hits := 0
197 // mut suffix_misses := 0
198 mut in_cg := 0
199 mut not_in_cg := 0
200 mut total_callees := 0
201 collector := CallCollector{
202 a: a
203 tc: tc
204 fn_decls: fn_decls
205 struct_decls: struct_decls
206 const_decls: const_decls
207 }
208 enqueue_detected_runtime_helpers(a, tc, mut used, mut queue)
209 enqueue_function_value_selectors(a, collector, fn_decls, mut used, mut queue)
210 // Methods used as values (`recv.method` passed as a callback) are reachable only
211 // through a wrapper cgen generates later. The checker records them per enclosing
212 // function in `method_values_by_fn`; they are seeded inside the BFS below (only when
213 // that function is reached), so an unreachable function's method value never forces an
214 // otherwise-unused specialization to be transformed/emitted.
215 enqueue_initializer_calls(a, collector, imports, fn_decls, mut used, mut queue)
216 enqueue_top_level_calls(a, collector, fn_decls, mut used, mut queue)
217 // Interface dispatch reachability: calling an interface method `Foo.m` may
218 // dispatch to any concrete `T.m` for a type `T` that implements `Foo`. Those
219 // concrete methods are only referenced from the generated dispatch switch, so
220 // without this they would be pruned and produce undefined-symbol errors.
221 mut iface_impls := map[string][]string{}
222 mut checked_iface_impls := map[string]bool{}
223 mut processed_nodes := []bool{len: a.nodes.len}
224 mut calls := []string{cap: 128}
225 mut qi := 0
226 for qi < queue.len {
227 name := queue[qi]
228 qi++
229 prev_len := queue.len
230 fn_infos := fn_decl_infos_for_queue_name(name, fn_decl_lists, a)
231 if fn_infos.len == 0 {
232 not_in_cg++
233 if trace_markused && qi <= 10 {
234 eprintln('BFS qi=${qi.str()} name="${name}" in_cg=false')
235 }
236 continue
237 }
238 for fn_info in fn_infos {
239 if trace_markused && qi <= 10 {
240 eprintln('BFS qi=${qi.str()} name="${name}" in_cg=true')
241 }
242 in_cg++
243 node_key := int(fn_info.node_id)
244 if node_key < 0 || node_key >= processed_nodes.len {
245 continue
246 }
247 if processed_nodes[node_key] {
248 continue
249 }
250 processed_nodes[node_key] = true
251 // This function is reachable, so any methods it uses as *values* (recorded by
252 // the checker per enclosing function) are reachable too -- mark them so they
253 // survive pruning (cgen emits a wrapper that calls them).
254 if mvs := tc.method_values_by_fn[node_key] {
255 for mkey in mvs {
256 enqueue(mkey, mut used, mut queue)
257 lowered_mv := markused_c_name(mkey)
258 if lowered_mv != mkey {
259 enqueue(lowered_mv, mut used, mut queue)
260 }
261 mv_short := mkey.all_after_last('.')
262 if cands := suffix_map[mv_short] {
263 for cand in cands {
264 if cand == mkey || cand.ends_with('.${mkey}') {
265 enqueue(cand, mut used, mut queue)
266 }
267 }
268 }
269 }
270 }
271 calls.clear()
272 node := a.node(fn_info.node_id)
273 receiver_name, receiver_struct := receiver_info(a, node)
274 collector.collect_calls(node, fn_info.module, imports, receiver_name, receiver_struct, mut
275 calls)
276 total_callees += calls.len
277 for callee in calls {
278 if !valid_symbol_name(callee) {
279 continue
280 }
281 mut found_direct := false
282 if callee_info := fn_decls[callee] {
283 found_direct = true
284 if enqueue(callee, mut used, mut queue) {
285 if trace_markused && qi == 1 {
286 eprintln('main: all_fns hit: "${callee}"')
287 }
288 }
289 add_safe_decl_alias(callee, callee_info, a, mut used)
290 } else if callee in tc.fn_ret_types {
291 found_direct = true
292 if enqueue(callee, mut used, mut queue) {
293 if trace_markused && qi == 1 {
294 eprintln('main: resolved hit: "${callee}"')
295 }
296 }
297 }
298 if !found_direct || !callee.contains('.') {
299 short := callee.all_after_last('.')
300 if suffix_candidates := suffix_map[short] {
301 for candidate in suffix_candidates {
302 if candidate in fn_decls || candidate in tc.fn_ret_types {
303 if enqueue(candidate, mut used, mut queue) {
304 suffix_hits++
305 }
306 }
307 }
308 }
309 }
310 if callee.contains('.') {
311 recv := callee.all_before_last('.')
312 method := callee.all_after_last('.')
313 ensure_iface_impls(recv, fn_info.module, tc, mut iface_impls, mut
314 checked_iface_impls)
315 if impls := iface_impls[recv] {
316 for impl in impls {
317 impl_method := '${impl}.${method}'
318 enqueue(impl_method, mut used, mut queue)
319 short_impl := '${impl.all_after_last('.')}.${method}'
320 if short_impl != impl_method {
321 enqueue(short_impl, mut used, mut queue)
322 }
323 }
324 }
325 }
326 }
327 }
328 new_added := queue.len - prev_len
329 if trace_markused && qi <= 10 {
330 eprintln(' -> added ${new_added.str()} new entries, queue now ${queue.len.str()}')
331 }
332 }
333 if trace_markused {
334 eprintln('total_callees:')
335 eprintln(total_callees.str())
336 eprintln('markused: in_cg:')
337 eprintln(in_cg.str())
338 eprintln('not_in_cg:')
339 eprintln(not_in_cg.str())
340 eprintln('queue.len:')
341 eprintln(queue.len.str())
342 eprintln('markused: suffix_hits:')
343 eprintln(suffix_hits.str())
344 eprintln('markused: total used: ${used.len}')
345 }
346 return used
347}
348
349// ensure_iface_impls supports ensure iface impls handling for markused.
350fn ensure_iface_impls(recv string, cur_module string, tc &types.TypeChecker, mut iface_impls map[string][]string, mut checked map[string]bool) {
351 if recv.len == 0 {
352 return
353 }
354 iface_name := interface_name_for_receiver(recv, cur_module, tc) or { return }
355 if iface_name in checked {
356 return
357 }
358 checked[iface_name] = true
359 if recv != iface_name {
360 checked[recv] = true
361 }
362 mut impls := []string{}
363 for struct_name, _ in tc.structs {
364 if tc.named_type_implements_interface(struct_name, iface_name) {
365 impls << struct_name
366 }
367 }
368 iface_impls[recv] = impls
369 if iface_name != recv {
370 iface_impls[iface_name] = impls
371 checked[iface_name] = true
372 }
373}
374
375fn interface_name_for_receiver(recv string, cur_module string, tc &types.TypeChecker) ?string {
376 if recv in tc.interface_names {
377 return recv
378 }
379 if recv.contains('.') {
380 return none
381 }
382 if cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' {
383 qname := '${cur_module}.${recv}'
384 if qname in tc.interface_names {
385 return qname
386 }
387 }
388 return none
389}
390
391// add_suffix_candidate updates add suffix candidate state for markused.
392fn add_suffix_candidate(mut suffix_map map[string][]string, short string, name string) {
393 if !valid_symbol_name(short) || !valid_symbol_name(name) {
394 return
395 }
396 mut candidates := suffix_map[short] or { []string{} }
397 candidates << name
398 suffix_map[short] = candidates
399}
400
401fn add_fn_decl_info(mut fn_decls map[string]FnDeclInfo, mut fn_decl_lists map[string][]FnDeclInfo, name string, info FnDeclInfo) {
402 fn_decls[name] = info
403 mut infos := fn_decl_lists[name] or { []FnDeclInfo{} }
404 infos << info
405 fn_decl_lists[name] = infos
406}
407
408fn fn_decl_infos_for_queue_name(name string, fn_decl_lists map[string][]FnDeclInfo, a &flat.FlatAst) []FnDeclInfo {
409 infos := fn_decl_lists[name] or { return []FnDeclInfo{} }
410 if infos.len <= 1 {
411 return infos
412 }
413 mut exact_infos := []FnDeclInfo{}
414 for info in infos {
415 node := a.node(info.node_id)
416 if fn_decl_key_is_exact_for_info(name, node.value, info.module) {
417 exact_infos << info
418 }
419 }
420 return exact_infos
421}
422
423fn fn_decl_key_is_exact_for_info(name string, decl_name string, module_name string) bool {
424 qname := qualify_fn(module_name, decl_name)
425 if name == qname {
426 return true
427 }
428 lowered := markused_c_name(qname)
429 return lowered != qname && name == lowered
430}
431
432fn add_safe_decl_alias(callee string, callee_info FnDeclInfo, a &flat.FlatAst, mut used map[string]bool) {
433 alias := a.node(callee_info.node_id).value
434 if alias == callee || alias in used {
435 return
436 }
437 if fn_decl_key_is_exact_for_info(callee, alias, callee_info.module) {
438 return
439 }
440 used[alias] = true
441}
442
443// valid_symbol_name supports valid symbol name handling for markused.
444fn valid_symbol_name(name string) bool {
445 return name.len > 0 && name.len < 512
446}
447
448// markused_clone_bool_map returns a value clone even when the source is passed
449// from a `mut map` parameter. v3 self-host cgen otherwise infers a `map*` local
450// for `param.clone()` in a few recursive markused scanners.
451fn markused_clone_bool_map(src map[string]bool) map[string]bool {
452 return src.clone()
453}
454
455fn markused_clone_string_map(src map[string]string) map[string]string {
456 return src.clone()
457}
458
459// enqueue_initializer_calls supports enqueue initializer calls handling for markused.
460fn 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) {
461 mut cur_module := ''
462 mut calls := []string{cap: 32}
463 for node in a.nodes {
464 match node.kind {
465 .module_decl {
466 cur_module = node.value
467 }
468 .const_decl, .global_decl {
469 for i in 0 .. node.children_count {
470 field := a.child_node(&node, i)
471 if field.children_count == 0 {
472 continue
473 }
474 calls.clear()
475 collector.collect_calls(field, cur_module, imports, '', '', mut calls)
476 for callee in calls {
477 if callee_info := fn_decls[callee] {
478 enqueue(callee, mut used, mut queue)
479 add_safe_decl_alias(callee, callee_info, a, mut used)
480 } else {
481 enqueue(callee, mut used, mut queue)
482 }
483 }
484 }
485 }
486 else {}
487 }
488 }
489}
490
491fn enqueue_top_level_calls(a &flat.FlatAst, collector CallCollector, fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) {
492 if markused_has_entry_main(a) {
493 return
494 }
495 mut calls := []string{cap: 32}
496 for file_idx, file_node in a.nodes {
497 if !markused_should_scan_top_level_file(a, file_idx, file_node) {
498 continue
499 }
500 module_name := markused_top_level_file_module_name(a, file_node)
501 file_imports := markused_top_level_file_imports(a, file_node)
502 mut local_values := map[string]bool{}
503 mut local_types := map[string]string{}
504 for i in 0 .. file_node.children_count {
505 child_id := a.child(&file_node, i)
506 if int(child_id) < a.user_code_start {
507 continue
508 }
509 child := a.node(child_id)
510 if !markused_is_top_level_stmt(child) {
511 continue
512 }
513 calls.clear()
514 collector.collect_top_level_stmt_calls(child_id, module_name, file_imports, mut
515 local_values, mut local_types, mut calls)
516 for callee in calls {
517 if callee_info := fn_decls[callee] {
518 enqueue(callee, mut used, mut queue)
519 add_safe_decl_alias(callee, callee_info, a, mut used)
520 } else {
521 enqueue(callee, mut used, mut queue)
522 }
523 }
524 }
525 }
526}
527
528fn markused_has_entry_main(a &flat.FlatAst) bool {
529 mut cur_module := ''
530 for node in a.nodes {
531 match node.kind {
532 .file {
533 cur_module = ''
534 }
535 .module_decl {
536 cur_module = node.value
537 }
538 .fn_decl {
539 if node.value == 'main' && (cur_module.len == 0 || cur_module == 'main') {
540 return true
541 }
542 }
543 else {}
544 }
545 }
546 return false
547}
548
549fn markused_should_scan_top_level_file(a &flat.FlatAst, file_idx int, file_node flat.Node) bool {
550 if file_idx < a.user_code_start || file_node.kind != .file || file_node.children_count == 0 {
551 return false
552 }
553 module_name := markused_top_level_file_module_name(a, file_node)
554 return module_name.len == 0 || module_name == 'main'
555}
556
557fn markused_top_level_file_module_name(a &flat.FlatAst, file_node flat.Node) string {
558 for i in 0 .. file_node.children_count {
559 child := a.child_node(&file_node, i)
560 if child.kind == .module_decl {
561 return child.value
562 }
563 }
564 return ''
565}
566
567fn markused_top_level_file_imports(a &flat.FlatAst, file_node flat.Node) map[string]string {
568 mut imports := map[string]string{}
569 for i in 0 .. file_node.children_count {
570 child := a.child_node(&file_node, i)
571 if child.kind == .import_decl {
572 imports[child.typ] = child.value
573 }
574 }
575 return imports
576}
577
578fn markused_is_top_level_stmt(node flat.Node) bool {
579 return match node.kind {
580 .expr_stmt, .assign, .decl_assign, .global_decl, .selector_assign, .index_assign,
581 .for_stmt, .for_in_stmt, .if_expr, .match_stmt, .assert_stmt, .defer_stmt, .block {
582 true
583 }
584 else {
585 false
586 }
587 }
588}
589
590// enqueue supports enqueue handling for markused.
591fn enqueue(name string, mut used map[string]bool, mut queue []string) bool {
592 if !valid_symbol_name(name) {
593 return false
594 }
595 if name in used {
596 return false
597 }
598 used[name] = true
599 queue << name
600 return true
601}
602
603// FnDeclInfo stores fn decl info metadata used by markused.
604struct FnDeclInfo {
605 node_id flat.NodeId
606 module string
607}
608
609// StructDeclInfo stores struct decl info metadata used by markused.
610struct StructDeclInfo {
611 node_id flat.NodeId
612 module string
613}
614
615// ConstDeclInfo stores const decl info metadata used by markused.
616struct ConstDeclInfo {
617 expr_id flat.NodeId
618 module string
619}
620
621// CallCollector represents call collector data used by markused.
622struct CallCollector {
623 a &flat.FlatAst = unsafe { nil }
624 tc &types.TypeChecker = unsafe { nil }
625 fn_decls map[string]FnDeclInfo
626 struct_decls map[string]StructDeclInfo
627 const_decls map[string]ConstDeclInfo
628}
629
630// enqueue_auto_roots supports enqueue auto roots handling for markused.
631fn enqueue_auto_roots(fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) {
632 for name, info in fn_decls {
633 if !is_auto_root_fn(name) {
634 continue
635 }
636 enqueue(name, mut used, mut queue)
637 short_name := name.all_after_last('.')
638 if short_name != name {
639 enqueue(short_name, mut used, mut queue)
640 }
641 qualified_name := qualify_fn(info.module, short_name)
642 if qualified_name != name {
643 enqueue(qualified_name, mut used, mut queue)
644 }
645 }
646}
647
648fn enqueue_export_roots(a &flat.FlatAst, mut used map[string]bool, mut queue []string) {
649 if a.export_fn_names.len == 0 {
650 return
651 }
652 mut cur_module := ''
653 for node in a.nodes {
654 match node.kind {
655 .file {
656 cur_module = ''
657 }
658 .module_decl {
659 cur_module = node.value
660 }
661 .fn_decl {
662 qname := qualify_fn(cur_module, node.value)
663 if qname in a.export_fn_names {
664 enqueue(qname, mut used, mut queue)
665 }
666 }
667 else {}
668 }
669 }
670}
671
672fn enqueue_veb_handler_roots(a &flat.FlatAst, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
673 mut cur_module := ''
674 for node in a.nodes {
675 match node.kind {
676 .file {
677 cur_module = ''
678 }
679 .module_decl {
680 cur_module = node.value
681 }
682 .fn_decl {
683 if markused_fn_needs_implicit_veb_ctx(a, tc, cur_module, node) {
684 enqueue(qualify_fn(cur_module, node.value), mut used, mut queue)
685 }
686 }
687 else {}
688 }
689 }
690}
691
692fn markused_fn_needs_implicit_veb_ctx(a &flat.FlatAst, tc &types.TypeChecker, cur_module string, node flat.Node) bool {
693 return markused_fn_returns_veb_result(tc, node) && markused_fn_has_receiver_param(a, node)
694 && !markused_fn_receiver_type_is_context(a, node) && !markused_fn_has_param(a, node, 'ctx')
695 && markused_type_name_known_in_module(tc, cur_module, 'Context')
696}
697
698fn markused_fn_returns_veb_result(tc &types.TypeChecker, node flat.Node) bool {
699 if node.typ == 'veb.Result' {
700 return true
701 }
702 ret := tc.parse_type(node.typ)
703 return ret.name() == 'veb.Result'
704}
705
706fn markused_fn_has_receiver_param(a &flat.FlatAst, node flat.Node) bool {
707 if !node.value.contains('.') || node.children_count == 0 {
708 return false
709 }
710 first := a.child_node(&node, 0)
711 if first.kind != .param || first.typ.len == 0 {
712 return false
713 }
714 receiver := node.value.all_before_last('.').all_after_last('.')
715 param_type := first.typ.trim_left('&').all_after_last('.')
716 return receiver == param_type
717}
718
719fn markused_fn_receiver_type_is_context(a &flat.FlatAst, node flat.Node) bool {
720 if !markused_fn_has_receiver_param(a, node) {
721 return false
722 }
723 first := a.child_node(&node, 0)
724 return first.typ.trim_left('&').all_after_last('.') == 'Context'
725}
726
727fn markused_fn_has_param(a &flat.FlatAst, node flat.Node, name string) bool {
728 for i in 0 .. node.children_count {
729 param := a.child_node(&node, i)
730 if param.kind == .param && param.value == name {
731 return true
732 }
733 }
734 return false
735}
736
737fn markused_type_name_known_in_module(tc &types.TypeChecker, module_name string, typ string) bool {
738 qtyp := qualify_fn(module_name, typ)
739 return qtyp in tc.type_aliases || qtyp in tc.structs || qtyp in tc.interface_names
740 || qtyp in tc.enum_names || qtyp in tc.sum_types
741}
742
743fn enqueue_test_file_roots(a &flat.FlatAst, test_files map[string]bool, mut used map[string]bool, mut queue []string) {
744 if test_files.len == 0 {
745 return
746 }
747 for file_idx, file_node in a.nodes {
748 if !is_user_test_file_node(a, file_idx, file_node, test_files) {
749 continue
750 }
751 module_name := test_file_module_name(a, file_node)
752 mut decl_ids := []flat.NodeId{}
753 markused_collect_test_harness_decl_ids(a, file_node, mut decl_ids)
754 for child_id in decl_ids {
755 child := a.node(child_id)
756 if is_test_harness_root_name(child.value) {
757 enqueue(qualify_fn(module_name, child.value), mut used, mut queue)
758 }
759 }
760 }
761}
762
763fn markused_collect_test_harness_decl_ids(a &flat.FlatAst, node flat.Node, mut ids []flat.NodeId) {
764 if node.kind != .file && node.kind != .block {
765 return
766 }
767 for i in 0 .. node.children_count {
768 child_id := a.child(&node, i)
769 if int(child_id) < a.user_code_start {
770 continue
771 }
772 child := a.node(child_id)
773 if child.kind == .fn_decl {
774 ids << child_id
775 } else if child.kind == .block {
776 markused_collect_test_harness_decl_ids(a, child, mut ids)
777 }
778 }
779}
780
781fn is_user_test_file_node(a &flat.FlatAst, file_idx int, file_node flat.Node, test_files map[string]bool) bool {
782 if file_idx < a.user_code_start || file_node.kind != .file || file_node.children_count == 0 {
783 return false
784 }
785 return test_files[file_node.value]
786}
787
788fn test_file_module_name(a &flat.FlatAst, file_node flat.Node) string {
789 for i in 0 .. file_node.children_count {
790 child := a.child_node(&file_node, i)
791 if child.kind == .module_decl {
792 return child.value
793 }
794 }
795 return ''
796}
797
798fn is_test_harness_root_name(name string) bool {
799 return name.starts_with('test_')
800 || name in ['testsuite_begin', 'testsuite_end', 'before_each', 'after_each']
801}
802
803// enqueue_main_module_roots supports enqueue main module roots handling for markused.
804fn enqueue_main_module_roots(fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) {
805 for name, info in fn_decls {
806 if info.module != 'main' || name != 'main' {
807 continue
808 }
809 enqueue(name, mut used, mut queue)
810 }
811}
812
813// is_auto_root_fn reports whether is auto root fn applies in markused.
814fn is_auto_root_fn(name string) bool {
815 short_name := name.all_after_last('.')
816 return short_name == 'init'
817}
818
819// enqueue_detected_runtime_helpers supports enqueue detected runtime helpers handling for markused.
820fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
821 mut needs_optional_helpers := false
822 mut needs_string_interp_helpers := false
823 mut needs_string_plus_helper := false
824 mut needs_string_membership_helpers := false
825 mut needs_new_map := false
826 mut needs_map_iteration_snapshot := false
827 mut cur_module := ''
828 mut imports := map[string]string{}
829 for node in a.nodes {
830 match node.kind {
831 .file {
832 cur_module = ''
833 imports = map[string]string{}
834 }
835 .module_decl {
836 cur_module = node.value
837 }
838 .import_decl {
839 imports[node.typ] = node.value
840 }
841 .fn_decl {
842 ret_type := tc.parse_type(node.typ)
843 if ret_type is types.OptionType || ret_type is types.ResultType {
844 needs_optional_helpers = true
845 }
846 }
847 .param, .field_decl, .field_init, .const_field {
848 if type_string_needs_optional_helpers(node.typ) {
849 needs_optional_helpers = true
850 }
851 }
852 .none_expr {
853 needs_optional_helpers = true
854 }
855 .or_expr {
856 needs_optional_helpers = true
857 if node.children_count > 0 {
858 expr_id := a.child(&node, 0)
859 expr_type := tc.expr_type(expr_id) or { tc.resolve_type(expr_id) }
860 if type_needs_zero_map(expr_type) {
861 needs_new_map = true
862 }
863 }
864 }
865 .call {
866 if node.children_count > 0 {
867 fn_node := a.child_node(&node, 0)
868 if fn_node.kind == .ident
869 && (fn_node.value == 'error' || fn_node.value == 'error_with_code') {
870 needs_optional_helpers = true
871 }
872 if fn_node.kind == .ident && fn_node.value == 'flag_default_value' {
873 enqueue('escape_default_string', mut used, mut queue)
874 }
875 if fn_node.kind == .selector
876 && fn_node.value in ['trim_space', 'trim_space_left', 'trim_space_right', 'to_upper', 'to_upper_ascii', 'to_lower', 'to_lower_ascii'] {
877 enqueue('string.${fn_node.value}', mut used, mut queue)
878 }
879 if markused_call_lowers_to_join_path_single(a, fn_node, imports) {
880 enqueue('join_path_single', mut used, mut queue)
881 enqueue('os.join_path_single', mut used, mut queue)
882 }
883 if fn_node.kind == .selector && fn_node.value == 'runes_iterator' {
884 enqueue('RunesIterator.next', mut used, mut queue)
885 }
886 if fn_node.kind == .ident
887 && fn_node.value in ['print', 'println', 'eprint', 'eprintln']
888 && node.children_count >= 2 {
889 enqueue_stringified_custom_str_method(a.child(&node, 1), cur_module, tc, mut
890 used, mut queue)
891 }
892 }
893 }
894 .string_interp {
895 needs_string_interp_helpers = true
896 needs_string_plus_helper = true
897 for i in 0 .. node.children_count {
898 enqueue_stringified_custom_str_method(a.child(&node, i), cur_module, tc, mut
899 used, mut queue)
900 }
901 }
902 .assign {
903 if node.op == .plus_assign && node.children_count == 2 {
904 lhs_id := a.child(&node, 0)
905 rhs_id := a.child(&node, 1)
906 rhs := a.node(rhs_id)
907 lhs_type := markused_membership_container_type(tc, tc.resolve_type(lhs_id))
908 rhs_type := markused_membership_container_type(tc, tc.resolve_type(rhs_id))
909 if lhs_type == 'string' || rhs_type == 'string'
910 || rhs.kind in [.string_literal, .string_interp] {
911 needs_string_plus_helper = true
912 }
913 }
914 }
915 .in_expr {
916 if node.children_count >= 2 {
917 rhs_id := a.child(&node, 1)
918 rhs_type := markused_membership_container_type(tc, tc.resolve_type(rhs_id))
919 if rhs_type == 'string' {
920 needs_string_membership_helpers = true
921 }
922 }
923 }
924 .map_init {
925 needs_new_map = true
926 }
927 .for_in_stmt {
928 if node.value.int() == 3 && node.children_count > 2 {
929 container_id := a.child(&node, 2)
930 container_type := tc.resolve_type(container_id)
931 if types.unwrap_pointer(container_type) is types.Map {
932 needs_map_iteration_snapshot = true
933 }
934 }
935 }
936 .enum_decl {
937 // A `[flag]` enum's synthesized `<Enum>__autostr` helper (emitted
938 // unconditionally in cgen) builds its `Enum{.a | .b}` string with
939 // `string__plus`. markused never walks that generated body, so without
940 // seeding the helper here it can be pruned when the program has no other
941 // string concatenation, leaving the autostr calling an undefined
942 // `string__plus`.
943 if node.typ == 'flag' {
944 needs_string_plus_helper = true
945 }
946 }
947 else {}
948 }
949 }
950 if needs_optional_helpers {
951 for helper in ['IError.str', 'error', 'error_with_code'] {
952 enqueue(helper, mut used, mut queue)
953 }
954 }
955 if needs_string_interp_helpers {
956 for helper in ['strings.new_builder', 'strings.Builder.write_string', 'strings.Builder.str',
957 'string_plus_many'] {
958 enqueue(helper, mut used, mut queue)
959 }
960 }
961 if needs_string_plus_helper {
962 enqueue('string__plus', mut used, mut queue)
963 }
964 if needs_string_membership_helpers {
965 for helper in ['string__contains', 'string__contains_u8'] {
966 enqueue(helper, mut used, mut queue)
967 }
968 }
969 if needs_new_map {
970 enqueue('new_map', mut used, mut queue)
971 }
972 if needs_map_iteration_snapshot {
973 for helper in ['map.clone', 'map__clone', 'map.free', 'map__free'] {
974 enqueue(helper, mut used, mut queue)
975 }
976 }
977}
978
979fn markused_call_lowers_to_join_path_single(a &flat.FlatAst, fn_node flat.Node, imports map[string]string) bool {
980 if fn_node.kind == .ident {
981 return fn_node.value == 'join_path'
982 }
983 if fn_node.kind != .selector || fn_node.value != 'join_path' || fn_node.children_count == 0 {
984 return false
985 }
986 base_id := a.child(&fn_node, 0)
987 if int(base_id) < 0 {
988 return false
989 }
990 base := a.node(base_id)
991 if base.kind != .ident {
992 return false
993 }
994 return base.value == 'os' || imports[base.value] == 'os'
995}
996
997// enqueue_stringified_custom_str_method supports enqueue_stringified_custom_str_method handling.
998fn enqueue_stringified_custom_str_method(expr_id flat.NodeId, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
999 mut typ := tc.expr_type(expr_id) or { tc.resolve_type(expr_id) }
1000 for _ in 0 .. 8 {
1001 if typ is types.Alias {
1002 typ = typ.base_type
1003 continue
1004 }
1005 if typ is types.OptionType {
1006 typ = typ.base_type
1007 continue
1008 }
1009 if typ is types.ResultType {
1010 typ = typ.base_type
1011 continue
1012 }
1013 break
1014 }
1015 type_name := typ.name()
1016 match typ {
1017 types.Primitive, types.Rune, types.Char, types.ISize, types.USize, types.String {
1018 enqueue_stringified_primitive_helpers(type_name, mut used, mut queue)
1019 }
1020 types.Enum {
1021 enqueue_enum_str_method(typ.name, cur_module, tc, mut used, mut queue)
1022 }
1023 types.Struct {
1024 enqueue_structlike_str_method(typ.name, cur_module, tc, mut used, mut queue)
1025 }
1026 types.SumType {
1027 enqueue_structlike_str_method(typ.name, cur_module, tc, mut used, mut queue)
1028 }
1029 else {}
1030 }
1031}
1032
1033fn enqueue_stringified_primitive_helpers(type_name string, mut used map[string]bool, mut queue []string) {
1034 match type_name {
1035 'bool' {
1036 enqueue('bool.str', mut used, mut queue)
1037 }
1038 'rune', 'char' {
1039 enqueue('rune.str', mut used, mut queue)
1040 }
1041 'int', 'i8', 'i16', 'i32', 'i64' {
1042 enqueue('${type_name}.str', mut used, mut queue)
1043 enqueue(markused_c_name('${type_name}.str'), mut used, mut queue)
1044 enqueue('strconv__format_int', mut used, mut queue)
1045 }
1046 'isize' {
1047 enqueue('strconv__format_int', mut used, mut queue)
1048 }
1049 'u8', 'byte', 'u16', 'u32', 'usize' {
1050 enqueue('strconv__format_uint', mut used, mut queue)
1051 }
1052 'u64' {
1053 enqueue('u64.str', mut used, mut queue)
1054 enqueue(markused_c_name('u64.str'), mut used, mut queue)
1055 enqueue('strconv__format_uint', mut used, mut queue)
1056 }
1057 'f32' {
1058 enqueue('strconv__f32_to_str_l', mut used, mut queue)
1059 }
1060 'f64' {
1061 enqueue('strconv__f64_to_str_l', mut used, mut queue)
1062 }
1063 else {}
1064 }
1065}
1066
1067// enqueue_enum_str_method supports enqueue enum str method handling for markused.
1068fn enqueue_enum_str_method(type_name string, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
1069 for candidate in stringification_type_candidates(type_name, cur_module) {
1070 method := '${candidate}.str'
1071 if method in tc.fn_ret_types {
1072 enqueue(method, mut used, mut queue)
1073 lowered := markused_c_name(method)
1074 if lowered != method {
1075 enqueue(lowered, mut used, mut queue)
1076 }
1077 }
1078 }
1079}
1080
1081// enqueue_structlike_str_method supports enqueue structlike str method handling for markused.
1082fn enqueue_structlike_str_method(type_name string, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
1083 for candidate in stringification_type_candidates(type_name, cur_module) {
1084 lowered := '${markused_c_name(candidate)}__str'
1085 if lowered in tc.fn_ret_types {
1086 enqueue(lowered, mut used, mut queue)
1087 }
1088 method := '${candidate}.str'
1089 if method in tc.fn_ret_types {
1090 enqueue(method, mut used, mut queue)
1091 }
1092 }
1093}
1094
1095// stringification_type_candidates supports stringification type candidates handling for markused.
1096fn stringification_type_candidates(type_name string, cur_module string) []string {
1097 if type_name.len == 0 {
1098 return []string{}
1099 }
1100 mut candidates := []string{cap: 2}
1101 candidates << type_name
1102 if !type_name.contains('.') && cur_module.len > 0 && cur_module != 'main'
1103 && cur_module != 'builtin' {
1104 candidates << '${cur_module}.${type_name}'
1105 }
1106 return candidates
1107}
1108
1109// enqueue_function_value_selectors supports enqueue function value selectors handling for markused.
1110fn enqueue_function_value_selectors(a &flat.FlatAst, collector CallCollector, fn_decls map[string]FnDeclInfo, mut used map[string]bool, mut queue []string) {
1111 ignored_top_level_nodes := markused_ignored_top_level_nodes(a)
1112 ignored_fn_decl_nodes := markused_ignored_fn_decl_nodes(a)
1113 shadowed_value_idents := markused_shadowed_value_idents(a)
1114 for node_idx, node in a.nodes {
1115 if node_idx < ignored_fn_decl_nodes.len && ignored_fn_decl_nodes[node_idx] {
1116 continue
1117 }
1118 if node_idx < ignored_top_level_nodes.len && ignored_top_level_nodes[node_idx] {
1119 continue
1120 }
1121 if node.kind == .ident && node.value.len > 0 {
1122 node_id := flat.NodeId(node_idx)
1123 is_shadowed := node_idx < shadowed_value_idents.len && shadowed_value_idents[node_idx]
1124 if is_shadowed {
1125 continue
1126 }
1127 if resolved := collector.tc.resolved_fn_value_name(node_id) {
1128 enqueue(resolved, mut used, mut queue)
1129 continue
1130 }
1131 if node.value in fn_decls && collector.node_is_fn_value(node_id) {
1132 enqueue(node.value, mut used, mut queue)
1133 }
1134 continue
1135 }
1136 if node.kind == .selector && node.children_count > 0 && node.value.len > 0 {
1137 base_id := a.child(&node, 0)
1138 if int(base_id) >= 0 && int(base_id) < shadowed_value_idents.len
1139 && shadowed_value_idents[int(base_id)] {
1140 continue
1141 }
1142 base := a.node(base_id)
1143 if base.kind == .ident && base.value.len > 0 {
1144 name := '${base.value}.${node.value}'
1145 if name in fn_decls {
1146 enqueue(name, mut used, mut queue)
1147 }
1148 }
1149 }
1150 }
1151}
1152
1153fn markused_ignored_fn_decl_nodes(a &flat.FlatAst) []bool {
1154 mut ignored := []bool{len: a.nodes.len}
1155 for node_idx, node in a.nodes {
1156 if node.kind == .fn_decl {
1157 markused_mark_node_subtree(a, flat.NodeId(node_idx), mut ignored)
1158 }
1159 }
1160 return ignored
1161}
1162
1163fn markused_ignored_top_level_nodes(a &flat.FlatAst) []bool {
1164 if !markused_has_entry_main(a) {
1165 return []bool{}
1166 }
1167 mut ignored := []bool{len: a.nodes.len}
1168 for file_idx, file_node in a.nodes {
1169 if !markused_should_scan_top_level_file(a, file_idx, file_node) {
1170 continue
1171 }
1172 for i in 0 .. file_node.children_count {
1173 child_id := a.child(&file_node, i)
1174 if int(child_id) < a.user_code_start {
1175 continue
1176 }
1177 child := a.node(child_id)
1178 if !markused_is_top_level_stmt(child) {
1179 continue
1180 }
1181 markused_mark_node_subtree(a, child_id, mut ignored)
1182 }
1183 }
1184 return ignored
1185}
1186
1187fn markused_shadowed_value_idents(a &flat.FlatAst) []bool {
1188 mut shadowed := []bool{len: a.nodes.len}
1189 for file_idx, file_node in a.nodes {
1190 if !markused_should_scan_top_level_file(a, file_idx, file_node) {
1191 continue
1192 }
1193 mut local_values := map[string]bool{}
1194 for i in 0 .. file_node.children_count {
1195 child_id := a.child(&file_node, i)
1196 if int(child_id) < a.user_code_start {
1197 continue
1198 }
1199 child := a.node(child_id)
1200 if !markused_is_top_level_stmt(child) {
1201 continue
1202 }
1203 if local_values.len > 0 {
1204 markused_mark_shadowed_idents(a, child, local_values, mut shadowed)
1205 }
1206 markused_mark_top_level_lhs_idents(a, child, mut shadowed)
1207 markused_add_top_level_lhs_names(a, child, mut local_values)
1208 }
1209 }
1210 return shadowed
1211}
1212
1213fn markused_mark_shadowed_idents(a &flat.FlatAst, node &flat.Node, local_values map[string]bool, mut shadowed []bool) {
1214 if local_values.len == 0 {
1215 return
1216 }
1217 mut stack := []flat.NodeId{cap: int(node.children_count)}
1218 for i in 0 .. node.children_count {
1219 child_id := a.child(node, i)
1220 if int(child_id) >= 0 {
1221 stack << child_id
1222 }
1223 }
1224 for stack.len > 0 {
1225 id := stack.pop()
1226 idx := int(id)
1227 if idx < 0 || idx >= shadowed.len {
1228 continue
1229 }
1230 child := a.node(id)
1231 if child.kind == .ident && child.value in local_values {
1232 shadowed[idx] = true
1233 }
1234 for i in 0 .. child.children_count {
1235 next_id := a.child(child, i)
1236 if int(next_id) >= 0 {
1237 stack << next_id
1238 }
1239 }
1240 }
1241}
1242
1243fn markused_mark_top_level_lhs_idents(a &flat.FlatAst, node &flat.Node, mut shadowed []bool) {
1244 if node.kind != .decl_assign {
1245 return
1246 }
1247 mut i := 0
1248 for i + 1 < node.children_count {
1249 lhs_id := a.child(node, i)
1250 idx := int(lhs_id)
1251 if idx >= 0 && idx < shadowed.len {
1252 lhs := a.node(lhs_id)
1253 if lhs.kind == .ident {
1254 shadowed[idx] = true
1255 }
1256 }
1257 i += 2
1258 }
1259}
1260
1261fn markused_add_top_level_lhs_names(a &flat.FlatAst, node &flat.Node, mut local_values map[string]bool) {
1262 match node.kind {
1263 .decl_assign, .assign {
1264 mut i := 0
1265 for i + 1 < node.children_count {
1266 lhs_id := a.child(node, i)
1267 if int(lhs_id) >= 0 {
1268 lhs := a.node(lhs_id)
1269 if lhs.kind == .ident && lhs.value.len > 0 {
1270 local_values[lhs.value] = true
1271 }
1272 }
1273 i += 2
1274 }
1275 }
1276 .global_decl {
1277 for i in 0 .. node.children_count {
1278 field := a.child_node(node, i)
1279 if field.value.len > 0 {
1280 local_values[field.value] = true
1281 }
1282 }
1283 }
1284 else {}
1285 }
1286}
1287
1288fn markused_mark_node_subtree(a &flat.FlatAst, root flat.NodeId, mut marked []bool) {
1289 mut stack := [root]
1290 for stack.len > 0 {
1291 id := stack.pop()
1292 idx := int(id)
1293 if idx < 0 || idx >= marked.len || marked[idx] {
1294 continue
1295 }
1296 marked[idx] = true
1297 node := a.node(id)
1298 for i in 0 .. node.children_count {
1299 child_id := a.child(node, i)
1300 if int(child_id) >= 0 {
1301 stack << child_id
1302 }
1303 }
1304 }
1305}
1306
1307// type_string_needs_optional_helpers returns type string needs optional helpers data for markused.
1308fn type_string_needs_optional_helpers(typ string) bool {
1309 return typ.len > 0 && (typ[0] == `?` || typ[0] == `!`)
1310}
1311
1312// type_needs_zero_map returns type needs zero map data for markused.
1313fn type_needs_zero_map(typ types.Type) bool {
1314 mut clean := typ
1315 for _ in 0 .. 8 {
1316 if clean is types.Alias {
1317 clean = clean.base_type
1318 continue
1319 }
1320 if clean is types.OptionType {
1321 clean = clean.base_type
1322 continue
1323 }
1324 if clean is types.ResultType {
1325 clean = clean.base_type
1326 continue
1327 }
1328 break
1329 }
1330 return clean is types.Map
1331}
1332
1333// markused_membership_container_type
1334// supports helper handling in markused.
1335fn markused_membership_container_type(tc &types.TypeChecker, typ types.Type) string {
1336 mut clean := typ.name().trim_space()
1337 for {
1338 if clean.starts_with('shared ') {
1339 clean = clean[7..].trim_space()
1340 continue
1341 }
1342 if clean.starts_with('&') {
1343 clean = clean[1..].trim_space()
1344 continue
1345 }
1346 if clean in tc.type_aliases {
1347 alias := tc.type_aliases[clean].trim_space()
1348 if alias == clean {
1349 break
1350 }
1351 clean = alias
1352 continue
1353 }
1354 break
1355 }
1356 return clean
1357}
1358
1359// qualify_fn supports qualify fn handling for markused.
1360fn qualify_fn(mod string, name string) string {
1361 if mod.len == 0 || mod == 'main' || mod == 'builtin' {
1362 return name
1363 }
1364 return '${mod}.${name}'
1365}
1366
1367// receiver_info supports receiver info handling for markused.
1368fn receiver_info(a &flat.FlatAst, node &flat.Node) (string, string) {
1369 mut receiver_struct := ''
1370 if node.value.contains('.') {
1371 receiver_struct = node.value.all_before_last('.')
1372 }
1373 for pi in 0 .. node.children_count {
1374 pc := a.child_node(node, pi)
1375 if pc.kind == .param {
1376 if receiver_struct.len > 0 {
1377 return pc.value, receiver_struct
1378 }
1379 clean_type := pc.typ.trim_left('&')
1380 if pc.value.len > 0 && clean_type.len > 0 && clean_type[0] >= `A`
1381 && clean_type[0] <= `Z` {
1382 return pc.value, clean_type
1383 }
1384 }
1385 }
1386 if receiver_struct.len == 0 {
1387 return '', ''
1388 }
1389 return '', receiver_struct
1390}
1391
1392// collect_calls updates collect calls state for markused.
1393fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports map[string]string, receiver_name string, receiver_struct string, mut calls []string) {
1394 local_values := c.local_value_names(node)
1395 local_types := c.local_value_type_names(node, cur_module, imports)
1396 visible_local_idents := markused_visible_local_idents(c.a, node, local_values)
1397 mut stack := []flat.NodeId{cap: int(node.children_count)}
1398 for i in 0 .. node.children_count {
1399 child_id := c.a.child(node, i)
1400 if int(child_id) >= 0 {
1401 stack << child_id
1402 }
1403 }
1404 for stack.len > 0 {
1405 child_id := stack.pop()
1406 child := &c.a.nodes[int(child_id)]
1407 match child.kind {
1408 .ident {
1409 name_is_local := markused_ident_is_visible_local(child_id, child.value,
1410 local_values, visible_local_idents)
1411 c.collect_fn_value_ident(child_id, child.value, cur_module, imports, name_is_local, mut
1412 calls)
1413 }
1414 .selector {
1415 if !c.selector_base_is_local(child, local_values) {
1416 c.collect_fn_value_selector(child_id, child, cur_module, imports, mut calls)
1417 } else if resolved := c.tc.resolved_fn_value_name(child_id) {
1418 calls << resolved
1419 }
1420 }
1421 .call {
1422 mut resolved_call := ''
1423 if resolved := c.tc.resolved_call_name(child_id) {
1424 resolved_call = resolved
1425 }
1426 if child.children_count > 0 {
1427 callee_id := c.a.child(child, 0)
1428 if int(callee_id) >= 0 {
1429 callee := c.a.nodes[int(callee_id)]
1430 if callee.kind == .ident && callee.value.len > 0 {
1431 if resolved_call.len > 0 {
1432 calls << resolved_call
1433 }
1434 if callee.value !in local_values {
1435 calls << callee.value
1436 qcallee := qualify_fn(cur_module, callee.value)
1437 if qcallee != callee.value {
1438 calls << qcallee
1439 }
1440 }
1441 } else if callee.kind == .selector && callee.value.len > 0 {
1442 mut has_exact_selector_call := false
1443 if callee.children_count > 0 {
1444 base_id := c.a.child(&callee, 0)
1445 if int(base_id) >= 0 {
1446 base := c.a.nodes[int(base_id)]
1447 has_exact_selector_call = c.collect_checker_selected_call(resolved_call, mut
1448 calls)
1449 if !has_exact_selector_call {
1450 has_exact_selector_call = c.collect_typed_receiver_method(base_id,
1451 callee.value, cur_module, imports, local_values,
1452 local_types, mut calls)
1453 }
1454 if !has_exact_selector_call && base.kind == .ident
1455 && base.value in imports && base.value !in local_values {
1456 calls << imports[base.value] + '.' + callee.value
1457 has_exact_selector_call = true
1458 }
1459 if !has_exact_selector_call && resolved_call.len > 0 {
1460 calls << resolved_call
1461 has_exact_selector_call = true
1462 }
1463 if !has_exact_selector_call && base.kind == .ident
1464 && base.value in local_values && !(receiver_name.len > 0
1465 && base.value == receiver_name) {
1466 has_exact_selector_call = true
1467 }
1468 if !has_exact_selector_call {
1469 if base.kind == .ident && base.value.len > 0 {
1470 if receiver_name.len > 0 && base.value == receiver_name {
1471 calls << receiver_struct + '.' + callee.value
1472 qrecv := qualify_fn(cur_module, receiver_struct +
1473 '.' + callee.value)
1474 if qrecv != receiver_struct + '.' + callee.value {
1475 calls << qrecv
1476 }
1477 }
1478 mod_name := if base.value in imports {
1479 imports[base.value]
1480 } else {
1481 base.value
1482 }
1483 calls << mod_name + '.' + callee.value
1484 calls << qualify_fn(cur_module, base.value + '.' +
1485 callee.value)
1486 if base.value.len > 0 && base.value[0] >= `A`
1487 && base.value[0] <= `Z` {
1488 named_type := c.tc.parse_type(base.value)
1489 named_type_name := resolve_type_name(named_type)
1490 if named_type_name.len > 0 {
1491 calls << named_type_name + '.' + callee.value
1492 }
1493 }
1494 } else if base.kind == .selector && base.children_count > 0 {
1495 inner_id := c.a.child(&base, 0)
1496 if int(inner_id) >= 0 {
1497 inner := c.a.nodes[int(inner_id)]
1498 if inner.kind == .ident && inner.value.len > 0 {
1499 mod_name := if inner.value in imports {
1500 imports[inner.value]
1501 } else {
1502 inner.value
1503 }
1504 calls << mod_name + '.' + base.value + '.' +
1505 callee.value
1506 }
1507 }
1508 }
1509 }
1510 }
1511 }
1512 if !has_exact_selector_call {
1513 calls << callee.value
1514 }
1515 } else if resolved_call.len > 0 {
1516 calls << resolved_call
1517 }
1518 }
1519 } else if resolved_call.len > 0 {
1520 calls << resolved_call
1521 }
1522 for ci in 1 .. child.children_count {
1523 arg_id := c.a.child(child, ci)
1524 if int(arg_id) >= 0 {
1525 arg := c.a.nodes[int(arg_id)]
1526 if arg.kind == .ident && arg.value.len > 0 {
1527 arg_is_local := markused_ident_is_visible_local(arg_id, arg.value,
1528 local_values, visible_local_idents)
1529 c.collect_fn_value_ident(arg_id, arg.value, cur_module, imports,
1530 arg_is_local, mut calls)
1531 }
1532 }
1533 }
1534 }
1535 .prefix {
1536 if child.op == .amp && child.children_count > 0 {
1537 inner_id := c.a.child(child, 0)
1538 if int(inner_id) >= 0 {
1539 inner := c.a.nodes[int(inner_id)]
1540 if inner.kind == .struct_init {
1541 calls << 'memdup'
1542 }
1543 }
1544 }
1545 }
1546 .string_interp {
1547 calls << 'string_plus_many'
1548 }
1549 .assign, .selector_assign, .index_assign {
1550 c.collect_assign_operator_call(child, cur_module, mut calls)
1551 }
1552 .infix {
1553 if child.op == .plus {
1554 calls << 'string__plus'
1555 }
1556 if child.children_count >= 2 {
1557 lhs_id := c.a.child(child, 0)
1558 rhs_id := c.a.child(child, 1)
1559 if child.op in [.dot, .amp] {
1560 lhs_name := c.qualified_expr_name(lhs_id)
1561 if lhs_name.len > 0 {
1562 rhs := c.a.nodes[int(rhs_id)]
1563 if rhs.kind == .call && rhs.children_count > 0 {
1564 fn_node := c.a.child_node(&rhs, 0)
1565 if fn_node.kind == .ident && fn_node.value.len > 0 {
1566 mod_name := if lhs_name in imports {
1567 imports[lhs_name]
1568 } else {
1569 lhs_name
1570 }
1571 calls << mod_name + '.' + fn_node.value
1572 calls << qualify_fn(cur_module, lhs_name + '.' + fn_node.value)
1573 }
1574 }
1575 }
1576 }
1577 if int(lhs_id) >= 0 {
1578 c.collect_struct_operator_call(lhs_id, child.op, cur_module, mut calls)
1579 }
1580 }
1581 }
1582 .or_expr {
1583 if child.children_count > 0 {
1584 expr_id := c.a.child(child, 0)
1585 c.collect_zero_struct_default_calls(c.node_type(expr_id), cur_module, imports, mut
1586 calls)
1587 }
1588 }
1589 .struct_init {
1590 c.collect_struct_default_calls(child, cur_module, imports, mut calls)
1591 }
1592 else {}
1593 }
1594
1595 if child.children_count > 0 {
1596 mut j := int(child.children_count) - 1
1597 for j >= 0 {
1598 if child.kind == .decl_assign && j % 2 == 0 {
1599 j--
1600 continue
1601 }
1602 next_id := c.a.child(child, j)
1603 if int(next_id) >= 0 {
1604 stack << next_id
1605 }
1606 j--
1607 }
1608 }
1609 }
1610}
1611
1612fn (c &CallCollector) local_value_names(node &flat.Node) map[string]bool {
1613 return markused_local_value_names(c.a, node)
1614}
1615
1616fn (c &CallCollector) local_value_type_names(node &flat.Node, cur_module string, imports map[string]string) map[string]string {
1617 mut result := map[string]string{}
1618 mut stack := []flat.NodeId{cap: int(node.children_count)}
1619 for i in 0 .. node.children_count {
1620 child_id := c.a.child(node, i)
1621 if int(child_id) >= 0 {
1622 stack << child_id
1623 }
1624 }
1625 for stack.len > 0 {
1626 id := stack.pop()
1627 child := c.a.node(id)
1628 if child.kind == .param && child.value.len > 0 && child.typ.len > 0 {
1629 result[child.value] = markused_resolve_imported_type_name(child.typ, imports)
1630 } else if child.kind == .decl_assign {
1631 mut i := 0
1632 for i + 1 < child.children_count {
1633 lhs_id := c.a.child(child, i)
1634 rhs_id := c.a.child(child, i + 1)
1635 if int(lhs_id) >= 0 && int(rhs_id) >= 0 {
1636 lhs := c.a.node(lhs_id)
1637 if lhs.kind == .ident && lhs.value.len > 0 {
1638 type_name := if child.children_count == 2 && child.typ.len > 0 {
1639 child.typ
1640 } else {
1641 c.top_level_decl_rhs_type_name(rhs_id, cur_module, imports)
1642 }
1643 if type_name.len > 0 {
1644 result[lhs.value] = type_name
1645 }
1646 }
1647 }
1648 i += 2
1649 }
1650 }
1651 for i in 0 .. child.children_count {
1652 next_id := c.a.child(child, i)
1653 if int(next_id) >= 0 {
1654 stack << next_id
1655 }
1656 }
1657 }
1658 return result
1659}
1660
1661fn markused_local_value_names(a &flat.FlatAst, node &flat.Node) map[string]bool {
1662 mut names := map[string]bool{}
1663 mut stack := []flat.NodeId{cap: int(node.children_count)}
1664 for i in 0 .. node.children_count {
1665 child_id := a.child(node, i)
1666 if int(child_id) >= 0 {
1667 stack << child_id
1668 }
1669 }
1670 for stack.len > 0 {
1671 id := stack.pop()
1672 child := a.node(id)
1673 if child.kind == .param && child.value.len > 0 {
1674 names[child.value] = true
1675 } else if child.kind == .decl_assign {
1676 mut i := 0
1677 for i < child.children_count {
1678 lhs_id := a.child(child, i)
1679 if int(lhs_id) >= 0 {
1680 lhs := a.node(lhs_id)
1681 if lhs.kind == .ident && lhs.value.len > 0 {
1682 names[lhs.value] = true
1683 }
1684 }
1685 i += 2
1686 }
1687 }
1688 for i in 0 .. child.children_count {
1689 next_id := a.child(child, i)
1690 if int(next_id) >= 0 {
1691 stack << next_id
1692 }
1693 }
1694 }
1695 return names
1696}
1697
1698fn markused_visible_local_idents(a &flat.FlatAst, root &flat.Node, local_values map[string]bool) map[int]bool {
1699 mut visible_ids := map[int]bool{}
1700 if local_values.len == 0 {
1701 return visible_ids
1702 }
1703 mut locals := map[string]bool{}
1704 if root.kind == .fn_decl {
1705 for i in 0 .. root.children_count {
1706 child := a.child_node(root, i)
1707 if child.kind == .param && child.value.len > 0 {
1708 locals[child.value] = true
1709 }
1710 }
1711 }
1712 markused_collect_visible_local_idents(a, root, local_values, mut locals, mut visible_ids)
1713 return visible_ids
1714}
1715
1716fn markused_ident_is_visible_local(id flat.NodeId, name string, local_values map[string]bool, visible_local_idents map[int]bool) bool {
1717 return name.len > 0 && name in local_values && int(id) in visible_local_idents
1718}
1719
1720fn markused_collect_visible_local_idents(a &flat.FlatAst, node &flat.Node, local_values map[string]bool, mut locals map[string]bool, mut visible_ids map[int]bool) {
1721 for i in 0 .. node.children_count {
1722 child_id := a.child(node, i)
1723 if int(child_id) < 0 {
1724 continue
1725 }
1726 child := a.node(child_id)
1727 match child.kind {
1728 .fn_decl, .c_fn_decl, .fn_literal {
1729 continue
1730 }
1731 .block, .if_expr, .match_stmt, .for_stmt, .for_in_stmt {
1732 mut scoped := markused_clone_bool_map(locals)
1733 markused_collect_visible_local_idents(a, child, local_values, mut scoped, mut
1734 visible_ids)
1735 }
1736 .decl_assign {
1737 mut i2 := 1
1738 for i2 < child.children_count {
1739 rhs_id := a.child(child, i2)
1740 if int(rhs_id) >= 0 {
1741 markused_mark_visible_local_ident(a, rhs_id, local_values, locals, mut
1742 visible_ids)
1743 rhs := a.node(rhs_id)
1744 markused_collect_visible_local_idents(a, rhs, local_values, mut locals, mut
1745 visible_ids)
1746 }
1747 i2 += 2
1748 }
1749 i2 = 0
1750 for i2 + 1 < child.children_count {
1751 lhs_id := a.child(child, i2)
1752 lhs := a.node(lhs_id)
1753 if lhs.kind == .ident && lhs.value in local_values && lhs.value in locals {
1754 visible_ids[int(lhs_id)] = true
1755 }
1756 if lhs.kind == .ident && lhs.value.len > 0 {
1757 locals[lhs.value] = true
1758 }
1759 i2 += 2
1760 }
1761 }
1762 else {
1763 if child.kind == .ident && child.value in local_values && child.value in locals {
1764 visible_ids[int(child_id)] = true
1765 }
1766 markused_collect_visible_local_idents(a, child, local_values, mut locals, mut
1767 visible_ids)
1768 }
1769 }
1770 }
1771}
1772
1773fn markused_mark_visible_local_ident(a &flat.FlatAst, id flat.NodeId, local_values map[string]bool, locals map[string]bool, mut visible_ids map[int]bool) {
1774 node := a.node(id)
1775 if node.kind == .ident && node.value in local_values && node.value in locals {
1776 visible_ids[int(id)] = true
1777 }
1778}
1779
1780fn (c &CallCollector) top_level_decl_rhs_type_name(rhs_id flat.NodeId, cur_module string, imports map[string]string) string {
1781 rhs := c.a.node(rhs_id)
1782 if rhs.kind == .struct_init && rhs.value.len > 0 {
1783 return c.struct_lookup_name(markused_resolve_imported_type_name(rhs.value, imports),
1784 cur_module)
1785 }
1786 type_name := resolve_type_name(c.node_type(rhs_id))
1787 if type_name.len > 0 {
1788 struct_type := c.struct_lookup_name(type_name, cur_module)
1789 if struct_type.len > 0 {
1790 return struct_type
1791 }
1792 return type_name
1793 }
1794 return ''
1795}
1796
1797fn (c &CallCollector) collect_top_level_stmt_calls(id flat.NodeId, cur_module string, imports map[string]string, mut local_values map[string]bool, mut local_types map[string]string, mut calls []string) {
1798 if int(id) < 0 {
1799 return
1800 }
1801 node := c.a.node(id)
1802 match node.kind {
1803 .decl_assign, .assign {
1804 c.collect_top_level_assign_calls(node, cur_module, imports, mut local_values, mut
1805 local_types, mut calls)
1806 }
1807 .global_decl {
1808 c.collect_top_level_global_decl_calls(node, cur_module, imports, mut local_values, mut
1809 local_types, mut calls)
1810 }
1811 .block, .for_stmt, .for_in_stmt {
1812 mut nested_values := markused_clone_bool_map(local_values)
1813 mut nested_types := markused_clone_string_map(local_types)
1814 c.collect_top_level_child_calls(node, cur_module, imports, mut nested_values, mut
1815 nested_types, mut calls)
1816 }
1817 .if_expr, .match_stmt {
1818 c.collect_top_level_branch_calls(node, cur_module, imports, local_values, local_types, mut
1819 calls)
1820 }
1821 else {
1822 c.collect_top_level_expr_calls(id, cur_module, imports, local_values, local_types, mut
1823 calls)
1824 }
1825 }
1826}
1827
1828fn (c &CallCollector) collect_top_level_child_calls(node &flat.Node, cur_module string, imports map[string]string, mut local_values map[string]bool, mut local_types map[string]string, mut calls []string) {
1829 for i in 0 .. node.children_count {
1830 child_id := c.a.child(node, i)
1831 if int(child_id) >= 0 {
1832 c.collect_top_level_stmt_calls(child_id, cur_module, imports, mut local_values, mut
1833 local_types, mut calls)
1834 }
1835 }
1836}
1837
1838fn (c &CallCollector) collect_top_level_branch_calls(node &flat.Node, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) {
1839 for i in 0 .. node.children_count {
1840 child_id := c.a.child(node, i)
1841 if int(child_id) < 0 {
1842 continue
1843 }
1844 mut branch_values := local_values.clone()
1845 mut branch_types := local_types.clone()
1846 c.collect_top_level_stmt_calls(child_id, cur_module, imports, mut branch_values, mut
1847 branch_types, mut calls)
1848 }
1849}
1850
1851fn (c &CallCollector) collect_top_level_assign_calls(node &flat.Node, cur_module string, imports map[string]string, mut local_values map[string]bool, mut local_types map[string]string, mut calls []string) {
1852 if node.kind !in [.decl_assign, .assign] {
1853 return
1854 }
1855 mut i := 0
1856 pre_values := markused_clone_bool_map(local_values)
1857 pre_types := markused_clone_string_map(local_types)
1858 for i + 1 < node.children_count {
1859 rhs_id := c.a.child(node, i + 1)
1860 if int(rhs_id) >= 0 {
1861 mut rhs_values := markused_clone_bool_map(pre_values)
1862 mut rhs_types := markused_clone_string_map(pre_types)
1863 c.collect_top_level_stmt_calls(rhs_id, cur_module, imports, mut rhs_values, mut
1864 rhs_types, mut calls)
1865 }
1866 i += 2
1867 }
1868 i = 0
1869 for i + 1 < node.children_count {
1870 lhs_id := c.a.child(node, i)
1871 rhs_id := c.a.child(node, i + 1)
1872 if int(lhs_id) >= 0 && int(rhs_id) >= 0 {
1873 lhs := c.a.node(lhs_id)
1874 if lhs.kind == .ident && lhs.value.len > 0 {
1875 local_values[lhs.value] = true
1876 type_name := c.top_level_decl_rhs_type_name(rhs_id, cur_module, imports)
1877 if type_name.len > 0 {
1878 local_types[lhs.value] = type_name
1879 }
1880 }
1881 }
1882 i += 2
1883 }
1884}
1885
1886fn (c &CallCollector) collect_top_level_global_decl_calls(node &flat.Node, cur_module string, imports map[string]string, mut local_values map[string]bool, mut local_types map[string]string, mut calls []string) {
1887 if node.kind != .global_decl {
1888 return
1889 }
1890 pre_values := markused_clone_bool_map(local_values)
1891 pre_types := markused_clone_string_map(local_types)
1892 for i in 0 .. node.children_count {
1893 field := c.a.child_node(node, i)
1894 if field.children_count == 0 {
1895 continue
1896 }
1897 expr_id := c.a.child(field, 0)
1898 if int(expr_id) >= 0 {
1899 mut rhs_values := markused_clone_bool_map(pre_values)
1900 mut rhs_types := markused_clone_string_map(pre_types)
1901 c.collect_top_level_stmt_calls(expr_id, cur_module, imports, mut rhs_values, mut
1902 rhs_types, mut calls)
1903 }
1904 }
1905 for i in 0 .. node.children_count {
1906 field := c.a.child_node(node, i)
1907 if field.value.len == 0 {
1908 continue
1909 }
1910 local_values[field.value] = true
1911 if field.children_count > 0 {
1912 expr_id := c.a.child(field, 0)
1913 type_name := c.top_level_decl_rhs_type_name(expr_id, cur_module, imports)
1914 if type_name.len > 0 {
1915 local_types[field.value] = type_name
1916 }
1917 }
1918 }
1919}
1920
1921fn (c &CallCollector) collect_top_level_expr_calls(id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) {
1922 if int(id) < 0 {
1923 return
1924 }
1925 mut stack := [id]
1926 for stack.len > 0 {
1927 child_id := stack.pop()
1928 child := c.a.node(child_id)
1929 match child.kind {
1930 .ident {
1931 if child.value !in local_values {
1932 c.collect_fn_value_ident(child_id, child.value, cur_module, imports, false, mut
1933 calls)
1934 }
1935 }
1936 .selector {
1937 if !c.top_level_selector_base_is_local(child, local_values) {
1938 c.collect_fn_value_selector(child_id, child, cur_module, imports, mut calls)
1939 }
1940 }
1941 .call {
1942 c.collect_top_level_call(child_id, child, cur_module, imports, local_values,
1943 local_types, mut calls)
1944 }
1945 .prefix {
1946 if child.op == .amp && child.children_count > 0 {
1947 inner_id := c.a.child(child, 0)
1948 if int(inner_id) >= 0 {
1949 inner := c.a.node(inner_id)
1950 if inner.kind == .struct_init {
1951 calls << 'memdup'
1952 }
1953 }
1954 }
1955 }
1956 .string_interp {
1957 calls << 'string_plus_many'
1958 }
1959 .assign, .selector_assign, .index_assign {
1960 c.collect_assign_operator_call(child, cur_module, mut calls)
1961 }
1962 .infix {
1963 if child.op == .plus {
1964 calls << 'string__plus'
1965 }
1966 if child.children_count >= 2 {
1967 lhs_id := c.a.child(child, 0)
1968 rhs_id := c.a.child(child, 1)
1969 if child.op in [.dot, .amp] {
1970 lhs_name := c.qualified_expr_name(lhs_id)
1971 if lhs_name.len > 0 {
1972 rhs := c.a.node(rhs_id)
1973 if rhs.kind == .call && rhs.children_count > 0 {
1974 fn_node := c.a.child_node(rhs, 0)
1975 if fn_node.kind == .ident && fn_node.value.len > 0 {
1976 mod_name := if lhs_name in imports {
1977 imports[lhs_name]
1978 } else {
1979 lhs_name
1980 }
1981 calls << mod_name + '.' + fn_node.value
1982 calls << qualify_fn(cur_module, lhs_name + '.' + fn_node.value)
1983 }
1984 }
1985 }
1986 }
1987 if int(lhs_id) >= 0 {
1988 c.collect_struct_operator_call(lhs_id, child.op, cur_module, mut calls)
1989 }
1990 }
1991 }
1992 .or_expr {
1993 if child.children_count > 0 {
1994 expr_id := c.a.child(child, 0)
1995 c.collect_zero_struct_default_calls(c.node_type(expr_id), cur_module, imports, mut
1996 calls)
1997 }
1998 }
1999 .struct_init {
2000 c.collect_struct_default_calls(child, cur_module, imports, mut calls)
2001 }
2002 else {}
2003 }
2004
2005 if child.children_count > 0 {
2006 mut j := int(child.children_count) - 1
2007 for j >= 0 {
2008 next_id := c.a.child(child, j)
2009 if int(next_id) >= 0 {
2010 stack << next_id
2011 }
2012 j--
2013 }
2014 }
2015 }
2016}
2017
2018fn (c &CallCollector) top_level_selector_base_is_local(node flat.Node, local_values map[string]bool) bool {
2019 return c.selector_base_is_local(&node, local_values)
2020}
2021
2022fn (c &CallCollector) selector_base_is_local(node &flat.Node, local_values map[string]bool) bool {
2023 if node.children_count == 0 {
2024 return false
2025 }
2026 base_id := c.a.child(node, 0)
2027 if int(base_id) < 0 {
2028 return false
2029 }
2030 base := c.a.node(base_id)
2031 return base.kind == .ident && base.value in local_values
2032}
2033
2034fn (c &CallCollector) collect_top_level_call(call_id flat.NodeId, call &flat.Node, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) {
2035 mut resolved_call := ''
2036 if resolved := c.tc.resolved_call_name(call_id) {
2037 resolved_call = resolved
2038 }
2039 if call.children_count == 0 {
2040 if resolved_call.len > 0 {
2041 calls << resolved_call
2042 }
2043 return
2044 }
2045 callee_id := c.a.child(call, 0)
2046 if int(callee_id) < 0 {
2047 return
2048 }
2049 callee := c.a.node(callee_id)
2050 if callee.kind == .ident && callee.value.len > 0 {
2051 if resolved_call.len > 0 {
2052 calls << resolved_call
2053 }
2054 if callee.value !in local_values {
2055 calls << callee.value
2056 qcallee := qualify_fn(cur_module, callee.value)
2057 if qcallee != callee.value {
2058 calls << qcallee
2059 }
2060 }
2061 } else if callee.kind == .selector && callee.value.len > 0 {
2062 c.collect_top_level_selector_call(callee, callee.value, resolved_call, cur_module, imports,
2063 local_values, local_types, mut calls)
2064 } else if resolved_call.len > 0 {
2065 calls << resolved_call
2066 }
2067 for ci in 1 .. call.children_count {
2068 arg_id := c.a.child(call, ci)
2069 if int(arg_id) >= 0 {
2070 arg := c.a.node(arg_id)
2071 if arg.kind == .ident && arg.value.len > 0 {
2072 if arg.value !in local_values {
2073 c.collect_fn_value_ident(arg_id, arg.value, cur_module, imports, false, mut
2074 calls)
2075 }
2076 }
2077 }
2078 }
2079}
2080
2081fn (c &CallCollector) collect_top_level_selector_call(callee &flat.Node, method string, resolved_call string, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) {
2082 mut has_exact_selector_call := false
2083 if callee.children_count > 0 {
2084 base_id := c.a.child(callee, 0)
2085 if int(base_id) >= 0 {
2086 base := c.a.node(base_id)
2087 has_exact_selector_call = c.collect_checker_selected_call(resolved_call, mut calls)
2088 if !has_exact_selector_call {
2089 has_exact_selector_call = c.collect_top_level_typed_receiver_method(base_id,
2090 method, cur_module, imports, local_values, local_types, mut calls)
2091 }
2092 if !has_exact_selector_call && base.kind == .ident && base.value in imports
2093 && base.value !in local_values {
2094 calls << imports[base.value] + '.' + method
2095 has_exact_selector_call = true
2096 }
2097 if !has_exact_selector_call && resolved_call.len > 0 {
2098 calls << resolved_call
2099 has_exact_selector_call = true
2100 }
2101 if !has_exact_selector_call {
2102 c.collect_top_level_selector_fallback(base, method, cur_module, imports,
2103 local_values, mut calls)
2104 }
2105 }
2106 }
2107}
2108
2109fn (c &CallCollector) collect_checker_selected_call(resolved_call string, mut calls []string) bool {
2110 if resolved_call.len == 0 || markused_is_builtin_collection_resolved_call(resolved_call)
2111 || !c.is_known_fn_name(resolved_call) {
2112 return false
2113 }
2114 c.add_typed_receiver_method_name(resolved_call, mut calls)
2115 return true
2116}
2117
2118fn markused_is_builtin_collection_resolved_call(name string) bool {
2119 return name.len == 0 || markused_is_raw_collection_method_name(name, 'array.')
2120 || name == 'array_clone' || markused_is_runtime_collection_helper_name(name)
2121 || markused_is_raw_collection_method_name(name, 'map.')
2122}
2123
2124fn markused_is_raw_collection_method_name(name string, prefix string) bool {
2125 if !name.starts_with(prefix) {
2126 return false
2127 }
2128 rest := name[prefix.len..]
2129 return rest.len > 0 && !rest.contains('.')
2130}
2131
2132fn markused_is_runtime_collection_helper_name(name string) bool {
2133 return name in ['array__clone', 'array__reverse', 'array__prepend', 'array__insert',
2134 'array__push_many', 'array__needs_unique_shift', 'map__delete', 'map__move', 'map__reserve',
2135 'map__keys', 'map__values', 'map__clear', 'map__free', 'map__get', 'map__get_check',
2136 'map__exists', 'map__set']
2137}
2138
2139fn (c &CallCollector) collect_top_level_selector_fallback(base &flat.Node, method string, cur_module string, imports map[string]string, local_values map[string]bool, mut calls []string) {
2140 if base.kind == .ident && base.value.len > 0 {
2141 if base.value in local_values {
2142 return
2143 }
2144 if base.value in imports {
2145 return
2146 }
2147 mod_name := base.value
2148 calls << mod_name + '.' + method
2149 calls << qualify_fn(cur_module, base.value + '.' + method)
2150 if base.value.len > 0 && base.value[0] >= `A` && base.value[0] <= `Z` {
2151 named_type := c.tc.parse_type(base.value)
2152 named_type_name := resolve_type_name(named_type)
2153 if named_type_name.len > 0 {
2154 calls << named_type_name + '.' + method
2155 }
2156 }
2157 } else if base.kind == .selector && base.children_count > 0 {
2158 inner_id := c.a.child(base, 0)
2159 if int(inner_id) >= 0 {
2160 inner := c.a.node(inner_id)
2161 if inner.kind == .ident && inner.value.len > 0 {
2162 mod_name := if inner.value in imports {
2163 imports[inner.value]
2164 } else {
2165 inner.value
2166 }
2167 calls << mod_name + '.' + base.value + '.' + method
2168 }
2169 }
2170 }
2171}
2172
2173fn (c &CallCollector) collect_top_level_typed_receiver_method(base_id flat.NodeId, method string, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) bool {
2174 type_name := c.top_level_receiver_type_name(base_id, cur_module, imports, local_values,
2175 local_types)
2176 if type_name.len == 0 {
2177 return false
2178 }
2179 method_name := c.typed_receiver_method_name(type_name, method, cur_module) or { return false }
2180 c.add_typed_receiver_method_name(method_name, mut calls)
2181 return true
2182}
2183
2184fn (c &CallCollector) top_level_receiver_type_name(base_id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string) string {
2185 base := c.a.node(base_id)
2186 type_name := resolve_type_name(c.node_type(base_id))
2187 if type_name.len > 0 {
2188 struct_type := c.struct_lookup_name(type_name, cur_module)
2189 if struct_type.len > 0 {
2190 return struct_type
2191 }
2192 return type_name
2193 }
2194 if base.kind == .ident && base.value.len > 0 {
2195 if local_type := local_types[base.value] {
2196 return local_type
2197 }
2198 if base.value in local_values {
2199 return ''
2200 }
2201 if base.value in imports {
2202 return ''
2203 }
2204 }
2205 if base.kind == .ident && base.value.len > 0 {
2206 return c.value_type_name(base.value, cur_module, imports)
2207 }
2208 if base.kind == .selector {
2209 name := c.qualified_expr_name(base_id)
2210 if name.len > 0 {
2211 return c.value_type_name(name, cur_module, imports)
2212 }
2213 }
2214 return ''
2215}
2216
2217// collect_struct_operator_call updates collect struct operator call state for markused.
2218fn (c &CallCollector) collect_struct_operator_call(lhs_id flat.NodeId, op flat.Op, cur_module string, mut calls []string) {
2219 lhs_type := c.node_type(lhs_id)
2220 lhs_name := resolve_type_name(lhs_type)
2221 struct_type := c.struct_lookup_name(lhs_name, cur_module)
2222 if struct_type.len == 0 {
2223 return
2224 }
2225 method_name := c.struct_operator_call_name(struct_type, op) or { return }
2226 c.add_operator_call_name(method_name, mut calls)
2227}
2228
2229// collect_assign_operator_call adds operator overloads used through assignment operators.
2230fn (c &CallCollector) collect_assign_operator_call(node &flat.Node, cur_module string, mut calls []string) {
2231 op := markused_assign_operator_symbol(node.op) or { return }
2232 mut i := 0
2233 for i + 1 < node.children_count {
2234 lhs_id := c.a.child(node, i)
2235 if int(lhs_id) >= 0 {
2236 c.collect_struct_operator_call(lhs_id, op, cur_module, mut calls)
2237 }
2238 i += 2
2239 }
2240}
2241
2242// struct_operator_call_name supports struct operator call name handling for CallCollector.
2243fn (c &CallCollector) struct_operator_call_name(struct_type string, op flat.Op) ?string {
2244 if op_name := markused_struct_operator_symbol(op) {
2245 if method_name := c.struct_operator_fn_name(struct_type, op_name) {
2246 return method_name
2247 }
2248 }
2249 match op {
2250 .gt, .ge, .le {
2251 if method_name := c.struct_operator_fn_name(struct_type, '<') {
2252 return method_name
2253 }
2254 }
2255 .ne {
2256 if method_name := c.struct_operator_fn_name(struct_type, '==') {
2257 return method_name
2258 }
2259 }
2260 else {}
2261 }
2262
2263 return none
2264}
2265
2266// markused_assign_operator_symbol maps assignment operators to their binary operator.
2267fn markused_assign_operator_symbol(op flat.Op) ?flat.Op {
2268 match op {
2269 .plus_assign { return .plus }
2270 .minus_assign { return .minus }
2271 .mul_assign { return .mul }
2272 .div_assign { return .div }
2273 .mod_assign { return .mod }
2274 else {}
2275 }
2276
2277 return none
2278}
2279
2280// markused_struct_operator_symbol supports markused struct operator symbol handling for markused.
2281fn markused_struct_operator_symbol(op flat.Op) ?string {
2282 match op {
2283 .plus { return '+' }
2284 .minus { return '-' }
2285 .mul { return '*' }
2286 .div { return '/' }
2287 .mod { return '%' }
2288 .eq { return '==' }
2289 .ne { return '!=' }
2290 .lt { return '<' }
2291 .gt { return '>' }
2292 .le { return '<=' }
2293 .ge { return '>=' }
2294 else {}
2295 }
2296
2297 return none
2298}
2299
2300// struct_operator_fn_name supports struct operator fn name handling for CallCollector.
2301fn (c &CallCollector) struct_operator_fn_name(struct_type string, op_name string) ?string {
2302 method_name := '${struct_type}.${op_name}'
2303 if c.is_known_fn_name(method_name) {
2304 return method_name
2305 }
2306 cmethod_name := markused_c_name(method_name)
2307 if c.is_known_fn_name(cmethod_name) {
2308 return cmethod_name
2309 }
2310 return none
2311}
2312
2313// is_known_fn_name reports whether is known fn name applies in markused.
2314fn (c &CallCollector) is_known_fn_name(name string) bool {
2315 return name in c.fn_decls || name in c.tc.fn_ret_types || name in c.tc.fn_param_types
2316}
2317
2318// struct_lookup_name supports struct lookup name handling for CallCollector.
2319fn (c &CallCollector) struct_lookup_name(type_name string, cur_module string) string {
2320 if type_name.len == 0 {
2321 return ''
2322 }
2323 if type_name.contains('.') {
2324 if type_name in c.struct_decls {
2325 return type_name
2326 }
2327 short_type := type_name.all_after_last('.')
2328 type_mod := type_name.all_before_last('.')
2329 if info := c.struct_decls[short_type] {
2330 if info.module == type_mod {
2331 return short_type
2332 }
2333 }
2334 if info := c.struct_decls[type_name] {
2335 if info.module == type_mod {
2336 return type_name
2337 }
2338 }
2339 return ''
2340 }
2341 if info := c.struct_decls[type_name] {
2342 if info.module.len == 0 || info.module == cur_module {
2343 return type_name
2344 }
2345 }
2346 if cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' {
2347 qtype := '${cur_module}.${type_name}'
2348 if qtype in c.struct_decls {
2349 return qtype
2350 }
2351 }
2352 return ''
2353}
2354
2355// add_operator_call_name updates add operator call name state for CallCollector.
2356fn (c &CallCollector) add_operator_call_name(method_name string, mut calls []string) {
2357 calls << method_name
2358 lowered := markused_c_name(method_name)
2359 if lowered != method_name {
2360 calls << lowered
2361 }
2362}
2363
2364// qualified_expr_name supports qualified expr name handling for CallCollector.
2365fn (c &CallCollector) qualified_expr_name(id flat.NodeId) string {
2366 if int(id) < 0 {
2367 return ''
2368 }
2369 node := c.a.nodes[int(id)]
2370 match node.kind {
2371 .ident {
2372 return node.value
2373 }
2374 .selector {
2375 if node.children_count == 0 {
2376 return node.value
2377 }
2378 base := c.qualified_expr_name(c.a.child(&node, 0))
2379 if base.len == 0 {
2380 return node.value
2381 }
2382 return base + '.' + node.value
2383 }
2384 else {
2385 return ''
2386 }
2387 }
2388}
2389
2390// collect_fn_value_ident updates collect fn value ident state for markused.
2391fn (c &CallCollector) collect_fn_value_ident(id flat.NodeId, name string, cur_module string, imports map[string]string, is_local_value bool, mut calls []string) {
2392 if is_local_value {
2393 return
2394 }
2395 if resolved := c.tc.resolved_fn_value_name(id) {
2396 calls << resolved
2397 return
2398 }
2399 if name.len == 0 || !c.name_may_reference_fn(name, cur_module, imports) {
2400 return
2401 }
2402 has_fn_decl := c.name_has_fn_decl(name, cur_module, imports)
2403 if !has_fn_decl && !c.node_is_fn_value(id) {
2404 return
2405 }
2406 c.add_fn_value_candidates(name, cur_module, imports, mut calls)
2407 c.add_const_alias_candidates(name, cur_module, imports, mut calls)
2408}
2409
2410// collect_fn_value_selector updates collect fn value selector state for markused.
2411fn (c &CallCollector) collect_fn_value_selector(id flat.NodeId, node &flat.Node, cur_module string, imports map[string]string, mut calls []string) {
2412 if resolved := c.tc.resolved_fn_value_name(id) {
2413 calls << resolved
2414 return
2415 }
2416 if node.children_count == 0 {
2417 return
2418 }
2419 base := c.a.child_node(node, 0)
2420 if base.kind != .ident || base.value.len == 0 || node.value.len == 0 {
2421 return
2422 }
2423 name := '${base.value}.${node.value}'
2424 if !c.name_may_reference_fn(name, cur_module, imports) {
2425 return
2426 }
2427 has_fn_decl := c.name_has_fn_decl(name, cur_module, imports)
2428 if !has_fn_decl && !c.node_is_fn_value(id) {
2429 return
2430 }
2431 c.add_fn_value_candidates(name, cur_module, imports, mut calls)
2432 c.add_const_alias_candidates(name, cur_module, imports, mut calls)
2433 if base.value in imports {
2434 mod_name := imports[base.value]
2435 resolved_name := '${mod_name}.${node.value}'
2436 c.add_fn_value_candidates(resolved_name, cur_module, imports, mut calls)
2437 c.add_const_alias_candidates(resolved_name, cur_module, imports, mut calls)
2438 }
2439}
2440
2441// name_may_reference_fn returns name may reference fn data for CallCollector.
2442fn (c &CallCollector) name_may_reference_fn(name string, cur_module string, imports map[string]string) bool {
2443 return c.name_has_candidate_decl(name, cur_module, imports, true)
2444}
2445
2446// name_has_fn_decl returns name has fn decl data for CallCollector.
2447fn (c &CallCollector) name_has_fn_decl(name string, cur_module string, imports map[string]string) bool {
2448 return c.name_has_candidate_decl(name, cur_module, imports, false)
2449}
2450
2451// name_has_candidate_decl returns name has candidate decl data for CallCollector.
2452fn (c &CallCollector) name_has_candidate_decl(name string, cur_module string, imports map[string]string, include_consts bool) bool {
2453 if c.candidate_matches_decl(name, include_consts) {
2454 return true
2455 }
2456 qname := qualify_fn(cur_module, name)
2457 if qname != name && c.candidate_matches_decl(qname, include_consts) {
2458 return true
2459 }
2460 if name.contains('.') {
2461 base := name.all_before_last('.')
2462 member := name.all_after_last('.')
2463 if base in imports {
2464 imported_name := imports[base] + '.' + member
2465 if c.candidate_matches_decl(imported_name, include_consts) {
2466 return true
2467 }
2468 }
2469 }
2470 return false
2471}
2472
2473// candidate_matches_decl supports candidate matches decl handling for CallCollector.
2474fn (c &CallCollector) candidate_matches_decl(candidate string, include_consts bool) bool {
2475 if candidate in c.fn_decls {
2476 return true
2477 }
2478 return include_consts && candidate in c.const_decls
2479}
2480
2481// node_is_fn_value supports node is fn value handling for CallCollector.
2482fn (c &CallCollector) node_is_fn_value(id flat.NodeId) bool {
2483 expr_type := c.node_type(id)
2484 return expr_type is types.FnType
2485}
2486
2487// add_fn_value_candidates updates add fn value candidates state for CallCollector.
2488fn (c &CallCollector) add_fn_value_candidates(name string, cur_module string, imports map[string]string, mut calls []string) {
2489 if name.len == 0 {
2490 return
2491 }
2492 calls << name
2493 qname := qualify_fn(cur_module, name)
2494 if qname != name {
2495 calls << qname
2496 }
2497 if name.contains('.') {
2498 base := name.all_before_last('.')
2499 member := name.all_after_last('.')
2500 if base in imports {
2501 calls << imports[base] + '.' + member
2502 }
2503 }
2504}
2505
2506// add_const_alias_candidates converts add const alias candidates data for markused.
2507fn (c &CallCollector) add_const_alias_candidates(name string, cur_module string, imports map[string]string, mut calls []string) {
2508 c.add_const_alias_candidate(name, imports, mut calls)
2509 qname := qualify_fn(cur_module, name)
2510 if qname != name {
2511 c.add_const_alias_candidate(qname, imports, mut calls)
2512 }
2513 if name.contains('.') {
2514 base := name.all_before_last('.')
2515 member := name.all_after_last('.')
2516 if base in imports {
2517 c.add_const_alias_candidate(imports[base] + '.' + member, imports, mut calls)
2518 }
2519 }
2520}
2521
2522// add_const_alias_candidate converts add const alias candidate data for markused.
2523fn (c &CallCollector) add_const_alias_candidate(const_name string, imports map[string]string, mut calls []string) {
2524 info := c.const_decls[const_name] or { return }
2525 expr := c.a.node(info.expr_id)
2526 match expr.kind {
2527 .ident {
2528 c.add_fn_value_candidates(expr.value, info.module, imports, mut calls)
2529 }
2530 .selector {
2531 if expr.children_count > 0 {
2532 base := c.a.child_node(expr, 0)
2533 if base.kind == .ident && base.value.len > 0 && expr.value.len > 0 {
2534 c.add_fn_value_candidates('${base.value}.${expr.value}', info.module, imports, mut
2535 calls)
2536 if base.value in imports {
2537 c.add_fn_value_candidates('${imports[base.value]}.${expr.value}',
2538 info.module, imports, mut calls)
2539 }
2540 }
2541 }
2542 }
2543 else {}
2544 }
2545}
2546
2547// node_type supports node type handling for CallCollector.
2548fn (c &CallCollector) node_type(id flat.NodeId) types.Type {
2549 if t := c.tc.expr_type(id) {
2550 return t
2551 }
2552 return c.tc.resolve_type(id)
2553}
2554
2555fn (c &CallCollector) collect_typed_receiver_method(base_id flat.NodeId, method string, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string, mut calls []string) bool {
2556 type_name := c.receiver_type_name(base_id, cur_module, imports, local_values, local_types)
2557 if type_name.len == 0 {
2558 return false
2559 }
2560 method_name := c.typed_receiver_method_name(type_name, method, cur_module) or { return false }
2561 c.add_typed_receiver_method_name(method_name, mut calls)
2562 return true
2563}
2564
2565fn (c &CallCollector) receiver_type_name(base_id flat.NodeId, cur_module string, imports map[string]string, local_values map[string]bool, local_types map[string]string) string {
2566 base := c.a.node(base_id)
2567 base_type := c.node_type(base_id)
2568 type_name := resolve_type_name(base_type)
2569 if type_name.len > 0 {
2570 return type_name
2571 }
2572 if base.kind == .ident && base.value.len > 0 {
2573 if local_type := local_types[base.value] {
2574 return local_type
2575 }
2576 }
2577 if base.kind == .ident && base.value.len > 0 {
2578 if base.value in local_values {
2579 return ''
2580 }
2581 return c.value_type_name(base.value, cur_module, imports)
2582 }
2583 if base.kind == .selector {
2584 name := c.qualified_expr_name(base_id)
2585 if name.len > 0 {
2586 return c.value_type_name(name, cur_module, imports)
2587 }
2588 }
2589 return ''
2590}
2591
2592fn (c &CallCollector) typed_receiver_method_name(type_name string, method string, cur_module string) ?string {
2593 if type_name.len == 0 || method.len == 0 {
2594 return none
2595 }
2596 mut candidates := []string{cap: 4}
2597 if type_name.starts_with('map[') {
2598 candidates << markused_map_receiver_method_candidates(type_name, method, cur_module)
2599 } else if type_name.starts_with('[]') {
2600 candidates << markused_array_receiver_method_candidates(type_name, method, cur_module)
2601 } else if type_name.contains('.') {
2602 candidates << '${type_name}.${method}'
2603 unqualified_type := markused_unqualified_receiver_type_name(type_name)
2604 if unqualified_type != type_name {
2605 candidates << '${unqualified_type}.${method}'
2606 }
2607 } else {
2608 if cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin' {
2609 candidates << '${cur_module}.${type_name}.${method}'
2610 }
2611 candidates << '${type_name}.${method}'
2612 }
2613 for candidate in candidates {
2614 if c.is_known_fn_name(candidate) {
2615 return candidate
2616 }
2617 lowered := markused_c_name(candidate)
2618 if lowered != candidate && c.is_known_fn_name(lowered) {
2619 return lowered
2620 }
2621 }
2622 return none
2623}
2624
2625fn markused_can_prefix_collection_receiver(cur_module string) bool {
2626 return cur_module.len > 0 && cur_module != 'main' && cur_module != 'builtin'
2627}
2628
2629fn markused_push_receiver_candidate(mut candidates []string, candidate string) {
2630 if candidate.len > 0 && candidate !in candidates {
2631 candidates << candidate
2632 }
2633}
2634
2635fn markused_array_receiver_method_candidates(receiver_type string, method string, cur_module string) []string {
2636 mut clean_type := receiver_type.trim_space()
2637 for {
2638 if clean_type.starts_with('&') {
2639 clean_type = clean_type[1..].trim_space()
2640 continue
2641 }
2642 break
2643 }
2644 mut candidates := []string{}
2645 markused_push_receiver_candidate(mut candidates, '${clean_type}.${method}')
2646 if !clean_type.starts_with('[]') || clean_type.len <= 2 {
2647 return candidates
2648 }
2649 elem_type := clean_type[2..]
2650 short_elem := if elem_type.contains('.') { elem_type.all_after_last('.') } else { elem_type }
2651 markused_push_receiver_candidate(mut candidates, '[]${short_elem}.${method}')
2652 if elem_type.contains('.') {
2653 markused_push_receiver_candidate(mut candidates,
2654 '${elem_type.all_before_last('.')}.[]${short_elem}.${method}')
2655 } else if markused_can_prefix_collection_receiver(cur_module) {
2656 markused_push_receiver_candidate(mut candidates, '${cur_module}.[]${short_elem}.${method}')
2657 }
2658 return candidates
2659}
2660
2661fn markused_map_receiver_method_candidates(receiver_type string, method string, cur_module string) []string {
2662 clean_type := markused_clean_map_type(receiver_type)
2663 key_type := markused_map_key_type(clean_type)
2664 value_type := markused_map_value_type(clean_type)
2665 mut candidates := []string{}
2666 candidates << '${clean_type}.${method}'
2667 candidates << 'map.${method}'
2668 if key_type.len == 0 || value_type.len == 0 {
2669 markused_push_receiver_candidate(mut candidates, '${clean_type}.${method}')
2670 return candidates
2671 }
2672 key_types := markused_receiver_type_text_variants(key_type)
2673 value_types := markused_receiver_type_text_variants(value_type)
2674 mut map_types := []string{}
2675 for key in key_types {
2676 for value in value_types {
2677 markused_push_receiver_candidate(mut map_types, 'map[${key}]${value}')
2678 }
2679 }
2680 for map_type in map_types {
2681 markused_push_receiver_candidate(mut candidates, '${map_type}.${method}')
2682 }
2683 mut module_names := []string{}
2684 if markused_can_prefix_collection_receiver(cur_module) {
2685 markused_push_receiver_candidate(mut module_names, cur_module)
2686 }
2687 for mod_name in markused_receiver_type_text_module_names(key_type) {
2688 markused_push_receiver_candidate(mut module_names, mod_name)
2689 }
2690 for mod_name in markused_receiver_type_text_module_names(value_type) {
2691 markused_push_receiver_candidate(mut module_names, mod_name)
2692 }
2693 for mod_name in module_names {
2694 for map_type in map_types {
2695 markused_push_receiver_candidate(mut candidates, '${mod_name}.${map_type}.${method}')
2696 }
2697 }
2698 return candidates
2699}
2700
2701fn markused_receiver_type_text_variants(type_text string) []string {
2702 clean := type_text.trim_space()
2703 mut names := []string{}
2704 markused_push_receiver_candidate(mut names, clean)
2705 markused_push_receiver_candidate(mut names, markused_receiver_type_text_short_spelling(clean))
2706 if markused_type_text_is_fixed_array(clean) {
2707 source := markused_receiver_type_text_source_fixed_spelling(clean)
2708 markused_push_receiver_candidate(mut names, source)
2709 markused_push_receiver_candidate(mut names,
2710 markused_receiver_type_text_short_spelling(source))
2711 }
2712 return names
2713}
2714
2715fn markused_receiver_type_text_source_fixed_spelling(type_text string) string {
2716 clean := type_text.trim_space()
2717 if clean.len == 0 || clean.starts_with('[') || !markused_type_text_is_fixed_array(clean) {
2718 return clean
2719 }
2720 elem, dims := markused_postfix_fixed_array_parts(clean)
2721 if elem.len == 0 || dims.len == 0 {
2722 return clean
2723 }
2724 mut source := elem
2725 for i := dims.len; i > 0; i-- {
2726 source = '[${dims[i - 1]}]${source}'
2727 }
2728 return source
2729}
2730
2731fn markused_postfix_fixed_array_parts(type_text string) (string, []string) {
2732 clean := type_text.trim_space()
2733 mut end := clean.len
2734 mut dims := []string{}
2735 for end > 0 && clean[end - 1] == `]` {
2736 start := markused_trailing_matching_bracket_start(clean, end)
2737 if start < 0 {
2738 break
2739 }
2740 dims << clean[start + 1..end - 1].trim_space()
2741 end = start
2742 }
2743 return clean[..end], dims
2744}
2745
2746fn markused_trailing_matching_bracket_start(s string, end int) int {
2747 mut depth := 0
2748 for i := end - 1; i >= 0; i-- {
2749 if s[i] == `]` {
2750 depth++
2751 } else if s[i] == `[` {
2752 depth--
2753 if depth == 0 {
2754 return i
2755 }
2756 }
2757 }
2758 return -1
2759}
2760
2761fn markused_receiver_type_text_short_spelling(type_text string) string {
2762 clean := type_text.trim_space()
2763 if clean.starts_with('[]') {
2764 return '[]' + markused_receiver_type_text_short_spelling(clean[2..])
2765 }
2766 if clean.starts_with('[') {
2767 bracket_end := markused_matching_bracket(clean, 0)
2768 if bracket_end < clean.len {
2769 return clean[..bracket_end + 1] +
2770 markused_receiver_type_text_short_spelling(clean[bracket_end + 1..])
2771 }
2772 }
2773 if clean.starts_with('map[') {
2774 bracket_end := markused_matching_bracket(clean, 3)
2775 if bracket_end < clean.len {
2776 key := markused_receiver_type_text_short_spelling(clean[4..bracket_end])
2777 value := markused_receiver_type_text_short_spelling(clean[bracket_end + 1..])
2778 return 'map[${key}]${value}'
2779 }
2780 }
2781 if clean.contains('.') {
2782 return clean.all_after_last('.')
2783 }
2784 return clean
2785}
2786
2787fn markused_receiver_type_text_module_names(type_text string) []string {
2788 clean := type_text.trim_space()
2789 mut names := []string{}
2790 if clean.starts_with('[]') {
2791 return markused_receiver_type_text_module_names(clean[2..])
2792 }
2793 if clean.starts_with('[') {
2794 bracket_end := markused_matching_bracket(clean, 0)
2795 if bracket_end < clean.len {
2796 return markused_receiver_type_text_module_names(clean[bracket_end + 1..])
2797 }
2798 }
2799 if clean.starts_with('map[') {
2800 key_type := markused_map_key_type(clean)
2801 value_type := markused_map_value_type(clean)
2802 for name in markused_receiver_type_text_module_names(key_type) {
2803 markused_push_receiver_candidate(mut names, name)
2804 }
2805 for name in markused_receiver_type_text_module_names(value_type) {
2806 markused_push_receiver_candidate(mut names, name)
2807 }
2808 return names
2809 }
2810 if markused_type_text_is_fixed_array(clean) {
2811 return markused_receiver_type_text_module_names(markused_fixed_array_elem_type(clean))
2812 }
2813 if clean.contains('.') {
2814 markused_push_receiver_candidate(mut names, clean.all_before_last('.'))
2815 }
2816 return names
2817}
2818
2819fn markused_clean_map_type(receiver_type string) string {
2820 mut clean := receiver_type.trim_space()
2821 for {
2822 if clean.starts_with('shared ') {
2823 clean = clean[7..].trim_space()
2824 continue
2825 }
2826 if clean.starts_with('&') {
2827 clean = clean[1..].trim_space()
2828 continue
2829 }
2830 break
2831 }
2832 return clean
2833}
2834
2835fn markused_type_text_is_fixed_array(type_text string) bool {
2836 clean := type_text.trim_space()
2837 if clean.len == 0 || clean.starts_with('[]') || clean.starts_with('map[') {
2838 return false
2839 }
2840 if clean.starts_with('[') {
2841 bracket_end := markused_matching_bracket(clean, 0)
2842 return bracket_end < clean.len
2843 }
2844 if !clean.contains('[') || !clean.ends_with(']') {
2845 return false
2846 }
2847 len_text := markused_fixed_array_len_text(clean)
2848 return len_text.len > 0 && !len_text.contains(',')
2849}
2850
2851fn markused_fixed_array_len_text(type_text string) string {
2852 return type_text.all_after('[').all_before(']').trim_space()
2853}
2854
2855fn markused_fixed_array_elem_type(type_text string) string {
2856 if type_text.starts_with('[') {
2857 bracket_end := markused_matching_bracket(type_text, 0)
2858 if bracket_end < type_text.len {
2859 return type_text[bracket_end + 1..]
2860 }
2861 return ''
2862 }
2863 return type_text.all_before('[')
2864}
2865
2866fn markused_map_key_type(type_str string) string {
2867 if !type_str.starts_with('map[') {
2868 return ''
2869 }
2870 bracket_end := markused_matching_bracket(type_str, 3)
2871 if bracket_end > 4 {
2872 return type_str[4..bracket_end]
2873 }
2874 return ''
2875}
2876
2877fn markused_map_value_type(type_str string) string {
2878 if !type_str.starts_with('map[') {
2879 return ''
2880 }
2881 bracket_end := markused_matching_bracket(type_str, 3)
2882 if bracket_end + 1 < type_str.len {
2883 return type_str[bracket_end + 1..]
2884 }
2885 return ''
2886}
2887
2888fn markused_matching_bracket(s string, start int) int {
2889 mut depth := 0
2890 for i in start .. s.len {
2891 if s[i] == `[` {
2892 depth++
2893 } else if s[i] == `]` {
2894 depth--
2895 if depth == 0 {
2896 return i
2897 }
2898 }
2899 }
2900 return s.len
2901}
2902
2903fn markused_unqualified_receiver_type_name(type_name string) string {
2904 if !type_name.starts_with('map[') {
2905 return type_name.all_after_last('.')
2906 }
2907 key_start := 'map['.len
2908 key_end := type_name.index_u8(`]`)
2909 if key_end < key_start {
2910 return type_name
2911 }
2912 key := type_name[key_start..key_end].all_after_last('.')
2913 value := type_name[key_end + 1..].all_after_last('.')
2914 return 'map[${key}]${value}'
2915}
2916
2917fn (c &CallCollector) add_typed_receiver_method_name(method_name string, mut calls []string) {
2918 calls << method_name
2919 lowered := markused_c_name(method_name)
2920 if lowered != method_name {
2921 calls << lowered
2922 }
2923}
2924
2925fn (c &CallCollector) value_type_name(name string, cur_module string, imports map[string]string) string {
2926 for candidate in c.value_name_candidates(name, cur_module, imports) {
2927 if typ := c.tc.file_scope.lookup(candidate) {
2928 type_name := resolve_type_name(typ)
2929 if type_name.len > 0 {
2930 return type_name
2931 }
2932 }
2933 if typ := c.tc.const_types[candidate] {
2934 type_name := resolve_type_name(typ)
2935 if type_name.len > 0 {
2936 return type_name
2937 }
2938 }
2939 if info := c.const_decls[candidate] {
2940 type_name := c.const_initializer_type_name(info)
2941 if type_name.len > 0 {
2942 return type_name
2943 }
2944 }
2945 }
2946 return ''
2947}
2948
2949fn (c &CallCollector) const_initializer_type_name(info ConstDeclInfo) string {
2950 expr := c.a.node(info.expr_id)
2951 if expr.kind == .struct_init && expr.value.len > 0 {
2952 return c.struct_lookup_name(expr.value, info.module)
2953 }
2954 type_name := resolve_type_name(c.node_type(info.expr_id))
2955 if type_name.len > 0 {
2956 return type_name
2957 }
2958 return ''
2959}
2960
2961fn (c &CallCollector) value_name_candidates(name string, cur_module string, imports map[string]string) []string {
2962 mut candidates := []string{cap: 3}
2963 qname := qualify_fn(cur_module, name)
2964 candidates << qname
2965 if qname != name {
2966 candidates << name
2967 }
2968 if name.contains('.') {
2969 base := name.all_before_last('.')
2970 member := name.all_after_last('.')
2971 if base in imports {
2972 candidates << '${imports[base]}.${member}'
2973 }
2974 }
2975 return candidates
2976}
2977
2978fn markused_resolve_imported_type_name(name string, imports map[string]string) string {
2979 if !name.contains('.') {
2980 return name
2981 }
2982 base := name.all_before_last('.')
2983 member := name.all_after_last('.')
2984 if base in imports {
2985 return '${imports[base]}.${member}'
2986 }
2987 return name
2988}
2989
2990// collect_zero_struct_default_calls updates collect zero struct default calls state for markused.
2991fn (c &CallCollector) collect_zero_struct_default_calls(typ types.Type, cur_module string, imports map[string]string, mut calls []string) {
2992 type_name := zero_value_struct_type_name(typ)
2993 if type_name.len == 0 {
2994 return
2995 }
2996 c.collect_struct_default_calls_for_type(type_name, cur_module, imports, mut calls)
2997}
2998
2999// zero_value_struct_type_name supports zero value struct type name handling for markused.
3000fn zero_value_struct_type_name(typ types.Type) string {
3001 mut clean := typ
3002 for _ in 0 .. 8 {
3003 if clean is types.Alias {
3004 clean = clean.base_type
3005 continue
3006 }
3007 if clean is types.OptionType {
3008 clean = clean.base_type
3009 continue
3010 }
3011 if clean is types.ResultType {
3012 clean = clean.base_type
3013 continue
3014 }
3015 break
3016 }
3017 if clean is types.Struct {
3018 return clean.name
3019 }
3020 return ''
3021}
3022
3023// collect_struct_default_calls updates collect struct default calls state for markused.
3024fn (c &CallCollector) collect_struct_default_calls(init &flat.Node, cur_module string, imports map[string]string, mut calls []string) {
3025 info := c.struct_decl_info(init.value, cur_module) or { return }
3026 mut set_fields := map[string]bool{}
3027 for i in 0 .. init.children_count {
3028 field := c.a.child_node(init, i)
3029 if field.kind == .field_init {
3030 set_fields[field.value] = true
3031 }
3032 }
3033 c.collect_struct_default_calls_from_info(info, set_fields, imports, mut calls)
3034}
3035
3036// collect_struct_default_calls_for_type supports collect_struct_default_calls_for_type handling.
3037fn (c &CallCollector) collect_struct_default_calls_for_type(type_name string, cur_module string, imports map[string]string, mut calls []string) {
3038 info := c.struct_decl_info(type_name, cur_module) or { return }
3039 c.collect_struct_default_calls_from_info(info, map[string]bool{}, imports, mut calls)
3040}
3041
3042// struct_decl_info supports struct decl info handling for CallCollector.
3043fn (c &CallCollector) struct_decl_info(type_name string, cur_module string) ?StructDeclInfo {
3044 struct_name := c.struct_lookup_name(type_name, cur_module)
3045 if struct_name.len == 0 {
3046 return none
3047 }
3048 return c.struct_decls[struct_name] or { none }
3049}
3050
3051// collect_struct_default_calls_from_info supports collect_struct_default_calls_from_info handling.
3052fn (c &CallCollector) collect_struct_default_calls_from_info(info StructDeclInfo, provided map[string]bool, imports map[string]string, mut calls []string) {
3053 node := c.a.node(info.node_id)
3054 for i in 0 .. node.children_count {
3055 field := c.a.child_node(node, i)
3056 if field.kind != .field_decl || field.children_count == 0 || field.value in provided {
3057 continue
3058 }
3059 c.collect_calls(field, info.module, imports, '', '', mut calls)
3060 }
3061}
3062
3063// resolve_type_name resolves resolve type name information for markused.
3064fn resolve_type_name(t types.Type) string {
3065 if t is types.Alias {
3066 return t.name
3067 } else if t is types.Struct {
3068 return t.name
3069 } else if t is types.Interface {
3070 return t.name
3071 } else if t is types.SumType {
3072 return t.name
3073 } else if t is types.Enum {
3074 return t.name
3075 } else if t is types.String {
3076 return 'string'
3077 } else if t is types.Array {
3078 return '[]${markused_nested_type_name(t.elem_type)}'
3079 } else if t is types.ArrayFixed {
3080 mut len_text := t.len.str()
3081 if t.len_expr.len > 0 {
3082 len_text = t.len_expr
3083 }
3084 return '${markused_nested_type_name(t.elem_type)}[${len_text}]'
3085 } else if t is types.Map {
3086 return 'map[${markused_nested_type_name(t.key_type)}]${markused_nested_type_name(t.value_type)}'
3087 } else if t is types.Pointer {
3088 return resolve_type_name(t.base_type)
3089 } else if t is types.Primitive {
3090 props := int(t.props)
3091 sz := int(t.size)
3092 if props & 1 > 0 {
3093 return 'bool'
3094 }
3095 if props & 4 > 0 {
3096 if props & 8 > 0 {
3097 return match sz {
3098 8 { 'u8' }
3099 16 { 'u16' }
3100 32 { 'u32' }
3101 64 { 'u64' }
3102 else { 'int' }
3103 }
3104 }
3105 return match sz {
3106 0 { 'int' }
3107 8 { 'i8' }
3108 16 { 'i16' }
3109 32 { 'i32' }
3110 64 { 'i64' }
3111 else { 'int' }
3112 }
3113 }
3114 if props & 2 > 0 {
3115 return match sz {
3116 32 { 'f32' }
3117 64 { 'f64' }
3118 else { 'f64' }
3119 }
3120 }
3121 return 'int'
3122 } else if t is types.ISize {
3123 return 'isize'
3124 } else if t is types.USize {
3125 return 'usize'
3126 } else if t is types.Rune {
3127 return 'rune'
3128 }
3129 return ''
3130}
3131
3132fn markused_nested_type_name(t types.Type) string {
3133 return t.name()
3134}
3135
3136// markused_c_name converts markused c name data for markused.
3137fn markused_c_name(name string) string {
3138 if name.starts_with('C.') {
3139 return name[2..]
3140 }
3141 if name == 'malloc' {
3142 return 'v_malloc'
3143 }
3144 if name == 'exit' {
3145 return 'v_exit'
3146 }
3147 if markused_c_name_is_plain(name) {
3148 return name
3149 }
3150 return name.replace('[]', 'Array_').replace('.-', '__minus').replace('.+', '__plus').replace('.==',
3151 '__eq').replace('.!=', '__ne').replace('.<=', '__le').replace('.>=', '__ge').replace('.<',
3152 '__lt').replace('.>', '__gt').replace('&', 'ptr').replace('[', '_').replace(']', '').replace(',',
3153 '_').replace(' ', '_').replace('.', '__')
3154}
3155
3156// markused_c_name_is_plain converts markused c name is plain data for markused.
3157fn markused_c_name_is_plain(name string) bool {
3158 for i in 0 .. name.len {
3159 c := name[i]
3160 if (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`) || c == `_` {
3161 continue
3162 }
3163 return false
3164 }
3165 return true
3166}
3167