vxx2 / vlib / v2_toberemoved / transformer / transformer.v
18649 lines · 17973 sloc · 536.61 KB · c0624b274a458fe3e487d89ae9554c2e8c25bdb6
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module transformer
5
6import os
7import strings
8import time
9import v2.ast
10import v2.pref
11import v2.token
12import v2.types
13
14const embed_file_helper_type_name = '__V2EmbedFileData'
15const c_keywords = ['auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double',
16 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long', 'register',
17 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', 'switch', 'typedef',
18 'union', 'unsigned', 'void', 'volatile', 'while', '_Bool', '_Complex', '_Imaginary', 'unix',
19 'linux']
20const smartcast_search_limit = 128
21const max_runtime_const_dep_expr_depth = 128
22
23// Transformer performs AST-level transformations to simplify
24// and normalize code before codegen. This avoids duplicating
25// transformation logic across multiple backends (SSA, cleanc, etc.)
26pub struct Transformer {
27mut:
28 pref &pref.Preferences = unsafe { nil }
29 env &types.Environment
30 // flat_fb_counts tracks how often each cursor-transform arm falls back to
31 // a legacy decode, keyed by arm name. Dumped via V2_FLAT_FB_STATS to
32 // prioritize the remaining flat-native migration work.
33 flat_fb_counts map[string]int
34 // pending_flat_stmt_ids queues already-emitted flat stmt ids that
35 // cursor-native arms hoist ahead of the statement being transformed —
36 // the flat-side twin of `pending_stmts`. Hoisting arms must first flush
37 // `pending_stmts` into this queue so the combined drain keeps the legacy
38 // chronological order.
39 pending_flat_stmt_ids []ast.FlatNodeId
40 // Current scope for type lookups (walks up scope chain)
41 scope &types.Scope = unsafe { nil }
42 // Function root scope for registering transformer-created temp variables
43 // This allows cleanc to look up temp variable types from the environment
44 fn_root_scope &types.Scope = unsafe { nil }
45 // Current module for scope lookups
46 cur_module string
47 // Temp variable counter for desugaring
48 temp_counter int
49 // Counter for synthesized positions (uses negative values to avoid collision)
50 synth_pos_counter int = -1
51 // Track needed auto-generated str functions (type_name -> elem_type for arrays)
52 needed_str_fns map[string]string
53 // Track needed auto-generated clone functions (fn_name -> struct_name)
54 needed_clone_fns map[string]string
55 // Track needed auto-generated array helper functions
56 needed_array_contains_fns map[string]ArrayMethodInfo
57 needed_array_index_fns map[string]ArrayMethodInfo
58 needed_array_last_index_fns map[string]ArrayMethodInfo
59 // Current function's return type name (for sum type wrapping in returns)
60 cur_fn_ret_type_name string
61 // Current function's concrete sum type return wrapper, when known from
62 // the declared return type.
63 cur_fn_return_sumtype_info ConcreteSumtypeWrapInfo
64 // Tracks whether the current function returns Option/Result.
65 // Some transformations use this to avoid wrapping plain sumtype returns.
66 cur_fn_returns_option bool
67 cur_fn_returns_result bool
68 // When set, match branch values should be wrapped in this sum type
69 // (used when a match expression is returned from a function with sum type return)
70 sumtype_return_wrap string
71 // When true, the last expression in each match branch is transformed as a
72 // value. This is needed while expanding `return match`, before branch-local
73 // returns are inserted.
74 preserve_match_branch_value bool
75 // Smart cast context stack - supports nested smart casts
76 smartcast_stack []SmartcastContext
77 smartcast_expr_counts map[string]int
78 // Functions that should be elided (conditional compilation, e.g. @[if verbose ?])
79 elided_fns map[string]bool
80 // Runtime const initializers grouped by module, preserving module discovery order.
81 runtime_const_inits_by_mod map[string][]RuntimeConstInit
82 runtime_const_modules []string
83 runtime_const_init_fn_name map[string]string
84 runtime_const_known map[string]bool
85 runtime_const_storage_known map[string]bool
86 // Resolved replacement for compile-time pseudo variable @VMODROOT.
87 comptime_vmodroot string
88 // Statements generated by expression-level expansions (e.g. filter/map)
89 // that must be hoisted before the current statement in transform_stmts.
90 pending_stmts []ast.Stmt
91 // When true, skip lowering value-position IfExpr to temp variable.
92 // Set during contexts that already handle IfExpr RHS (e.g. decl_assign).
93 skip_if_value_lowering bool
94 // For native backends: map interface variable names to their concrete type names.
95 // When we see `shape1 := Shape(rect)`, record shape1 → "Rectangle".
96 // Used to rewrite interface method calls to direct concrete calls.
97 interface_concrete_types map[string]string
98 // Track needed auto-generated sort comparator functions
99 needed_sort_fns map[string]SortComparatorInfo
100 needed_enum_str_fns map[string]types.Enum
101 // Track needed go wrapper functions (go call -> goroutine_create lowering)
102 needed_go_wrappers map[string]GoWrapperInfo
103 // Declared storage types for local variables in the current function.
104 // These stay separate from checker/current expression types, which can be
105 // narrowed by smartcasts while the variable is still stored as its declared type.
106 local_decl_types map[string]types.Type
107 local_fn_pointer_return_types map[string]types.Type
108 local_receiver_generic_bindings map[string]map[string]types.Type
109 // Track whether post-pass should inject the synthetic embed_file helper type.
110 needed_embed_file_helper bool
111 // Override array element types for variables whose checker-inferred type is wrong
112 // (e.g. .map(fn_name) typed as []voidptr instead of []ReturnType)
113 array_elem_type_overrides map[string]string
114 // File set for resolving positions to line numbers (for assert messages)
115 file_set &token.FileSet = unsafe { nil }
116 // Current file and function name (for assert messages)
117 cur_file_name string
118 cur_fn_name_str string
119 cur_fn_recv_prefix string // C prefix for current method's receiver type (e.g., "ui__Window")
120 cur_fn_recv_param string // Receiver parameter name (e.g., "w")
121 cur_fn_recv_is_ptr bool // Receiver is passed as a C pointer in the current method
122 cur_fn_generic_params []string
123 generic_var_type_params map[string]string
124 generic_fn_decl_names map[string]bool
125 generic_fn_decl_stmts map[string][]ast.Stmt
126 generic_fn_decl_body_cursors map[string]ast.CursorList
127 generic_fn_decl_index map[string]ast.FnDecl
128 generic_call_candidate_names map[string]bool
129 generic_fn_value_names map[string]bool
130 declared_method_fns map[string]bool
131 monomorphized_fn_bindings map[string]map[string]types.Type
132 cur_monomorphized_fn_bindings map[string]types.Type
133 // @[live] hot code reloading: function names and source file
134 live_fns []LiveFn
135 live_source_file string
136 // Cached scope/method/fn_scope snapshots for lock-free parallel access.
137 // Populated once in pre_pass from the shared Environment fields.
138 cached_scopes map[string]&types.Scope
139 cached_scope_keys []string
140 cached_methods map[string][]&types.Fn
141 cached_method_keys []string
142 cached_fn_scopes map[string]&types.Scope
143 cached_fn_type_index map[string]types.Type
144 cached_fn_return_type_index map[string]types.Type
145 cached_struct_field_types map[string]types.Type
146 // cached_method_base_index maps type_name -> generic-base method name ->
147 // FnType, precomputed once from cached_methods. lookup_method_cached used to
148 // linearly scan every method of a type and recompute its base name (via
149 // generic_base_name_without_specialization) on every call site — O(calls x
150 // methods) with a string scan inner loop, the single biggest transform cost.
151 // This makes the lookup O(1). Flat map keyed by "type_name#base_name" -> the
152 // types.Type sum (callers smartcast to FnType). Flat (not nested) so a lookup
153 // doesn't copy an inner map, and stored as the sum (not the FnType variant)
154 // because v2's own codegen mishandles a map valued by a bare sum-variant.
155 cached_method_base_index map[string]types.Type
156 // cached_method_keys_by_short buckets cached_method_keys by their short
157 // (final `__`-segment) name. The fuzzy method-key fallback loops only ever
158 // match a key whose short name equals the receiver's short name, so they can
159 // scan just this bucket instead of every method key (O(all_keys) per call).
160 cached_method_keys_by_short map[string][]string
161 // cached_imported_module_scopes maps a module name -> the scopes of the
162 // modules it imports (excluding self/C). lookup_imported_var_type used to
163 // rebuild this set on every identifier lookup — objects.keys() + sort() +
164 // a full scan of the module's symbol table for Module objects — which made
165 // it one of the hottest functions during type propagation (O(idents x
166 // objects) with a per-call sort). Precomputed once in cache_env_maps so the
167 // lookup is a single map fetch plus a short scan over only the imports.
168 cached_imported_module_scopes map[string][]&types.Scope
169 // Accumulated synth types for deferred application (thread-safe).
170 // Instead of writing directly to env.set_expr_type during parallel transform,
171 // store here and apply after merge.
172 synth_types map[int]types.Type
173 // Generic monomorphization clones generic FnDecls per env.generic_types
174 // binding before code generation, so backends receive concrete functions.
175 monomorphized_specs map[string]bool
176 // inject_changed_files records whether the last
177 // inject_generic_struct_specializations call actually appended new struct
178 // specializations (i.e. returned a modified file set). The monomorphize
179 // fixpoint uses it to skip the next iteration's full-program collect scan
180 // when nothing changed.
181 inject_changed_files bool
182 // last_mono_clones maps file index -> the FnDecl clone stmts that the most
183 // recent monomorphize_pass appended to that file. The fixpoint rescans only
184 // these freshly-materialized clones (the rest of the program was already
185 // scanned) instead of re-walking all files. Empty when mono_pass added none.
186 last_mono_clones map[int][]ast.Stmt
187 last_struct_clones map[int][]ast.Stmt
188 generic_spec_owner_file map[string]int
189 deferred_generic_call_specs []DeferredGenericCallSpec
190 generic_struct_specs map[string]GenericStructSpec
191 struct_field_generic_decl_types map[string]types.Type
192 struct_field_generic_decl_bindings map[string]map[string]types.Type
193 struct_default_decl_infos map[string]StructDefaultDeclInfo
194 concrete_embedded_owner_names map[string]map[string]string
195 cur_generic_call_file_idx int = -1
196 cur_import_aliases map[string]string
197 sum_type_decl_variant_names map[string][]string
198 // Cached at construction: avoids per-block t.pref nil/enum re-check in
199 // hot loops (transform_stmts, IfGuardExpr handling, OrExpr expansion, etc).
200 is_native_be bool
201 macos_tiny_candidate_graph bool
202}
203
204fn escape_c_keyword(name string) string {
205 n := if name.len > 0 && name[0] == `@` { name[1..] } else { name }
206 if n in c_keywords {
207 return '_${n}'
208 }
209 return n
210}
211
212fn enum_member_ident(enum_type string, field_name string) string {
213 escaped := if field_name.len > 0 && field_name[0] == `@` {
214 'at_${field_name[1..]}'
215 } else {
216 escape_c_keyword(field_name)
217 }
218 return '${enum_type}__${escaped}'
219}
220
221fn (t &Transformer) enum_type_c_name_for_lookup(lookup_name string, typ types.Enum) string {
222 if dunder := lookup_name.index('__') {
223 mod_name := lookup_name[..dunder]
224 last_dunder := lookup_name.last_index('__') or { dunder }
225 if last_dunder >= 0 && last_dunder + 2 < lookup_name.len {
226 type_name := lookup_name[last_dunder + 2..]
227 if scope := t.get_module_scope(mod_name) {
228 if direct_typ := scope.lookup_type(type_name) {
229 if direct_typ is types.Enum {
230 return lookup_name
231 }
232 }
233 }
234 }
235 }
236 if dot := lookup_name.last_index('.') {
237 mod_name := lookup_name[..dot]
238 type_name := lookup_name[dot + 1..]
239 if scope := t.get_module_scope(mod_name) {
240 if direct_typ := scope.lookup_type(type_name) {
241 if direct_typ is types.Enum {
242 return lookup_name.replace('.', '__')
243 }
244 }
245 }
246 }
247 if typ.name != '' {
248 return t.type_to_c_name(types.Type(typ))
249 }
250 return lookup_name.replace('.', '__')
251}
252
253fn (t &Transformer) enum_member_ident_for_lookup(lookup_name string, typ types.Enum, field_name string) string {
254 return enum_member_ident(t.enum_type_c_name_for_lookup(lookup_name, typ), field_name)
255}
256
257fn (t &Transformer) skip_native_backend_transform_file(file ast.File) bool {
258 if t.pref == unsafe { nil } {
259 return false
260 }
261 own := match t.pref.backend {
262 .arm64 { 'arm64' }
263 .x64 { 'x64' }
264 else { return false }
265 }
266
267 if !t.pref.single_backend && own != 'arm64' {
268 return false
269 }
270
271 name := file.name.replace('\\', '/')
272 if !name.contains('/v2/gen/') {
273 return false
274 }
275 for backend_mod in ['cleanc', 'eval', 'v', 'c', 'x64', 'arm64'] {
276 if backend_mod != own && name.contains('/v2/gen/${backend_mod}/') {
277 return true
278 }
279 }
280 return false
281}
282
283struct LiveFn {
284 decl_name string // e.g., 'frame' or 'update_model'
285 mangled_name string // e.g., 'frame' or 'Game__update_model'
286 is_method bool
287 recv_type string // e.g., 'Game' (empty for non-methods)
288}
289
290// SmartcastContext holds info about a single smartcast
291struct SmartcastContext {
292 expr string // The expression being smart-cast (e.g., "w.valera")
293 variant string // The variant type name for union member access (e.g., "int", "Kek", "Array_Attribute")
294 variant_full string // The full variant name for type casts (e.g., "ast__Kek", "Array_ast__Attribute")
295 sumtype string // The sum type name (e.g., "Valera")
296}
297
298struct ArrayMethodInfo {
299 array_type string
300 elem_type string
301 is_fixed bool
302 fixed_len int
303}
304
305// GoWrapperInfo tracks information needed to synthesize a go-wrapper function.
306// For `go foo(a, b)`, we generate a wrapper that packs args and calls
307// goroutines__goroutine_create, and a trampoline that unpacks args and calls foo.
308struct GoWrapperInfo {
309 fn_name string // C-mangled function name (e.g. "main__foo")
310 wrapper_name string // Wrapper function name (e.g. "__go_wrap_main__foo")
311 param_names []string // Parameter names
312 param_types []string // Parameter C type names
313}
314
315fn builder_write_string_stmt(sb_ref ast.Expr, s ast.Expr) ast.Stmt {
316 return ast.Stmt(ast.ExprStmt{
317 expr: ast.Expr(ast.CallExpr{
318 lhs: ast.Expr(ast.Ident{
319 name: 'strings__Builder__write_string'
320 })
321 args: [sb_ref, s]
322 })
323 })
324}
325
326struct RuntimeConstInit {
327 name string
328 expr ast.Expr
329 expr_cursor ast.Cursor
330}
331
332struct SortComparatorInfo {
333 elem_type string // C type name of array element
334 compare_op token.Token // .lt or .gt for the original comparison
335 // For field access: a.field < b.field
336 field_name string // empty for simple element comparison
337 field_path []string
338 // For index access: a[0] < b[0]
339 index_expr string // empty for non-index comparison, e.g. "0"
340 // Whether the comparison value type is string (needs string__lt)
341 is_string_cmp bool
342}
343
344pub fn Transformer.new_with_pref(env &types.Environment, p &pref.Preferences) &Transformer {
345 mut t := new_transformer_base(env, p)
346 if p != unsafe { nil } && p.vroot.len > 0 {
347 t.comptime_vmodroot = p.vroot
348 return t
349 }
350 cwd := os.getwd()
351 if root := detect_vmodroot_from_path(cwd) {
352 t.comptime_vmodroot = root
353 }
354 return t
355}
356
357fn new_transformer_base(env &types.Environment, p &pref.Preferences) &Transformer {
358 mut t := &Transformer{
359 pref: unsafe { p }
360 env: unsafe { env }
361 needed_str_fns: map[string]string{}
362 needed_clone_fns: map[string]string{}
363 needed_array_contains_fns: map[string]ArrayMethodInfo{}
364 needed_array_index_fns: map[string]ArrayMethodInfo{}
365 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
366 needed_sort_fns: map[string]SortComparatorInfo{}
367 needed_enum_str_fns: map[string]types.Enum{}
368 needed_go_wrappers: map[string]GoWrapperInfo{}
369 local_decl_types: map[string]types.Type{}
370 local_fn_pointer_return_types: map[string]types.Type{}
371 local_receiver_generic_bindings: map[string]map[string]types.Type{}
372 generic_var_type_params: map[string]string{}
373 generic_fn_decl_names: map[string]bool{}
374 generic_fn_decl_stmts: map[string][]ast.Stmt{}
375 generic_fn_decl_body_cursors: map[string]ast.CursorList{}
376 generic_fn_decl_index: map[string]ast.FnDecl{}
377 generic_call_candidate_names: map[string]bool{}
378 generic_fn_value_names: map[string]bool{}
379 declared_method_fns: map[string]bool{}
380 monomorphized_fn_bindings: map[string]map[string]types.Type{}
381 cur_monomorphized_fn_bindings: map[string]types.Type{}
382 runtime_const_inits_by_mod: map[string][]RuntimeConstInit{}
383 runtime_const_init_fn_name: map[string]string{}
384 runtime_const_known: map[string]bool{}
385 runtime_const_storage_known: map[string]bool{}
386 interface_concrete_types: map[string]string{}
387 smartcast_expr_counts: map[string]int{}
388 monomorphized_specs: map[string]bool{}
389 generic_spec_owner_file: map[string]int{}
390 deferred_generic_call_specs: []DeferredGenericCallSpec{}
391 generic_struct_specs: map[string]GenericStructSpec{}
392 struct_field_generic_decl_types: map[string]types.Type{}
393 struct_field_generic_decl_bindings: map[string]map[string]types.Type{}
394 struct_default_decl_infos: map[string]StructDefaultDeclInfo{}
395 concrete_embedded_owner_names: map[string]map[string]string{}
396 cur_import_aliases: map[string]string{}
397 sum_type_decl_variant_names: map[string][]string{}
398 is_native_be: p != unsafe { nil }
399 && (p.backend == .arm64 || p.backend == .x64)
400 }
401 return t
402}
403
404pub fn (mut t Transformer) set_file_set(fs &token.FileSet) {
405 t.file_set = unsafe { fs }
406}
407
408pub fn (mut t Transformer) enable_macos_tiny_candidate_graph() {
409 t.macos_tiny_candidate_graph = true
410}
411
412// new_worker_clone creates a lightweight Transformer that shares read-only state
413// (env, pref, elided_fns, comptime_vmodroot, file_set) but has its own
414// accumulator maps for thread-safe per-file transformation.
415// worker_idx offsets synth_pos_counter so workers don't generate conflicting IDs
416// or collide with synth positions already created during whole-program prepare.
417pub fn (t &Transformer) new_worker_clone(worker_idx int) &Transformer {
418 return &Transformer{
419 pref: unsafe { t.pref }
420 env: unsafe { t.env }
421 elided_fns: t.elided_fns.clone()
422 comptime_vmodroot: t.comptime_vmodroot
423 file_set: unsafe { t.file_set }
424 cached_scopes: t.cached_scopes.clone()
425 cached_scope_keys: t.cached_scope_keys
426 cached_methods: t.cached_methods
427 cached_fn_type_index: t.cached_fn_type_index
428 cached_fn_return_type_index: t.cached_fn_return_type_index
429 cached_imported_module_scopes: t.cached_imported_module_scopes
430 cached_struct_field_types: t.cached_struct_field_types
431 cached_method_base_index: t.cached_method_base_index
432 cached_method_keys_by_short: t.cached_method_keys_by_short
433 cached_method_keys: t.cached_method_keys
434 cached_fn_scopes: t.cached_fn_scopes.clone()
435 synth_types: t.synth_types.clone()
436 synth_pos_counter: t.synth_pos_counter - (worker_idx * 100_000)
437 needed_str_fns: map[string]string{}
438 needed_clone_fns: map[string]string{}
439 needed_array_contains_fns: map[string]ArrayMethodInfo{}
440 needed_array_index_fns: map[string]ArrayMethodInfo{}
441 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
442 needed_sort_fns: map[string]SortComparatorInfo{}
443 needed_enum_str_fns: map[string]types.Enum{}
444 needed_go_wrappers: map[string]GoWrapperInfo{}
445 local_decl_types: map[string]types.Type{}
446 local_fn_pointer_return_types: map[string]types.Type{}
447 local_receiver_generic_bindings: map[string]map[string]types.Type{}
448 generic_var_type_params: map[string]string{}
449 generic_fn_decl_body_cursors: t.generic_fn_decl_body_cursors.clone()
450 generic_fn_decl_index: t.generic_fn_decl_index.clone()
451 generic_call_candidate_names: t.generic_call_candidate_names.clone()
452 generic_fn_value_names: t.generic_fn_value_names.clone()
453 declared_method_fns: t.declared_method_fns.clone()
454 monomorphized_fn_bindings: t.monomorphized_fn_bindings.clone()
455 cur_monomorphized_fn_bindings: map[string]types.Type{}
456 runtime_const_inits_by_mod: map[string][]RuntimeConstInit{}
457 runtime_const_init_fn_name: map[string]string{}
458 runtime_const_known: map[string]bool{}
459 runtime_const_storage_known: map[string]bool{}
460 interface_concrete_types: map[string]string{}
461 smartcast_expr_counts: map[string]int{}
462 monomorphized_specs: t.monomorphized_specs.clone()
463 generic_spec_owner_file: t.generic_spec_owner_file.clone()
464 deferred_generic_call_specs: []DeferredGenericCallSpec{}
465 generic_struct_specs: t.generic_struct_specs.clone()
466 struct_field_generic_decl_types: t.struct_field_generic_decl_types.clone()
467 struct_field_generic_decl_bindings: t.struct_field_generic_decl_bindings.clone()
468 struct_default_decl_infos: t.struct_default_decl_infos.clone()
469 concrete_embedded_owner_names: t.concrete_embedded_owner_names.clone()
470 cur_generic_call_file_idx: t.cur_generic_call_file_idx
471 cur_import_aliases: t.cur_import_aliases.clone()
472 sum_type_decl_variant_names: t.sum_type_decl_variant_names.clone()
473 is_native_be: t.is_native_be
474 macos_tiny_candidate_graph: t.macos_tiny_candidate_graph
475 }
476}
477
478// merge_worker merges accumulated state from a worker transformer into this one.
479pub fn (mut t Transformer) merge_worker(w &Transformer) {
480 for k, v in w.needed_str_fns {
481 t.needed_str_fns[k] = v
482 }
483 for k, v in w.needed_clone_fns {
484 t.needed_clone_fns[k] = v
485 }
486 for k, v in w.needed_array_contains_fns {
487 t.needed_array_contains_fns[k] = v
488 }
489 for k, v in w.needed_array_index_fns {
490 t.needed_array_index_fns[k] = v
491 }
492 for k, v in w.needed_array_last_index_fns {
493 t.needed_array_last_index_fns[k] = v
494 }
495 for k, v in w.needed_sort_fns {
496 t.needed_sort_fns[k] = v
497 }
498 for k, v in w.needed_enum_str_fns {
499 t.needed_enum_str_fns[k] = v
500 }
501 for k, v in w.needed_go_wrappers {
502 t.needed_go_wrappers[k] = v
503 }
504 for k, v in w.elided_fns {
505 t.elided_fns[k] = v
506 }
507 if w.needed_embed_file_helper {
508 t.needed_embed_file_helper = true
509 }
510 for k, v in w.interface_concrete_types {
511 t.interface_concrete_types[k] = v
512 }
513 for k, v in w.array_elem_type_overrides {
514 t.array_elem_type_overrides[k] = v
515 }
516 for lf in w.live_fns {
517 t.live_fns << lf
518 }
519 if w.live_source_file.len > 0 {
520 t.live_source_file = w.live_source_file
521 }
522 for k, v in w.cached_fn_scopes {
523 t.cached_fn_scopes[k] = v
524 }
525 for k, v in w.synth_types {
526 t.synth_types[k] = v
527 }
528}
529
530// transform_file_standalone transforms a single file, for use in parallel workers.
531pub fn (mut t Transformer) transform_file_pub(file ast.File) ast.File {
532 return t.transform_file(file)
533}
534
535// transform_file_stmt_pub transforms one top-level statement in a file context,
536// for use by the declaration-level parallel transformer.
537pub fn (mut t Transformer) transform_file_stmt_pub(file ast.File, stmt ast.Stmt) []ast.Stmt {
538 t.enter_file_context(file)
539 return t.transform_stmts([stmt])
540}
541
542fn (mut t Transformer) enter_file_context(file ast.File) {
543 // Set current file name for assert messages
544 t.cur_file_name = file.name
545 // Set current module for scope lookups
546 t.cur_module = file.mod
547 t.cur_import_aliases = import_aliases_for_generic_collect(file.imports)
548 // Set module scope as starting point
549 if scope := t.get_module_scope(file.mod) {
550 t.scope = scope
551 } else {
552 t.scope = unsafe { nil }
553 }
554}
555
556fn detect_vmodroot_from_path(path string) ?string {
557 if path.len == 0 {
558 return none
559 }
560 mut dir := path
561 if !os.is_abs_path(dir) {
562 cwd := os.getwd()
563 if cwd.len > 0 {
564 dir = os.join_path(cwd, dir)
565 }
566 }
567 if !os.is_dir(dir) {
568 dir = os.dir(dir)
569 }
570 for _ in 0 .. 16 {
571 if os.is_file(os.join_path(dir, 'v.mod')) {
572 return dir
573 }
574 parent := os.dir(dir)
575 if parent == dir {
576 break
577 }
578 dir = parent
579 }
580 return none
581}
582
583fn (t &Transformer) is_eval_backend() bool {
584 return t.pref != unsafe { nil } && t.pref.backend == .eval
585}
586
587fn quote_v_string_literal(raw string) string {
588 mut escaped := raw.replace('\\', '\\\\')
589 escaped = escaped.replace("'", "\\'")
590 return "'${escaped}'"
591}
592
593fn quote_v_bytes_literal(raw []u8) string {
594 hex_digits := '0123456789abcdef'
595 mut sb := strings.new_builder(raw.len * 4 + 2)
596 sb.write_u8(`'`)
597 for b in raw {
598 if b >= ` ` && b <= `~` && b !in [`\\`, `'`] {
599 sb.write_u8(b)
600 continue
601 }
602 match b {
603 `\n` {
604 sb.write_string('\\n')
605 }
606 `\r` {
607 sb.write_string('\\r')
608 }
609 `\t` {
610 sb.write_string('\\t')
611 }
612 `\\` {
613 sb.write_string('\\\\')
614 }
615 `'` {
616 sb.write_string("\\'")
617 }
618 else {
619 sb.write_string('\\x')
620 sb.write_u8(hex_digits[b >> 4])
621 sb.write_u8(hex_digits[b & 0x0f])
622 }
623 }
624 }
625 sb.write_u8(`'`)
626 return sb.str()
627}
628
629fn embed_file_helper_type_expr(pos token.Pos) ast.Expr {
630 return ast.Expr(ast.Ident{
631 name: embed_file_helper_type_name
632 pos: pos
633 })
634}
635
636fn is_embed_file_helper_type(typ types.Type) bool {
637 return typ.name() == embed_file_helper_type_name
638}
639
640fn (t &Transformer) vmodroot_string_literal(pos token.Pos) ast.StringLiteral {
641 return ast.StringLiteral{
642 kind: .v
643 value: quote_v_string_literal(t.comptime_vmodroot)
644 pos: pos
645 }
646}
647
648fn embed_file_string_arg(expr ast.Expr) ?string {
649 match expr {
650 ast.StringLiteral {
651 if expr.value.len >= 2 {
652 return expr.value[1..expr.value.len - 1]
653 }
654 return ''
655 }
656 ast.BasicLiteral {
657 if expr.kind == .string {
658 if expr.value.len >= 2 {
659 return expr.value[1..expr.value.len - 1]
660 }
661 return ''
662 }
663 return none
664 }
665 else {
666 return none
667 }
668 }
669}
670
671fn (mut t Transformer) current_file_path(pos token.Pos) string {
672 if !pos.is_valid() || t.file_set == unsafe { nil } {
673 return t.cur_file_name
674 }
675 file := t.file_set.file(pos)
676 position := file.position(pos)
677 return position.filename
678}
679
680fn (mut t Transformer) resolve_embed_file_paths(raw_path string, pos token.Pos) (string, string) {
681 mut rel_path := raw_path
682 if rel_path.contains('@VMODROOT') {
683 rel_path = rel_path.replace('@VMODROOT', t.comptime_vmodroot)
684 }
685 if rel_path.contains('@VEXEROOT') {
686 mut vexeroot := ''
687 if os.args.len > 0 && os.args[0].len > 0 {
688 vexeroot = os.dir(os.real_path(os.args[0]))
689 }
690 if vexeroot == '' && t.pref != unsafe { nil } && t.pref.vroot.len > 0 {
691 vexeroot = os.join_path(t.pref.vroot, 'cmd', 'v2')
692 }
693 if vexeroot != '' {
694 rel_path = rel_path.replace('@VEXEROOT', vexeroot)
695 }
696 }
697 if os.is_abs_path(rel_path) {
698 return raw_path, os.real_path(rel_path)
699 }
700 source_path := t.current_file_path(pos)
701 base_dir := if source_path != '' { os.dir(source_path) } else { os.getwd() }
702 return raw_path, os.real_path(os.join_path_single(base_dir, rel_path))
703}
704
705// embed_file_init_parts is the side-effecting prologue of
706// `transform_embed_file_comptime_expr` — resolves the raw path argument,
707// reads the file bytes from disk, and sets `needed_embed_file_helper`.
708// Returns `(rpath, apath, file_bytes)` on success, none on zero-arg /
709// non-string-literal arg (the legacy returns the unchanged ComptimeExpr in
710// that case). Extracted so the legacy helper and the flat-write direct-emit
711// port (`transform_embed_file_comptime_expr_to_flat`) share the same I/O and
712// state-mutation prologue — same template as sessions 4, 59, 60.
713fn (mut t Transformer) embed_file_init_parts(expr ast.ComptimeExpr, args []ast.Expr) ?(string, string, []u8) {
714 if args.len == 0 {
715 return none
716 }
717 raw_path := embed_file_string_arg(args[0]) or { return none }
718 return t.embed_file_init_parts_from_raw(raw_path, expr.pos)
719}
720
721fn (mut t Transformer) embed_file_init_parts_from_raw(raw_path string, pos token.Pos) (string, string, []u8) {
722 rpath, apath := t.resolve_embed_file_paths(raw_path, pos)
723 file_bytes := os.read_bytes(apath) or { panic('embed_file: failed to read `${apath}`: ${err}') }
724 t.needed_embed_file_helper = true
725 return rpath, apath, file_bytes
726}
727
728fn (mut t Transformer) transform_embed_file_comptime_expr(expr ast.ComptimeExpr, args []ast.Expr) ast.Expr {
729 rpath, apath, file_bytes := t.embed_file_init_parts(expr, args) or { return expr }
730 return ast.InitExpr{
731 typ: embed_file_helper_type_expr(expr.pos)
732 fields: [
733 ast.FieldInit{
734 name: '_data'
735 value: ast.Expr(ast.StringLiteral{
736 kind: .v
737 value: quote_v_bytes_literal(file_bytes)
738 pos: expr.pos
739 })
740 },
741 ast.FieldInit{
742 name: 'len'
743 value: ast.Expr(ast.BasicLiteral{
744 kind: .number
745 value: file_bytes.len.str()
746 pos: expr.pos
747 })
748 },
749 ast.FieldInit{
750 name: 'path'
751 value: ast.Expr(ast.StringLiteral{
752 kind: .v
753 value: quote_v_string_literal(rpath)
754 pos: expr.pos
755 })
756 },
757 ast.FieldInit{
758 name: 'apath'
759 value: ast.Expr(ast.StringLiteral{
760 kind: .v
761 value: quote_v_string_literal(apath)
762 pos: expr.pos
763 })
764 },
765 ]
766 pos: expr.pos
767 }
768}
769
770// push_smartcast adds a new smartcast context to the stack
771fn (mut t Transformer) push_smartcast(expr string, variant string, sumtype string) {
772 if expr == '' {
773 return
774 }
775 qualified_sumtype := t.qualify_type_name(sumtype)
776 t.push_smartcast_ctx(SmartcastContext{
777 expr: expr
778 variant: variant
779 variant_full: variant // Default to same as variant
780 sumtype: qualified_sumtype
781 })
782}
783
784// push_smartcast_full adds a smartcast context with separate short and full variant names
785fn (mut t Transformer) push_smartcast_full(expr string, variant string, variant_full string, sumtype string) {
786 if expr == '' {
787 return
788 }
789 // Qualify the sumtype name so apply_smartcast_* can determine the module
790 // the sumtype lives in. e.g. 'Stmt' → 'ast__Stmt' when in builder module.
791 qualified_sumtype := t.qualify_type_name(sumtype)
792 t.push_smartcast_ctx(SmartcastContext{
793 expr: expr
794 variant: variant
795 variant_full: variant_full
796 sumtype: qualified_sumtype
797 })
798}
799
800fn (mut t Transformer) push_smartcast_ctx(ctx SmartcastContext) {
801 if ctx.expr == '' {
802 return
803 }
804 t.smartcast_stack << ctx
805 t.smartcast_expr_counts[ctx.expr] = t.smartcast_expr_counts[ctx.expr] + 1
806}
807
808fn (mut t Transformer) dec_smartcast_expr_count(expr string) {
809 if expr == '' {
810 return
811 }
812 count := t.smartcast_expr_counts[expr]
813 if count <= 1 {
814 t.smartcast_expr_counts.delete(expr)
815 } else {
816 t.smartcast_expr_counts[expr] = count - 1
817 }
818}
819
820// pop_smartcast removes the most recent smartcast context from the stack
821fn (mut t Transformer) pop_smartcast() {
822 if t.smartcast_stack.len > 0 {
823 ctx := t.smartcast_stack[t.smartcast_stack.len - 1]
824 t.smartcast_stack = t.smartcast_stack[..t.smartcast_stack.len - 1]
825 t.dec_smartcast_expr_count(ctx.expr)
826 }
827}
828
829fn (mut t Transformer) truncate_smartcasts(depth int) {
830 for t.smartcast_stack.len > depth {
831 t.pop_smartcast()
832 }
833}
834
835// SmartcastRemoveResult holds the removed context and its original index
836struct SmartcastRemoveResult {
837 ctx SmartcastContext
838 idx int
839}
840
841// remove_smartcast_for_expr removes the smartcast context for a specific expression
842// Returns the removed context or none if not found
843fn (mut t Transformer) remove_smartcast_for_expr(expr_str string) ?SmartcastContext {
844 if result := t.remove_smartcast_for_expr_with_idx(expr_str) {
845 return result.ctx
846 }
847 return none
848}
849
850// remove_smartcast_for_expr_with_idx removes the smartcast context and returns both context and original index
851fn (mut t Transformer) remove_smartcast_for_expr_with_idx(expr_str string) ?SmartcastRemoveResult {
852 if expr_str == '' {
853 return none
854 }
855 if t.smartcast_expr_counts[expr_str] == 0 {
856 return none
857 }
858 mut i := t.smartcast_stack.len
859 mut searched := 0
860 for i > 0 && searched < smartcast_search_limit {
861 i--
862 searched++
863 if t.smartcast_stack[i].expr == expr_str {
864 ctx := t.smartcast_stack[i]
865 // Remove this specific context by creating new slice without this element
866 mut new_stack := []SmartcastContext{cap: t.smartcast_stack.len - 1}
867 for j, c in t.smartcast_stack {
868 if j != i {
869 new_stack << c
870 }
871 }
872 t.smartcast_stack = new_stack
873 t.dec_smartcast_expr_count(ctx.expr)
874 return SmartcastRemoveResult{
875 ctx: ctx
876 idx: i
877 }
878 }
879 }
880 return none
881}
882
883// remove_smartcast_ctx_with_idx removes a specific smartcast context and returns
884// the removed context with its original index.
885fn (mut t Transformer) remove_smartcast_ctx_with_idx(ctx SmartcastContext) ?SmartcastRemoveResult {
886 mut i := t.smartcast_stack.len
887 mut searched := 0
888 for i > 0 && searched < smartcast_search_limit {
889 i--
890 searched++
891 cur := t.smartcast_stack[i]
892 if cur.expr == ctx.expr && cur.variant == ctx.variant
893 && cur.variant_full == ctx.variant_full {
894 removed := cur
895 mut new_stack := []SmartcastContext{cap: t.smartcast_stack.len - 1}
896 for j, c in t.smartcast_stack {
897 if j != i {
898 new_stack << c
899 }
900 }
901 t.smartcast_stack = new_stack
902 t.dec_smartcast_expr_count(removed.expr)
903 return SmartcastRemoveResult{
904 ctx: removed
905 idx: i
906 }
907 }
908 }
909 return none
910}
911
912// remove_matching_smartcasts temporarily removes all exact copies of ctx.
913fn (mut t Transformer) remove_matching_smartcasts(ctx SmartcastContext) []SmartcastRemoveResult {
914 mut removed := []SmartcastRemoveResult{}
915 if ctx.expr == '' || t.smartcast_stack.len == 0 {
916 return removed
917 }
918 start := if t.smartcast_stack.len > smartcast_search_limit {
919 t.smartcast_stack.len - smartcast_search_limit
920 } else {
921 0
922 }
923 mut new_stack := []SmartcastContext{cap: t.smartcast_stack.len}
924 for i, cur in t.smartcast_stack {
925 if i >= start && cur.expr == ctx.expr && cur.variant == ctx.variant
926 && cur.variant_full == ctx.variant_full {
927 removed << SmartcastRemoveResult{
928 ctx: cur
929 idx: i
930 }
931 t.dec_smartcast_expr_count(cur.expr)
932 continue
933 }
934 new_stack << cur
935 }
936 t.smartcast_stack = new_stack
937 return removed
938}
939
940// restore_smartcasts restores previously removed contexts in original order.
941fn (mut t Transformer) restore_smartcasts(removed []SmartcastRemoveResult) {
942 mut i := removed.len
943 for i > 0 {
944 i--
945 entry := removed[i]
946 t.insert_smartcast_at(entry.idx, entry.ctx)
947 }
948}
949
950// insert_smartcast_at inserts a smartcast context at a specific position
951fn (mut t Transformer) insert_smartcast_at(idx int, ctx SmartcastContext) {
952 if ctx.expr == '' {
953 return
954 }
955 if idx >= t.smartcast_stack.len {
956 // Append at end
957 t.smartcast_stack << ctx
958 t.smartcast_expr_counts[ctx.expr] = t.smartcast_expr_counts[ctx.expr] + 1
959 } else {
960 // Insert at position
961 mut new_stack := []SmartcastContext{cap: t.smartcast_stack.len + 1}
962 for i, c in t.smartcast_stack {
963 if i == idx {
964 new_stack << ctx
965 }
966 new_stack << c
967 }
968 // If idx was 0 and loop didn't add, add at beginning
969 if idx == 0 && new_stack.len == t.smartcast_stack.len {
970 new_stack = [ctx]
971 new_stack << t.smartcast_stack
972 }
973 t.smartcast_stack = new_stack
974 t.smartcast_expr_counts[ctx.expr] = t.smartcast_expr_counts[ctx.expr] + 1
975 }
976}
977
978// find_smartcast_for_expr finds the smartcast context that matches the given expression string
979// Returns the context or none if not found
980fn (t &Transformer) find_smartcast_for_expr(expr_str string) ?SmartcastContext {
981 // Empty expression strings are ambiguous (from unhandled AST nodes like IndexExpr)
982 // and must never match, as they would incorrectly apply smartcasts to unrelated
983 // expressions (e.g., EmptyExpr in enum shorthands like `.assign`).
984 if expr_str == '' {
985 return none
986 }
987 if t.smartcast_expr_counts[expr_str] == 0 {
988 return none
989 }
990 // Search from most recent to oldest (reverse order)
991 mut i := t.smartcast_stack.len
992 mut searched := 0
993 for i > 0 && searched < smartcast_search_limit {
994 i--
995 searched++
996 if t.smartcast_stack[i].expr == expr_str {
997 return t.smartcast_stack[i]
998 }
999 }
1000 return none
1001}
1002
1003fn (t &Transformer) smartcast_context_tag_value(ctx SmartcastContext) ?int {
1004 if ctx.sumtype.starts_with('__iface__') {
1005 return none
1006 }
1007 variants := t.get_sum_type_variants(ctx.sumtype)
1008 for candidate in [ctx.variant, ctx.variant_full] {
1009 if candidate == '' {
1010 continue
1011 }
1012 if matched := t.match_variant(candidate, variants) {
1013 for i, variant in variants {
1014 if variant == matched {
1015 return i
1016 }
1017 }
1018 }
1019 }
1020 return none
1021}
1022
1023fn (t &Transformer) find_sumtype_with_variant_at_tag(variant_name string, tag int) string {
1024 if variant_name == '' || tag < 0 {
1025 return ''
1026 }
1027 for st in ['Expr', 'Type', 'Stmt', 'ast__Expr', 'ast__Type', 'ast__Stmt'] {
1028 variants := t.get_sum_type_variants(st)
1029 if tag < variants.len
1030 && sum_type_variant_matches_for_sumtype(st, variants[tag], variant_name) {
1031 return st
1032 }
1033 }
1034 for mod_name, scope in t.cached_scopes {
1035 for type_name, typ in scope.types {
1036 if typ is types.SumType {
1037 st_name := if mod_name != '' && mod_name != 'main' && mod_name != 'builtin' {
1038 '${mod_name}__${type_name}'
1039 } else {
1040 type_name
1041 }
1042 variants := typ.get_variants()
1043 if tag < variants.len
1044 && sum_type_variant_matches_for_sumtype(st_name, variants[tag].name(), variant_name) {
1045 return st_name
1046 }
1047 }
1048 }
1049 }
1050 return ''
1051}
1052
1053fn (t &Transformer) smartcast_context_from_tag_check(expr ast.InfixExpr) ?SmartcastContext {
1054 if expr.op != .eq || expr.lhs !is ast.SelectorExpr {
1055 return none
1056 }
1057 tag_sel := expr.lhs as ast.SelectorExpr
1058 if tag_sel.rhs.name != '_tag' {
1059 return none
1060 }
1061 if expr.rhs !is ast.BasicLiteral {
1062 return none
1063 }
1064 tag_lit := expr.rhs as ast.BasicLiteral
1065 if tag_lit.kind != .number {
1066 return none
1067 }
1068 tag := tag_lit.value.int()
1069 if tag < 0 {
1070 return none
1071 }
1072 sumtype_expr := tag_sel.lhs
1073 mut sumtype_name := t.get_sumtype_name_for_expr(sumtype_expr)
1074 if sumtype_name == '' {
1075 return none
1076 }
1077 mut variants := t.get_sum_type_variants(sumtype_name)
1078 if tag >= variants.len {
1079 storage_sumtype := t.find_sumtype_with_variant_at_tag(sumtype_name, tag)
1080 if storage_sumtype == '' {
1081 return none
1082 }
1083 sumtype_name = storage_sumtype
1084 variants = t.get_sum_type_variants(sumtype_name)
1085 }
1086 variant_name := variants[tag]
1087 variant_short := if variant_name.contains('__') {
1088 variant_name.all_after_last('__')
1089 } else {
1090 variant_name
1091 }
1092 sumtype_module := if sumtype_name.contains('__') {
1093 sumtype_name.all_before_last('__')
1094 } else {
1095 ''
1096 }
1097 variant_full := if variant_name.contains('__') || sumtype_module == ''
1098 || variant_short.starts_with('Array_') || variant_short.starts_with('Map_') {
1099 variant_name
1100 } else {
1101 '${sumtype_module}__${variant_short}'
1102 }
1103 expr_str := t.expr_to_string(sumtype_expr)
1104 if expr_str == '' {
1105 return none
1106 }
1107 return SmartcastContext{
1108 expr: expr_str
1109 variant: variant_full
1110 variant_full: variant_full
1111 sumtype: sumtype_name
1112 }
1113}
1114
1115fn (t &Transformer) smartcast_context_from_condition_term(term ast.InfixExpr) ?SmartcastContext {
1116 if ctx := t.smartcast_context_from_is_check(term) {
1117 return ctx
1118 }
1119 return t.smartcast_context_from_tag_check(term)
1120}
1121
1122fn (mut t Transformer) transform_tag_check_lhs(lhs ast.Expr) ast.Expr {
1123 if lhs is ast.Ident {
1124 if t.smartcast_stack.len > 0 {
1125 ctx := t.smartcast_stack[t.smartcast_stack.len - 1]
1126 if ctx.expr == lhs.name && t.is_sum_type(ctx.variant) {
1127 return t.transform_expr(lhs)
1128 }
1129 }
1130 return ast.Expr(lhs)
1131 }
1132 return t.transform_expr(lhs)
1133}
1134
1135fn smartcast_type_name_matches(candidate string, target string) bool {
1136 mut c := candidate.trim_space()
1137 mut t := target.trim_space()
1138 if c.starts_with('&') {
1139 c = c[1..]
1140 }
1141 if t.starts_with('&') {
1142 t = t[1..]
1143 }
1144 c = c.trim_right('*')
1145 t = t.trim_right('*')
1146 if c == '' || t == '' {
1147 return false
1148 }
1149 if c == t {
1150 return true
1151 }
1152 c_short := if c.contains('__') { c.all_after_last('__') } else { c }
1153 t_short := if t.contains('__') { t.all_after_last('__') } else { t }
1154 return c_short == t_short
1155}
1156
1157fn (mut t Transformer) transform_tag_check_lhs_for_sumtype(lhs ast.Expr, sumtype_name string) ast.Expr {
1158 lhs_expr_str := t.expr_to_string(lhs)
1159 if lhs_expr_str != '' {
1160 if ctx := t.find_smartcast_for_expr(lhs_expr_str) {
1161 if !ctx.sumtype.starts_with('__iface__')
1162 && (smartcast_type_name_matches(ctx.variant, sumtype_name)
1163 || smartcast_type_name_matches(ctx.variant_full, sumtype_name)) {
1164 return t.apply_smartcast_direct_ctx(lhs, ctx)
1165 }
1166 }
1167 }
1168 return t.transform_tag_check_lhs(lhs)
1169}
1170
1171// has_active_smartcast returns true if there's any active smartcast context
1172fn (t &Transformer) has_active_smartcast() bool {
1173 return t.smartcast_stack.len > 0
1174}
1175
1176// cur_smartcast_expr returns the current (most recent) smartcast expression or empty string
1177fn (t &Transformer) cur_smartcast_expr() string {
1178 if t.smartcast_stack.len > 0 {
1179 return t.smartcast_stack[t.smartcast_stack.len - 1].expr
1180 }
1181 return ''
1182}
1183
1184// cur_smartcast_variant returns the current (most recent) smartcast variant or empty string
1185fn (t &Transformer) cur_smartcast_variant() string {
1186 if t.smartcast_stack.len > 0 {
1187 return t.smartcast_stack[t.smartcast_stack.len - 1].variant
1188 }
1189 return ''
1190}
1191
1192pub fn (mut t Transformer) set_synth_pos_counter(val int) {
1193 t.synth_pos_counter = val
1194}
1195
1196// next_synth_pos returns a unique negative position for synthesized AST nodes
1197fn (mut t Transformer) next_synth_pos() token.Pos {
1198 id := t.synth_pos_counter
1199 t.synth_pos_counter -= 1
1200 return token.Pos{
1201 id: id
1202 offset: 0
1203 }
1204}
1205
1206fn (mut t Transformer) make_number_expr(value string) ast.Expr {
1207 pos := t.next_synth_pos()
1208 return ast.Expr(ast.BasicLiteral{
1209 kind: .number
1210 value: value
1211 pos: pos
1212 })
1213}
1214
1215fn (mut t Transformer) make_infix_expr_at(op token.Token, lhs ast.Expr, rhs ast.Expr, pos token.Pos) ast.Expr {
1216 infix_pos := if pos.id != 0 || pos.offset != 0 {
1217 pos
1218 } else {
1219 t.next_synth_pos()
1220 }
1221 return ast.Expr(ast.InfixExpr{
1222 op: op
1223 lhs: lhs
1224 rhs: rhs
1225 pos: infix_pos
1226 })
1227}
1228
1229fn (mut t Transformer) make_infix_expr(op token.Token, lhs ast.Expr, rhs ast.Expr) ast.Expr {
1230 return t.make_infix_expr_at(op, lhs, rhs, token.Pos{})
1231}
1232
1233// synth_selector creates a typed SelectorExpr with a unique synthesized position
1234// and registers its type in the environment so downstream passes can resolve it.
1235fn (mut t Transformer) open_scope() {
1236 t.scope = types.new_scope(t.scope)
1237}
1238
1239// close_scope returns to the parent scope
1240fn (mut t Transformer) close_scope() {
1241 if t.scope != unsafe { nil } {
1242 t.scope = t.scope.parent
1243 }
1244}
1245
1246// is_var_enum checks if a variable's type is an enum
1247fn (t &Transformer) is_var_enum(name string) ?string {
1248 typ := t.lookup_var_type(name) or { return none }
1249 if typ is types.Enum {
1250 return typ.name
1251 }
1252 return none
1253}
1254
1255fn generic_ref_short_name(expr ast.Expr) string {
1256 if !expr_has_valid_data(expr) {
1257 return ''
1258 }
1259 return match expr {
1260 ast.Ident {
1261 expr.name
1262 }
1263 ast.SelectorExpr {
1264 expr.rhs.name
1265 }
1266 ast.GenericArgs {
1267 generic_ref_short_name(expr.lhs)
1268 }
1269 ast.GenericArgOrIndexExpr {
1270 generic_ref_short_name(expr.lhs)
1271 }
1272 ast.IndexExpr {
1273 generic_ref_short_name(expr.lhs)
1274 }
1275 ast.ParenExpr {
1276 generic_ref_short_name(expr.expr)
1277 }
1278 else {
1279 ''
1280 }
1281 }
1282}
1283
1284fn generic_ref_short_name_cursor(expr ast.Cursor) string {
1285 if !expr.is_valid() {
1286 return ''
1287 }
1288 return match expr.kind() {
1289 .expr_ident {
1290 expr.name()
1291 }
1292 .expr_selector {
1293 rhs := expr.edge(1)
1294 if rhs.kind() == .expr_ident {
1295 rhs.name()
1296 } else {
1297 ''
1298 }
1299 }
1300 .expr_generic_args, .expr_generic_arg_or_index, .expr_index, .expr_paren {
1301 generic_ref_short_name_cursor(expr.edge(0))
1302 }
1303 else {
1304 ''
1305 }
1306 }
1307}
1308
1309fn (mut t Transformer) collect_generic_fn_value_refs(files []ast.File) {
1310 t.collect_generic_fn_decl_names(files)
1311 for file in files {
1312 for stmt in file.stmts {
1313 t.collect_generic_fn_value_refs_in_stmt(stmt, false)
1314 }
1315 }
1316 t.collect_generic_fn_value_dependencies()
1317}
1318
1319fn (mut t Transformer) collect_generic_fn_value_refs_from_flat(flat &ast.FlatAst) {
1320 t.collect_generic_fn_decl_names_from_flat(flat)
1321 for i in 0 .. flat.files.len {
1322 stmts := flat.file_cursor(i).stmts()
1323 for j in 0 .. stmts.len() {
1324 t.collect_generic_fn_value_refs_in_stmt_cursor(stmts.at(j), false)
1325 }
1326 }
1327 t.collect_generic_fn_value_dependencies()
1328}
1329
1330fn (mut t Transformer) collect_generic_fn_decl_names(files []ast.File) {
1331 for file in files {
1332 for stmt in file.stmts {
1333 t.collect_generic_fn_decl_names_in_stmt(stmt)
1334 }
1335 }
1336}
1337
1338fn (mut t Transformer) collect_generic_fn_decl_names_from_flat(flat &ast.FlatAst) {
1339 for i in 0 .. flat.files.len {
1340 stmts := flat.file_cursor(i).stmts()
1341 for j in 0 .. stmts.len() {
1342 t.collect_generic_fn_decl_names_in_stmt_cursor(stmts.at(j))
1343 }
1344 }
1345}
1346
1347fn has_non_lifetime_generic_params_cursor(params ast.CursorList) bool {
1348 for i in 0 .. params.len() {
1349 if params.at(i).kind() != .expr_lifetime {
1350 return true
1351 }
1352 }
1353 return false
1354}
1355
1356fn (mut t Transformer) collect_generic_fn_decl_names_in_stmt_cursor(stmt ast.Cursor) {
1357 if !stmt.is_valid() {
1358 return
1359 }
1360 match stmt.kind() {
1361 .stmt_comptime {
1362 t.collect_generic_fn_decl_names_in_stmt_cursor(stmt.edge(0))
1363 }
1364 .stmt_expr {
1365 t.collect_generic_fn_decl_names_in_expr_cursor(stmt.edge(0))
1366 }
1367 .stmt_fn_decl {
1368 generic_params := stmt.edge(1).list_at(0)
1369 body := stmt.list_at(3)
1370 if has_non_lifetime_generic_params_cursor(generic_params) {
1371 t.generic_fn_decl_names[stmt.name()] = true
1372 t.generic_fn_decl_body_cursors[stmt.name()] = body
1373 }
1374 for i in 0 .. body.len() {
1375 t.collect_generic_fn_decl_names_in_stmt_cursor(body.at(i))
1376 }
1377 }
1378 else {}
1379 }
1380}
1381
1382fn (mut t Transformer) collect_generic_fn_decl_names_in_expr_cursor(expr ast.Cursor) {
1383 if !expr.is_valid() {
1384 return
1385 }
1386 match expr.kind() {
1387 .expr_comptime {
1388 t.collect_generic_fn_decl_names_in_expr_cursor(expr.edge(0))
1389 }
1390 .expr_if {
1391 if expr.edge_count() > 2 {
1392 for i in 2 .. expr.edge_count() {
1393 t.collect_generic_fn_decl_names_in_stmt_cursor(expr.edge(i))
1394 }
1395 }
1396 t.collect_generic_fn_decl_names_in_expr_cursor(expr.edge(1))
1397 }
1398 else {}
1399 }
1400}
1401
1402fn (mut t Transformer) collect_generic_fn_decl_names_in_stmt(stmt ast.Stmt) {
1403 if !stmt_has_valid_data(stmt) {
1404 return
1405 }
1406 match stmt {
1407 ast.ComptimeStmt {
1408 t.collect_generic_fn_decl_names_in_stmt(stmt.stmt)
1409 }
1410 ast.ExprStmt {
1411 t.collect_generic_fn_decl_names_in_expr(stmt.expr)
1412 }
1413 ast.FnDecl {
1414 if has_non_lifetime_generic_params(stmt.typ.generic_params) {
1415 t.generic_fn_decl_names[stmt.name] = true
1416 t.generic_fn_decl_stmts[stmt.name] = stmt.stmts
1417 }
1418 for body_stmt in stmt.stmts {
1419 t.collect_generic_fn_decl_names_in_stmt(body_stmt)
1420 }
1421 }
1422 else {}
1423 }
1424}
1425
1426fn (mut t Transformer) collect_generic_fn_decl_names_in_expr(expr ast.Expr) {
1427 if !expr_has_valid_data(expr) {
1428 return
1429 }
1430 match expr {
1431 ast.ComptimeExpr {
1432 t.collect_generic_fn_decl_names_in_expr(expr.expr)
1433 }
1434 ast.IfExpr {
1435 for stmt in expr.stmts {
1436 t.collect_generic_fn_decl_names_in_stmt(stmt)
1437 }
1438 t.collect_generic_fn_decl_names_in_expr(expr.else_expr)
1439 }
1440 else {}
1441 }
1442}
1443
1444fn (mut t Transformer) collect_generic_fn_value_dependencies() {
1445 mut processed := map[string]bool{}
1446 for {
1447 mut progressed := false
1448 for name, _ in t.generic_fn_value_names {
1449 if name in processed {
1450 continue
1451 }
1452 processed[name] = true
1453 progressed = true
1454 if body := t.generic_fn_decl_body_cursors[name] {
1455 t.collect_generic_fn_value_refs_in_stmt_list_cursor(body, true)
1456 } else if stmts := t.generic_fn_decl_stmts[name] {
1457 t.collect_generic_fn_value_refs_in_stmts(stmts, true)
1458 }
1459 }
1460 if !progressed {
1461 break
1462 }
1463 }
1464}
1465
1466fn (mut t Transformer) collect_generic_fn_value_refs_in_stmts(stmts []ast.Stmt, include_generic_calls bool) {
1467 for stmt in stmts {
1468 t.collect_generic_fn_value_refs_in_stmt(stmt, include_generic_calls)
1469 }
1470}
1471
1472fn (mut t Transformer) collect_generic_fn_value_refs_in_expr_cursor(expr ast.Cursor, include_generic_calls bool, in_call_lhs bool) {
1473 if !expr.is_valid() {
1474 return
1475 }
1476 match expr.kind() {
1477 .expr_array_init {
1478 for i in 5 .. expr.edge_count() {
1479 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i), include_generic_calls,
1480 false)
1481 }
1482 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1483 false)
1484 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(2), include_generic_calls,
1485 false)
1486 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(3), include_generic_calls,
1487 false)
1488 }
1489 .expr_as_cast {
1490 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1491 false)
1492 }
1493 .expr_assoc {
1494 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1495 false)
1496 for i in 2 .. expr.edge_count() {
1497 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i).edge(0),
1498 include_generic_calls, false)
1499 }
1500 }
1501 .expr_call {
1502 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1503 true)
1504 for i in 1 .. expr.edge_count() {
1505 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i), include_generic_calls,
1506 false)
1507 }
1508 }
1509 .expr_call_or_cast {
1510 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1511 false)
1512 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1513 false)
1514 }
1515 .expr_cast {
1516 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1517 false)
1518 }
1519 .expr_comptime, .expr_modifier, .expr_postfix, .expr_prefix, .expr_sql {
1520 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1521 false)
1522 }
1523 .expr_fn_literal {
1524 captured_len := expr.extra_int()
1525 for i in 0 .. captured_len {
1526 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1 + i),
1527 include_generic_calls, false)
1528 }
1529 for i in (1 + captured_len) .. expr.edge_count() {
1530 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(i), include_generic_calls)
1531 }
1532 }
1533 .expr_generic_args {
1534 base_name := generic_ref_short_name_cursor(expr.edge(0))
1535 if (include_generic_calls || !in_call_lhs) && base_name in t.generic_fn_decl_names {
1536 t.generic_fn_value_names[base_name] = true
1537 }
1538 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1539 in_call_lhs)
1540 for i in 1 .. expr.edge_count() {
1541 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i), include_generic_calls,
1542 false)
1543 }
1544 }
1545 .expr_generic_arg_or_index {
1546 base_name := generic_ref_short_name_cursor(expr.edge(0))
1547 if (include_generic_calls || !in_call_lhs) && base_name in t.generic_fn_decl_names {
1548 t.generic_fn_value_names[base_name] = true
1549 }
1550 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1551 in_call_lhs)
1552 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1553 false)
1554 }
1555 .expr_if {
1556 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1557 false)
1558 for i in 2 .. expr.edge_count() {
1559 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(i), include_generic_calls)
1560 }
1561 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1562 false)
1563 }
1564 .expr_if_guard {
1565 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(0), include_generic_calls)
1566 }
1567 .expr_index {
1568 base_name := generic_ref_short_name_cursor(expr.edge(0))
1569 if (include_generic_calls || !in_call_lhs) && base_name in t.generic_fn_decl_names {
1570 t.generic_fn_value_names[base_name] = true
1571 }
1572 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1573 in_call_lhs)
1574 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1575 false)
1576 }
1577 .expr_infix, .expr_range {
1578 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1579 false)
1580 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1581 false)
1582 }
1583 .expr_init {
1584 for i in 1 .. expr.edge_count() {
1585 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i).edge(0),
1586 include_generic_calls, false)
1587 }
1588 }
1589 .expr_keyword_operator, .expr_tuple {
1590 for i in 0 .. expr.edge_count() {
1591 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i), include_generic_calls,
1592 false)
1593 }
1594 }
1595 .expr_lambda {
1596 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1597 false)
1598 }
1599 .expr_lock {
1600 packed := u32(expr.extra_int())
1601 lock_len := int(packed & 0xFFFF)
1602 rlock_len := int((packed >> 16) & 0xFFFF)
1603 for i in 0 .. lock_len {
1604 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i), include_generic_calls,
1605 false)
1606 }
1607 for i in lock_len .. (lock_len + rlock_len) {
1608 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i), include_generic_calls,
1609 false)
1610 }
1611 for i in (lock_len + rlock_len) .. expr.edge_count() {
1612 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(i), include_generic_calls)
1613 }
1614 }
1615 .expr_map_init {
1616 keys_len := expr.extra_int()
1617 for i in 0 .. keys_len {
1618 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1 + i),
1619 include_generic_calls, false)
1620 }
1621 for i in (1 + keys_len) .. expr.edge_count() {
1622 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(i), include_generic_calls,
1623 false)
1624 }
1625 }
1626 .expr_match {
1627 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1628 false)
1629 for i in 1 .. expr.edge_count() {
1630 branch := expr.edge(i)
1631 conds := branch.list_at(0)
1632 for j in 0 .. conds.len() {
1633 t.collect_generic_fn_value_refs_in_expr_cursor(conds.at(j),
1634 include_generic_calls, false)
1635 }
1636 stmts := branch.list_at(1)
1637 for j in 0 .. stmts.len() {
1638 t.collect_generic_fn_value_refs_in_stmt_cursor(stmts.at(j),
1639 include_generic_calls)
1640 }
1641 }
1642 }
1643 .expr_or {
1644 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1645 false)
1646 for i in 1 .. expr.edge_count() {
1647 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(i), include_generic_calls)
1648 }
1649 }
1650 .expr_paren {
1651 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1652 in_call_lhs)
1653 }
1654 .expr_select {
1655 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(0), include_generic_calls)
1656 for i in 2 .. expr.edge_count() {
1657 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(i), include_generic_calls)
1658 }
1659 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(1), include_generic_calls,
1660 false)
1661 }
1662 .expr_selector {
1663 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1664 in_call_lhs)
1665 }
1666 .expr_string_inter {
1667 inters := expr.list_at(1)
1668 for i in 0 .. inters.len() {
1669 inter := inters.at(i)
1670 t.collect_generic_fn_value_refs_in_expr_cursor(inter.edge(0),
1671 include_generic_calls, false)
1672 t.collect_generic_fn_value_refs_in_expr_cursor(inter.edge(1),
1673 include_generic_calls, false)
1674 }
1675 }
1676 .expr_unsafe {
1677 for i in 0 .. expr.edge_count() {
1678 t.collect_generic_fn_value_refs_in_stmt_cursor(expr.edge(i), include_generic_calls)
1679 }
1680 }
1681 .aux_field_init {
1682 t.collect_generic_fn_value_refs_in_expr_cursor(expr.edge(0), include_generic_calls,
1683 false)
1684 }
1685 else {}
1686 }
1687}
1688
1689fn (mut t Transformer) collect_generic_fn_value_refs_in_stmt_edges_cursor(stmt ast.Cursor, start int, include_generic_calls bool) {
1690 if start >= stmt.edge_count() {
1691 return
1692 }
1693 for i in start .. stmt.edge_count() {
1694 t.collect_generic_fn_value_refs_in_stmt_cursor(stmt.edge(i), include_generic_calls)
1695 }
1696}
1697
1698fn (mut t Transformer) collect_generic_fn_value_refs_in_field_values_cursor(fields ast.CursorList, value_edge int, include_generic_calls bool) {
1699 for i in 0 .. fields.len() {
1700 t.collect_generic_fn_value_refs_in_expr_cursor(fields.at(i).edge(value_edge),
1701 include_generic_calls, false)
1702 }
1703}
1704
1705fn (mut t Transformer) collect_generic_fn_value_refs_in_stmt_cursor(stmt ast.Cursor, include_generic_calls bool) {
1706 if !stmt.is_valid() {
1707 return
1708 }
1709 match stmt.kind() {
1710 .stmt_assert {
1711 t.collect_generic_fn_value_refs_in_expr_cursor(stmt.edge(0), include_generic_calls,
1712 false)
1713 t.collect_generic_fn_value_refs_in_expr_cursor(stmt.edge(1), include_generic_calls,
1714 false)
1715 }
1716 .stmt_assign {
1717 lhs_len := stmt.extra_int()
1718 if lhs_len < stmt.edge_count() {
1719 for i in lhs_len .. stmt.edge_count() {
1720 t.collect_generic_fn_value_refs_in_expr_cursor(stmt.edge(i),
1721 include_generic_calls, false)
1722 }
1723 }
1724 }
1725 .stmt_block {
1726 t.collect_generic_fn_value_refs_in_stmt_edges_cursor(stmt, 0, include_generic_calls)
1727 }
1728 .stmt_comptime {
1729 t.collect_generic_fn_value_refs_in_stmt_cursor(stmt.edge(0), include_generic_calls)
1730 }
1731 .stmt_const_decl {
1732 t.collect_generic_fn_value_refs_in_field_values_cursor(stmt.list_at(0), 0,
1733 include_generic_calls)
1734 }
1735 .stmt_defer {
1736 t.collect_generic_fn_value_refs_in_stmt_edges_cursor(stmt, 0, include_generic_calls)
1737 }
1738 .stmt_enum_decl {
1739 t.collect_generic_fn_value_refs_in_field_values_cursor(stmt.list_at(2), 1,
1740 include_generic_calls)
1741 }
1742 .stmt_expr {
1743 t.collect_generic_fn_value_refs_in_expr_cursor(stmt.edge(0), include_generic_calls,
1744 false)
1745 }
1746 .stmt_fn_decl {
1747 t.collect_generic_fn_value_refs_in_stmt_list_cursor(stmt.list_at(3),
1748 include_generic_calls)
1749 }
1750 .stmt_for_in {
1751 t.collect_generic_fn_value_refs_in_expr_cursor(stmt.edge(2), include_generic_calls,
1752 false)
1753 }
1754 .stmt_for {
1755 t.collect_generic_fn_value_refs_in_stmt_cursor(stmt.edge(0), include_generic_calls)
1756 t.collect_generic_fn_value_refs_in_expr_cursor(stmt.edge(1), include_generic_calls,
1757 false)
1758 t.collect_generic_fn_value_refs_in_stmt_cursor(stmt.edge(2), include_generic_calls)
1759 t.collect_generic_fn_value_refs_in_stmt_edges_cursor(stmt, 3, include_generic_calls)
1760 }
1761 .stmt_global_decl {
1762 t.collect_generic_fn_value_refs_in_field_values_cursor(stmt.list_at(1), 1,
1763 include_generic_calls)
1764 }
1765 .stmt_label {
1766 t.collect_generic_fn_value_refs_in_stmt_cursor(stmt.edge(0), include_generic_calls)
1767 }
1768 .stmt_return {
1769 for i in 0 .. stmt.edge_count() {
1770 t.collect_generic_fn_value_refs_in_expr_cursor(stmt.edge(i), include_generic_calls,
1771 false)
1772 }
1773 }
1774 .stmt_struct_decl {
1775 t.collect_generic_fn_value_refs_in_field_values_cursor(stmt.list_at(4), 1,
1776 include_generic_calls)
1777 }
1778 else {}
1779 }
1780}
1781
1782fn (mut t Transformer) collect_generic_fn_value_refs_in_stmt_list_cursor(stmts ast.CursorList, include_generic_calls bool) {
1783 for i in 0 .. stmts.len() {
1784 t.collect_generic_fn_value_refs_in_stmt_cursor(stmts.at(i), include_generic_calls)
1785 }
1786}
1787
1788fn (mut t Transformer) collect_generic_fn_value_refs_in_stmt(stmt ast.Stmt, include_generic_calls bool) {
1789 if !stmt_has_valid_data(stmt) {
1790 return
1791 }
1792 match stmt {
1793 ast.AssertStmt {
1794 t.collect_generic_fn_value_refs_in_expr(stmt.expr, include_generic_calls, false)
1795 t.collect_generic_fn_value_refs_in_expr(stmt.extra, include_generic_calls, false)
1796 }
1797 ast.AssignStmt {
1798 for expr in stmt.rhs {
1799 t.collect_generic_fn_value_refs_in_expr(expr, include_generic_calls, false)
1800 }
1801 }
1802 ast.BlockStmt {
1803 t.collect_generic_fn_value_refs_in_stmts(stmt.stmts, include_generic_calls)
1804 }
1805 ast.ComptimeStmt {
1806 t.collect_generic_fn_value_refs_in_stmt(stmt.stmt, include_generic_calls)
1807 }
1808 ast.ConstDecl {
1809 for field in stmt.fields {
1810 t.collect_generic_fn_value_refs_in_expr(field.value, include_generic_calls, false)
1811 }
1812 }
1813 ast.DeferStmt {
1814 t.collect_generic_fn_value_refs_in_stmts(stmt.stmts, include_generic_calls)
1815 }
1816 ast.EnumDecl {
1817 for field in stmt.fields {
1818 t.collect_generic_fn_value_refs_in_expr(field.value, include_generic_calls, false)
1819 }
1820 }
1821 ast.ExprStmt {
1822 t.collect_generic_fn_value_refs_in_expr(stmt.expr, include_generic_calls, false)
1823 }
1824 ast.FnDecl {
1825 t.collect_generic_fn_value_refs_in_stmts(stmt.stmts, include_generic_calls)
1826 }
1827 ast.ForInStmt {
1828 t.collect_generic_fn_value_refs_in_expr(stmt.expr, include_generic_calls, false)
1829 }
1830 ast.ForStmt {
1831 t.collect_generic_fn_value_refs_in_stmt(stmt.init, include_generic_calls)
1832 t.collect_generic_fn_value_refs_in_expr(stmt.cond, include_generic_calls, false)
1833 t.collect_generic_fn_value_refs_in_stmt(stmt.post, include_generic_calls)
1834 t.collect_generic_fn_value_refs_in_stmts(stmt.stmts, include_generic_calls)
1835 }
1836 ast.GlobalDecl {
1837 for field in stmt.fields {
1838 t.collect_generic_fn_value_refs_in_expr(field.value, include_generic_calls, false)
1839 }
1840 }
1841 ast.LabelStmt {
1842 t.collect_generic_fn_value_refs_in_stmt(stmt.stmt, include_generic_calls)
1843 }
1844 ast.ReturnStmt {
1845 for expr in stmt.exprs {
1846 t.collect_generic_fn_value_refs_in_expr(expr, include_generic_calls, false)
1847 }
1848 }
1849 ast.StructDecl {
1850 for field in stmt.fields {
1851 t.collect_generic_fn_value_refs_in_expr(field.value, include_generic_calls, false)
1852 }
1853 }
1854 else {}
1855 }
1856}
1857
1858fn (mut t Transformer) collect_generic_fn_value_refs_in_expr(expr ast.Expr, include_generic_calls bool, in_call_lhs bool) {
1859 if !expr_has_valid_data(expr) {
1860 return
1861 }
1862 match expr {
1863 ast.ArrayInitExpr {
1864 for item in expr.exprs {
1865 t.collect_generic_fn_value_refs_in_expr(item, include_generic_calls, false)
1866 }
1867 t.collect_generic_fn_value_refs_in_expr(expr.init, include_generic_calls, false)
1868 t.collect_generic_fn_value_refs_in_expr(expr.cap, include_generic_calls, false)
1869 t.collect_generic_fn_value_refs_in_expr(expr.len, include_generic_calls, false)
1870 }
1871 ast.AsCastExpr {
1872 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1873 }
1874 ast.AssocExpr {
1875 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1876 for field in expr.fields {
1877 t.collect_generic_fn_value_refs_in_expr(field.value, include_generic_calls, false)
1878 }
1879 }
1880 ast.CallExpr {
1881 t.collect_generic_fn_value_refs_in_expr(expr.lhs, include_generic_calls, true)
1882 for arg in expr.args {
1883 t.collect_generic_fn_value_refs_in_expr(arg, include_generic_calls, false)
1884 }
1885 }
1886 ast.CallOrCastExpr {
1887 t.collect_generic_fn_value_refs_in_expr(expr.lhs, include_generic_calls, false)
1888 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1889 }
1890 ast.CastExpr {
1891 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1892 }
1893 ast.ComptimeExpr {
1894 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1895 }
1896 ast.FieldInit {
1897 t.collect_generic_fn_value_refs_in_expr(expr.value, include_generic_calls, false)
1898 }
1899 ast.FnLiteral {
1900 for captured in expr.captured_vars {
1901 t.collect_generic_fn_value_refs_in_expr(captured, include_generic_calls, false)
1902 }
1903 t.collect_generic_fn_value_refs_in_stmts(expr.stmts, include_generic_calls)
1904 }
1905 ast.GenericArgs {
1906 // Generic function values like `handler[A, X]` are instantiated later by
1907 // cgen. Roots are value-position refs; callees are added transitively.
1908 base_name := generic_ref_short_name(expr.lhs)
1909 if (include_generic_calls || !in_call_lhs) && base_name in t.generic_fn_decl_names {
1910 t.generic_fn_value_names[base_name] = true
1911 }
1912 t.collect_generic_fn_value_refs_in_expr(expr.lhs, include_generic_calls, in_call_lhs)
1913 for arg in expr.args {
1914 t.collect_generic_fn_value_refs_in_expr(arg, include_generic_calls, false)
1915 }
1916 }
1917 ast.GenericArgOrIndexExpr {
1918 base_name := generic_ref_short_name(expr.lhs)
1919 if (include_generic_calls || !in_call_lhs) && base_name in t.generic_fn_decl_names {
1920 t.generic_fn_value_names[base_name] = true
1921 }
1922 t.collect_generic_fn_value_refs_in_expr(expr.lhs, include_generic_calls, in_call_lhs)
1923 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1924 }
1925 ast.IfExpr {
1926 t.collect_generic_fn_value_refs_in_expr(expr.cond, include_generic_calls, false)
1927 t.collect_generic_fn_value_refs_in_stmts(expr.stmts, include_generic_calls)
1928 t.collect_generic_fn_value_refs_in_expr(expr.else_expr, include_generic_calls, false)
1929 }
1930 ast.IfGuardExpr {
1931 t.collect_generic_fn_value_refs_in_stmt(expr.stmt, include_generic_calls)
1932 }
1933 ast.IndexExpr {
1934 name := generic_ref_short_name(expr.lhs)
1935 if (include_generic_calls || !in_call_lhs) && name in t.generic_fn_decl_names {
1936 t.generic_fn_value_names[name] = true
1937 }
1938 t.collect_generic_fn_value_refs_in_expr(expr.lhs, include_generic_calls, in_call_lhs)
1939 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1940 }
1941 ast.InfixExpr {
1942 t.collect_generic_fn_value_refs_in_expr(expr.lhs, include_generic_calls, false)
1943 t.collect_generic_fn_value_refs_in_expr(expr.rhs, include_generic_calls, false)
1944 }
1945 ast.InitExpr {
1946 for field in expr.fields {
1947 t.collect_generic_fn_value_refs_in_expr(field.value, include_generic_calls, false)
1948 }
1949 }
1950 ast.KeywordOperator {
1951 for item in expr.exprs {
1952 t.collect_generic_fn_value_refs_in_expr(item, include_generic_calls, false)
1953 }
1954 }
1955 ast.LambdaExpr {
1956 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1957 }
1958 ast.LockExpr {
1959 for lock_expr in expr.lock_exprs {
1960 t.collect_generic_fn_value_refs_in_expr(lock_expr, include_generic_calls, false)
1961 }
1962 for rlock_expr in expr.rlock_exprs {
1963 t.collect_generic_fn_value_refs_in_expr(rlock_expr, include_generic_calls, false)
1964 }
1965 t.collect_generic_fn_value_refs_in_stmts(expr.stmts, include_generic_calls)
1966 }
1967 ast.MapInitExpr {
1968 for key in expr.keys {
1969 t.collect_generic_fn_value_refs_in_expr(key, include_generic_calls, false)
1970 }
1971 for val in expr.vals {
1972 t.collect_generic_fn_value_refs_in_expr(val, include_generic_calls, false)
1973 }
1974 }
1975 ast.MatchExpr {
1976 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1977 for branch in expr.branches {
1978 for cond in branch.cond {
1979 t.collect_generic_fn_value_refs_in_expr(cond, include_generic_calls, false)
1980 }
1981 t.collect_generic_fn_value_refs_in_stmts(branch.stmts, include_generic_calls)
1982 }
1983 }
1984 ast.ModifierExpr {
1985 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1986 }
1987 ast.OrExpr {
1988 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1989 t.collect_generic_fn_value_refs_in_stmts(expr.stmts, include_generic_calls)
1990 }
1991 ast.ParenExpr {
1992 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, in_call_lhs)
1993 }
1994 ast.PostfixExpr {
1995 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1996 }
1997 ast.PrefixExpr {
1998 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
1999 }
2000 ast.RangeExpr {
2001 t.collect_generic_fn_value_refs_in_expr(expr.start, include_generic_calls, false)
2002 t.collect_generic_fn_value_refs_in_expr(expr.end, include_generic_calls, false)
2003 }
2004 ast.SelectExpr {
2005 t.collect_generic_fn_value_refs_in_stmt(expr.stmt, include_generic_calls)
2006 t.collect_generic_fn_value_refs_in_stmts(expr.stmts, include_generic_calls)
2007 t.collect_generic_fn_value_refs_in_expr(expr.next, include_generic_calls, false)
2008 }
2009 ast.SelectorExpr {
2010 t.collect_generic_fn_value_refs_in_expr(expr.lhs, include_generic_calls, in_call_lhs)
2011 }
2012 ast.SqlExpr {
2013 t.collect_generic_fn_value_refs_in_expr(expr.expr, include_generic_calls, false)
2014 }
2015 ast.StringInterLiteral {
2016 for inter in expr.inters {
2017 t.collect_generic_fn_value_refs_in_expr(inter.expr, include_generic_calls, false)
2018 t.collect_generic_fn_value_refs_in_expr(inter.format_expr, include_generic_calls,
2019 false)
2020 }
2021 }
2022 ast.Tuple {
2023 for item in expr.exprs {
2024 t.collect_generic_fn_value_refs_in_expr(item, include_generic_calls, false)
2025 }
2026 }
2027 ast.UnsafeExpr {
2028 t.collect_generic_fn_value_refs_in_stmts(expr.stmts, include_generic_calls)
2029 }
2030 else {}
2031 }
2032}
2033
2034// pre_pass runs the sequential pre-pass: builds elided_fns and collects runtime const inits.
2035pub fn (mut t Transformer) pre_pass(files []ast.File) {
2036 t.collect_generic_fn_value_refs(files)
2037 // Pre-pass: scan all function declarations for conditional compilation attributes
2038 // to build elided_fns set before transforming call sites
2039 for file in files {
2040 for stmt in file.stmts {
2041 if stmt is ast.FnDecl {
2042 for attr in stmt.attributes {
2043 if attr.comptime_cond !is ast.EmptyExpr {
2044 if !t.eval_comptime_cond(attr.comptime_cond) {
2045 t.elided_fns[stmt.name] = true
2046 }
2047 }
2048 }
2049 }
2050 }
2051 }
2052 // Pre-pass: collect const declarations that require runtime initialization.
2053 if !t.is_eval_backend() {
2054 t.collect_runtime_const_inits(files)
2055 }
2056 // Cache scope and method maps for lock-free access during transform.
2057 t.cache_env_maps()
2058 t.collect_sum_type_decl_variant_names(files)
2059}
2060
2061// pre_pass_from_flat is the FlatAst-driven equivalent of pre_pass. It walks
2062// file-level stmt cursors so the transformer no longer depends on
2063// pre-rehydrated []ast.File or per-file []ast.Stmt arrays for its accumulators.
2064// The generic-function scans still reuse recursive legacy visitors; the
2065// conditional attribute and runtime-const scans are cursor-native.
2066pub fn (mut t Transformer) pre_pass_from_flat(flat &ast.FlatAst) {
2067 t.collect_generic_fn_value_refs_from_flat(flat)
2068 for i in 0 .. flat.files.len {
2069 stmts := flat.file_cursor(i).stmts()
2070 for j in 0 .. stmts.len() {
2071 stmt := stmts.at(j)
2072 if stmt.kind() != .stmt_fn_decl {
2073 continue
2074 }
2075 attrs := stmt.list_at(2)
2076 for ai in 0 .. attrs.len() {
2077 cond := attrs.at(ai).edge(1)
2078 if !cond.is_valid() || cond.kind() == .expr_empty {
2079 continue
2080 }
2081 if !t.eval_comptime_cond_cursor(cond) {
2082 t.elided_fns[stmt.name()] = true
2083 }
2084 }
2085 }
2086 }
2087 if !t.is_eval_backend() {
2088 t.collect_runtime_const_inits_from_flat(flat)
2089 }
2090 t.cache_env_maps()
2091 t.collect_sum_type_decl_variant_names_from_flat(flat)
2092}
2093
2094// cache_env_maps snapshots the shared Environment maps into plain maps
2095// for lock-free access during parallel file transformation.
2096fn (mut t Transformer) cache_env_maps() {
2097 t.cached_scopes = t.env.snapshot_scopes()
2098 t.cached_scope_keys = t.cached_scopes.keys()
2099 t.cached_methods = t.env.snapshot_methods()
2100 t.cached_method_keys = t.cached_methods.keys()
2101 t.cached_fn_scopes = t.env.snapshot_fn_scopes()
2102 t.build_cached_imported_module_scopes()
2103 t.build_cached_struct_field_type_index()
2104 t.build_cached_fn_type_index()
2105 t.build_cached_method_base_index()
2106 mut by_short := map[string][]string{}
2107 for key in t.cached_method_keys {
2108 by_short[method_short_name(key)] << key
2109 }
2110 t.cached_method_keys_by_short = by_short.move()
2111}
2112
2113// build_cached_imported_module_scopes precomputes, per module, the resolved
2114// scopes of the modules it imports (skipping self, C and unnamed entries).
2115// lookup_imported_var_type then iterates only this short list instead of
2116// rescanning and sorting the whole symbol table on every call. The import set
2117// is stable for the duration of a transform run (cached_scopes is snapshotted
2118// once in pre_pass), so a single precompute is safe.
2119fn (mut t Transformer) build_cached_imported_module_scopes() {
2120 mut by_module := map[string][]&types.Scope{}
2121 for module_name in t.cached_scope_keys {
2122 scope := t.cached_scopes[module_name] or { continue }
2123 mut imported_scopes := []&types.Scope{}
2124 for key, obj in scope.objects {
2125 if obj !is types.Module {
2126 continue
2127 }
2128 module_obj := obj as types.Module
2129 import_name := if module_obj.name != '' { module_obj.name } else { key }
2130 if import_name == '' || import_name == module_name || import_name == 'C' {
2131 continue
2132 }
2133 mut module_scope := module_obj.scope
2134 if module_scope == unsafe { nil } {
2135 module_scope = t.cached_scopes[import_name] or { continue }
2136 }
2137 imported_scopes << module_scope
2138 }
2139 by_module[module_name] = imported_scopes
2140 }
2141 t.cached_imported_module_scopes = by_module.move()
2142}
2143
2144fn (mut t Transformer) build_cached_struct_field_type_index() {
2145 mut index := map[string]types.Type{}
2146 for module_name in t.cached_scope_keys {
2147 scope := t.cached_scopes[module_name] or { continue }
2148 for obj_name, obj in scope.objects {
2149 typ := transformer_object_type(obj) or { continue }
2150 if typ is types.Struct {
2151 add_struct_field_type_index_entries(mut index, module_name, obj_name, typ)
2152 }
2153 }
2154 }
2155 t.cached_struct_field_types = index.move()
2156}
2157
2158fn add_struct_field_type_index_entries(mut index map[string]types.Type, module_name string, obj_name string, st types.Struct) {
2159 mut names := []string{cap: 4}
2160 if obj_name != '' {
2161 names << obj_name
2162 }
2163 if st.name != '' && st.name !in names {
2164 names << st.name
2165 }
2166 if module_name != '' && obj_name != '' && !obj_name.contains('__') {
2167 qualified := '${module_name}__${obj_name}'
2168 if qualified !in names {
2169 names << qualified
2170 }
2171 }
2172 for field in st.fields {
2173 if field.name == '' || !transformer_string_has_valid_data(field.name) {
2174 continue
2175 }
2176 for name in names {
2177 key := struct_field_generic_decl_key(name, field.name)
2178 if key !in index {
2179 index[key] = field.typ
2180 }
2181 }
2182 if module_name != '' && obj_name != '' {
2183 module_key := struct_field_lookup_cache_key(module_name, obj_name, field.name)
2184 if module_key !in index {
2185 index[module_key] = field.typ
2186 }
2187 }
2188 if module_name != '' && st.name.contains('__') {
2189 short_name := st.name.all_after_last('__')
2190 if short_name != '' {
2191 module_key := struct_field_lookup_cache_key(module_name, short_name, field.name)
2192 if module_key !in index {
2193 index[module_key] = field.typ
2194 }
2195 }
2196 }
2197 }
2198}
2199
2200fn struct_field_lookup_cache_key(module_name string, struct_name string, field_name string) string {
2201 return '${module_name}#${struct_name}.${field_name}'
2202}
2203
2204fn (mut t Transformer) build_cached_fn_type_index() {
2205 mut fn_index := map[string]types.Type{}
2206 mut ret_index := map[string]types.Type{}
2207 for module_name in t.cached_scope_keys {
2208 scope := t.cached_scopes[module_name] or { continue }
2209 for fn_name, obj in scope.objects {
2210 if obj is types.Fn {
2211 fn_obj := obj as types.Fn
2212 typ := fn_obj.get_typ()
2213 if typ is types.FnType {
2214 t.add_cached_fn_type_entry(mut fn_index, mut ret_index, module_name, fn_name,
2215 typ)
2216 short_module := short_module_name(module_name)
2217 if short_module != module_name {
2218 t.add_cached_fn_type_entry(mut fn_index, mut ret_index, short_module,
2219 fn_name, typ)
2220 }
2221 any_key := '*#${fn_name}'
2222 if any_key !in fn_index {
2223 fn_index[any_key] = typ
2224 if ret_type := typ.get_return_type() {
2225 ret_index[any_key] = ret_type
2226 }
2227 }
2228 }
2229 }
2230 }
2231 }
2232 t.cached_fn_type_index = fn_index.move()
2233 t.cached_fn_return_type_index = ret_index.move()
2234}
2235
2236fn (mut t Transformer) add_cached_fn_type_entry(mut fn_index map[string]types.Type, mut ret_index map[string]types.Type, module_name string, fn_name string, fn_type types.FnType) {
2237 key := '${module_name}#${fn_name}'
2238 if key in fn_index {
2239 return
2240 }
2241 fn_index[key] = fn_type
2242 if ret_type := fn_type.get_return_type() {
2243 ret_index[key] = ret_type
2244 }
2245}
2246
2247fn short_module_name(module_name string) string {
2248 if module_name.contains('.') {
2249 return module_name.all_after_last('.')
2250 }
2251 if module_name.contains('__') {
2252 return module_name.all_after_last('__')
2253 }
2254 return module_name
2255}
2256
2257// build_cached_method_base_index precomputes, for each type, a base-method-name
2258// -> FnType map so lookup_method_cached is O(1) instead of scanning every method
2259// and recomputing base names per call site. For each base name it keeps the
2260// first method (in snapshot order) whose type is an FnType, matching the old
2261// linear scan's "first base-or-exact match that is a FnType wins" semantics.
2262fn (mut t Transformer) build_cached_method_base_index() {
2263 // Iterate via keys()+index, not `for k, v in t.cached_methods`: v2's own
2264 // codegen mistypes the value of a `for k, v` over map[string][]&types.Fn as
2265 // []types.Fn (value array), breaking self-host.
2266 mut index := map[string]types.Type{}
2267 for type_name in t.cached_methods.keys() {
2268 methods := t.cached_methods[type_name] or { continue }
2269 for method in methods {
2270 typ := method.get_typ()
2271 if typ is types.FnType {
2272 base := generic_base_name_without_specialization(method.get_name())
2273 key := '${type_name}#${base}'
2274 if key !in index {
2275 index[key] = typ
2276 }
2277 }
2278 }
2279 }
2280 t.cached_method_base_index = index.move()
2281}
2282
2283// GeneratedFnsParts is the pure-computation bundle produced by
2284// `generated_fns_parts`. Holds the three already-routed buckets of
2285// auto-generated helper FnDecls: `core_fns` for the builtin module,
2286// `module_fns` keyed by source module name for module-prefixed helpers
2287// (e.g. `time__FormatDate__str`), and `user_fns` for everything else
2288// (typically main / user types).
2289pub struct GeneratedFnsParts {
2290pub:
2291 core_fns []ast.Stmt
2292 module_fns map[string][]ast.Stmt
2293 user_fns []ast.Stmt
2294}
2295
2296// generated_fns_parts is the pure-computation extraction of the
2297// "collect + route" segment previously inline in `post_pass`. Runs each
2298// `generate_*_functions` helper that has a non-empty needed-list (str,
2299// clone, sort-comparator, array-method, go-wrapper), then routes each
2300// emitted FnDecl by name: `is_core_generated_fn` → `core_fns`; otherwise
2301// `generated_fn_module(name, files)` non-empty → `module_fns[mod]`;
2302// otherwise → `user_fns`. Returns `none` when no generator fires.
2303//
2304// Does NOT mutate `files` or any caller state (beyond the generators'
2305// own bookkeeping on `t`, which is part of their existing contract).
2306// Caller decides whether to splice the buckets into a legacy
2307// `[]ast.File` (via `post_pass`) or into a `FlatBuilder` (future
2308// `post_pass_to_flat`).
2309//
2310// Fifth Phase 5 (post_pass port) `_parts` extraction (completes the
2311// s144 punch list: runtime-const-init-fns, runtime-const-init-main-
2312// calls, live-reload, test-main, generated-fns split). Bit-equal:
2313// identical generator invocation order, identical per-stmt routing
2314// (FnDecl name → bucket), identical bucket-append order.
2315pub fn (mut t Transformer) generated_fns_parts(files []ast.File) ?GeneratedFnsParts {
2316 mut generated_fns := []ast.Stmt{}
2317 if t.needed_str_fns.len > 0 {
2318 generated_fns << t.generate_str_functions(files)
2319 }
2320 if t.needed_clone_fns.len > 0 {
2321 generated_fns << t.generate_clone_functions()
2322 }
2323 if t.needed_sort_fns.len > 0 {
2324 generated_fns << t.generate_sort_comparator_functions()
2325 }
2326 if t.needed_array_contains_fns.len > 0 || t.needed_array_index_fns.len > 0
2327 || t.needed_array_last_index_fns.len > 0 {
2328 generated_fns << t.generate_array_method_functions()
2329 }
2330 if t.needed_go_wrappers.len > 0 {
2331 generated_fns << t.generate_go_wrapper_functions()
2332 }
2333 if generated_fns.len == 0 {
2334 return none
2335 }
2336 mut core_fns := []ast.Stmt{}
2337 mut user_fns := []ast.Stmt{}
2338 mut module_fns := map[string][]ast.Stmt{}
2339 for gf in generated_fns {
2340 mut is_core := false
2341 mut fn_module := ''
2342 if gf is ast.FnDecl {
2343 is_core = is_core_generated_fn(gf.name)
2344 if !is_core {
2345 // Check if function name has a module prefix (e.g., time__FormatDate__str)
2346 // Extract module name and route to correct module file.
2347 fn_module = generated_fn_module(gf.name, files)
2348 }
2349 }
2350 if is_core {
2351 core_fns << gf
2352 } else if fn_module != '' {
2353 module_fns[fn_module] << gf
2354 } else {
2355 user_fns << gf
2356 }
2357 }
2358 return GeneratedFnsParts{
2359 core_fns: core_fns
2360 module_fns: module_fns
2361 user_fns: user_fns
2362 }
2363}
2364
2365// generated_fns_parts_from_flat is the FlatAst-input counterpart to
2366// `generated_fns_parts`. Same generators, same bucket layout, same
2367// per-stmt routing — only the two `[]ast.File` consumers change:
2368// - `generate_str_functions(files)` is replaced with
2369// `generate_str_functions_with_explicit(explicit_str_fns)` where
2370// `explicit_str_fns` comes from `explicit_str_method_fn_names_from_flat`
2371// (s165).
2372// - `generated_fn_module(name, files)` is replaced with
2373// `generated_fn_module_from_flat(name, flat)` (s164).
2374//
2375// Lets `_via_driver` wedges compute `GeneratedFnsParts` from `&builder.flat`
2376// instead of `result []ast.File`, so the parts computation no longer pins
2377// the un-post_pass'd legacy file array.
2378pub fn (mut t Transformer) generated_fns_parts_from_flat(flat &ast.FlatAst) ?GeneratedFnsParts {
2379 explicit_str_fns := t.explicit_str_method_fn_names_from_flat(flat)
2380 mut generated_fns := []ast.Stmt{}
2381 if t.needed_str_fns.len > 0 {
2382 generated_fns << t.generate_str_functions_with_explicit(explicit_str_fns)
2383 }
2384 if t.needed_clone_fns.len > 0 {
2385 generated_fns << t.generate_clone_functions()
2386 }
2387 if t.needed_sort_fns.len > 0 {
2388 generated_fns << t.generate_sort_comparator_functions()
2389 }
2390 if t.needed_array_contains_fns.len > 0 || t.needed_array_index_fns.len > 0
2391 || t.needed_array_last_index_fns.len > 0 {
2392 generated_fns << t.generate_array_method_functions()
2393 }
2394 if t.needed_go_wrappers.len > 0 {
2395 generated_fns << t.generate_go_wrapper_functions()
2396 }
2397 if generated_fns.len == 0 {
2398 return none
2399 }
2400 mut core_fns := []ast.Stmt{}
2401 mut user_fns := []ast.Stmt{}
2402 mut module_fns := map[string][]ast.Stmt{}
2403 for gf in generated_fns {
2404 mut is_core := false
2405 mut fn_module := ''
2406 if gf is ast.FnDecl {
2407 is_core = is_core_generated_fn(gf.name)
2408 if !is_core {
2409 fn_module = generated_fn_module_from_flat(gf.name, flat)
2410 }
2411 }
2412 if is_core {
2413 core_fns << gf
2414 } else if fn_module != '' {
2415 module_fns[fn_module] << gf
2416 } else {
2417 user_fns << gf
2418 }
2419 }
2420 return GeneratedFnsParts{
2421 core_fns: core_fns
2422 module_fns: module_fns
2423 user_fns: user_fns
2424 }
2425}
2426
2427// find_flat_file_idx_by_mod returns the first index into `flat.files` whose
2428// mod string equals `mod_name`, or -1 if no such file is registered.
2429// Mirrors the linear `for i, file in files { if file.mod == mod_name {...} }`
2430// scans inline in legacy `post_pass`.
2431fn find_flat_file_idx_by_mod(flat &ast.FlatAst, mod_name string) int {
2432 for i in 0 .. flat.files.len {
2433 if flat.string_at(flat.files[i].mod_idx) == mod_name {
2434 return i
2435 }
2436 }
2437 return -1
2438}
2439
2440// inject_generated_fns_to_files is the legacy `[]ast.File` splice for
2441// `GeneratedFnsParts`. Extracted from `post_pass` so the splice can be
2442// invoked from tests as the reference for the flat-aware port (s153)
2443// without re-running the full generator pipeline. Behaviour is bit-equal
2444// to the previous inline routing:
2445//
2446// 1. `core_fns` → builtin file (fallback: append to `user_fns` if no
2447// builtin file is registered).
2448// 2. `module_fns[mod]` → mod file (fallback: append to `user_fns` if no
2449// mod file is registered).
2450// 3. `user_fns` → main file → builtin file → last file (3-tier
2451// fallback ordering).
2452//
2453// Each splice rebuilds the target `ast.File` with the existing attrs +
2454// imports preserved and the new FnDecls appended after the existing
2455// stmts. Map iteration over `module_fns` relies on V's insertion-ordered
2456// DenseArray semantics.
2457fn inject_generated_fns_to_files(mut result []ast.File, parts GeneratedFnsParts) {
2458 core_fns := unsafe { parts.core_fns }
2459 mut user_fns := unsafe { parts.user_fns }
2460 module_fns := unsafe { parts.module_fns }
2461 if core_fns.len > 0 {
2462 mut builtin_idx := -1
2463 for i, file in result {
2464 if file.mod == 'builtin' {
2465 builtin_idx = i
2466 break
2467 }
2468 }
2469 if builtin_idx >= 0 {
2470 file := result[builtin_idx]
2471 mut new_stmts := []ast.Stmt{cap: file.stmts.len + core_fns.len}
2472 for stmt in file.stmts {
2473 new_stmts << stmt
2474 }
2475 for fn_decl in core_fns {
2476 new_stmts << fn_decl
2477 }
2478 result[builtin_idx] = ast.File{
2479 attributes: file.attributes
2480 mod: file.mod
2481 name: file.name
2482 stmts: new_stmts
2483 imports: file.imports
2484 }
2485 } else {
2486 user_fns << core_fns
2487 }
2488 }
2489 for mod_name, fns in module_fns {
2490 mut mod_idx := -1
2491 for i, file in result {
2492 if file.mod == mod_name {
2493 mod_idx = i
2494 break
2495 }
2496 }
2497 if mod_idx >= 0 {
2498 file := result[mod_idx]
2499 mut new_stmts := []ast.Stmt{cap: file.stmts.len + fns.len}
2500 for stmt in file.stmts {
2501 new_stmts << stmt
2502 }
2503 for fn_decl in fns {
2504 new_stmts << fn_decl
2505 }
2506 result[mod_idx] = ast.File{
2507 attributes: file.attributes
2508 mod: file.mod
2509 name: file.name
2510 stmts: new_stmts
2511 imports: file.imports
2512 }
2513 } else {
2514 for fn_decl in fns {
2515 user_fns << fn_decl
2516 }
2517 }
2518 }
2519 if user_fns.len > 0 {
2520 mut target_idx := -1
2521 for i, file in result {
2522 if file.mod == 'main' {
2523 target_idx = i
2524 break
2525 }
2526 }
2527 if target_idx == -1 {
2528 for i, file in result {
2529 if file.mod == 'builtin' {
2530 target_idx = i
2531 break
2532 }
2533 }
2534 }
2535 if target_idx == -1 && result.len > 0 {
2536 target_idx = result.len - 1
2537 }
2538 if target_idx >= 0 {
2539 file := result[target_idx]
2540 mut new_stmts := []ast.Stmt{cap: file.stmts.len + user_fns.len}
2541 for stmt in file.stmts {
2542 new_stmts << stmt
2543 }
2544 for fn_decl in user_fns {
2545 new_stmts << fn_decl
2546 }
2547 result[target_idx] = ast.File{
2548 attributes: file.attributes
2549 mod: file.mod
2550 name: file.name
2551 stmts: new_stmts
2552 imports: file.imports
2553 }
2554 }
2555 }
2556}
2557
2558// inject_generated_fns_to_flat is the FlatBuilder-side splice counterpart
2559// to the 3-way `generated_fns_parts` routing previously inline in
2560// `post_pass`. Takes a pre-computed `GeneratedFnsParts` (caller still
2561// runs `t.generated_fns_parts(files)` against legacy `[]ast.File` because
2562// the generators themselves haven't been ported to flat yet) and folds
2563// each bucket into the appropriate FlatBuilder file root via
2564// `out.append_file_stmts` (s150's seed primitive):
2565//
2566// 1. `core_fns` → builtin file (fallback: append to `user_fns` if no
2567// builtin file registered, mirroring legacy `user_fns << core_fns`).
2568// 2. `module_fns[mod]` → mod file (fallback: append to `user_fns` if no
2569// mod file registered).
2570// 3. `user_fns` → main file → builtin file → last file (legacy 3-tier
2571// fallback ordering preserved).
2572//
2573// Each splice emits its FnDecls via `out.emit_stmt`, collects the ids,
2574// then a single `out.append_file_stmts(idx, ids)` call rebuilds the file
2575// root. Map iteration over `module_fns` relies on V's insertion-ordered
2576// DenseArray semantics so bit-equality is preserved.
2577//
2578// Third flat-aware port (s153, completes the 3 append-only post_pass
2579// steps from s150's punch list: embed_file_helper, test_main,
2580// generated_fns). Pinned by 3-case test in
2581// `inject_generated_fns_to_flat_test.v`.
2582pub fn (mut t Transformer) inject_generated_fns_to_flat(mut out ast.FlatBuilder, parts GeneratedFnsParts) {
2583 core_fns := unsafe { parts.core_fns }
2584 mut user_fns := unsafe { parts.user_fns }
2585 module_fns := unsafe { parts.module_fns }
2586 // Bucket 1: core_fns → builtin file (fallback: user_fns).
2587 if core_fns.len > 0 {
2588 builtin_idx := find_flat_file_idx_by_mod(&out.flat, 'builtin')
2589 if builtin_idx >= 0 {
2590 mut ids := []ast.FlatNodeId{cap: core_fns.len}
2591 for fn_decl in core_fns {
2592 ids << out.emit_stmt(fn_decl)
2593 }
2594 out.append_file_stmts(builtin_idx, ids)
2595 } else {
2596 user_fns << core_fns
2597 }
2598 }
2599 // Bucket 2: module_fns[mod] → mod file (fallback: user_fns).
2600 for mod_name, fns in module_fns {
2601 mod_idx := find_flat_file_idx_by_mod(&out.flat, mod_name)
2602 if mod_idx >= 0 {
2603 mut ids := []ast.FlatNodeId{cap: fns.len}
2604 for fn_decl in fns {
2605 ids << out.emit_stmt(fn_decl)
2606 }
2607 out.append_file_stmts(mod_idx, ids)
2608 } else {
2609 for fn_decl in fns {
2610 user_fns << fn_decl
2611 }
2612 }
2613 }
2614 // Bucket 3: user_fns → main → builtin → last file.
2615 if user_fns.len > 0 {
2616 mut target_idx := find_flat_file_idx_by_mod(&out.flat, 'main')
2617 if target_idx == -1 {
2618 target_idx = find_flat_file_idx_by_mod(&out.flat, 'builtin')
2619 }
2620 if target_idx == -1 && out.flat.files.len > 0 {
2621 target_idx = out.flat.files.len - 1
2622 }
2623 if target_idx >= 0 {
2624 mut ids := []ast.FlatNodeId{cap: user_fns.len}
2625 for fn_decl in user_fns {
2626 ids << out.emit_stmt(fn_decl)
2627 }
2628 out.append_file_stmts(target_idx, ids)
2629 }
2630 }
2631}
2632
2633// post_pass runs the sequential post-pass: injects runtime const init fns, generated functions,
2634// test main, live reload, and propagates types.
2635pub fn (mut t Transformer) post_pass(mut result []ast.File) {
2636 t.post_pass_files_with_generated_parts(mut result, none)
2637 t.apply_post_pass_tail(result)
2638}
2639
2640// post_pass_files_with_generated_parts runs the file-mutating post-pass steps.
2641pub fn (mut t Transformer) post_pass_files_with_generated_parts(mut result []ast.File, generated_parts ?GeneratedFnsParts) {
2642 if !t.is_eval_backend() {
2643 t.inject_runtime_const_init_fns(mut result)
2644 }
2645 if t.needed_embed_file_helper {
2646 t.inject_embed_file_helper(mut result)
2647 }
2648 // Generate auto helper functions. Split into two groups:
2649 // 1. Core functions (for standard types like string, int, etc.) go in the builtin
2650 // module file because cached builtin.o/vlib.o reference them. They must always
2651 // be available regardless of which program is being compiled.
2652 // 2. User-type functions (e.g. Array_Test2_str) go in the main module file because
2653 // they would pollute the cache and cause undefined symbol errors when a different
2654 // program reuses the same cache.
2655 if parts := generated_parts {
2656 inject_generated_fns_to_files(mut result, parts)
2657 } else {
2658 if parts := t.generated_fns_parts(result) {
2659 inject_generated_fns_to_files(mut result, parts)
2660 }
2661 }
2662 if t.pref == unsafe { nil } || t.pref.backend != .cleanc {
2663 t.inject_test_main(mut result)
2664 }
2665 if !t.is_eval_backend() {
2666 t.inject_main_runtime_const_init_calls(mut result)
2667 }
2668 if t.is_native_be {
2669 t.inject_live_reload(mut result)
2670 }
2671}
2672
2673// post_pass_to_flat is the FlatBuilder-side driver counterpart to
2674// `post_pass`. Runs the six file-mutating steps that legacy `post_pass`
2675// performs, in identical order, on a `FlatBuilder` instead of a legacy
2676// `[]ast.File`. Closes the post_pass port arc opened in s150 and
2677// completed in s151/s152/s153/s155/s159/s160.
2678//
2679// Order mirrors legacy `post_pass`:
2680// 1. `inject_runtime_const_init_fns_to_flat` (skip on eval backend)
2681// 2. `inject_embed_file_helper_to_flat` (gated by `needed_embed_file_helper`)
2682// 3. `inject_generated_fns_to_flat` (gated by `generated_parts != none`)
2683// 4. `inject_test_main_to_flat` (skip on cleanc backend)
2684// 5. `inject_main_runtime_const_init_to_flat` (skip on eval backend)
2685// 6. `inject_live_reload_to_flat` (only on native backend)
2686//
2687// `generated_parts` is computed by the caller via `t.generated_fns_parts`
2688// against whatever file source it has (legacy `[]ast.File` today). Once
2689// `generated_fns_parts_from_flat` lands, callers can drop the legacy file
2690// list entirely.
2691//
2692// The non-file post_pass tail (synth_types accumulator, fn_scopes
2693// propagation, `propagate_types`) is NOT included here — it operates on
2694// `t.env` and legacy `[]ast.File` respectively, not on `out`, so callers
2695// keep running it after this driver returns. `propagate_types` in
2696// particular still consumes `[]ast.File` and is already gated to skip on
2697// the arm64 backend in legacy `post_pass`.
2698pub fn (mut t Transformer) post_pass_to_flat(mut out ast.FlatBuilder, generated_parts ?GeneratedFnsParts) {
2699 if !t.is_eval_backend() {
2700 t.inject_runtime_const_init_fns_to_flat(mut out)
2701 }
2702 if t.needed_embed_file_helper {
2703 t.inject_embed_file_helper_to_flat(mut out)
2704 }
2705 if parts := generated_parts {
2706 t.inject_generated_fns_to_flat(mut out, parts)
2707 }
2708 if t.pref == unsafe { nil } || t.pref.backend != .cleanc {
2709 t.inject_test_main_to_flat(mut out)
2710 }
2711 if !t.is_eval_backend() {
2712 t.inject_main_runtime_const_init_to_flat(mut out)
2713 }
2714 if t.is_native_be {
2715 t.inject_live_reload_to_flat(mut out)
2716 }
2717}
2718
2719// apply_post_pass_tail runs the trailing portion of legacy `post_pass`
2720// that operates on `t.env` and `[]ast.File` rather than on a FlatBuilder:
2721//
2722// 1. Apply accumulated `synth_types` to the environment (via
2723// `t.env.set_expr_type`).
2724// 2. Push `cached_fn_scopes` back to `t.env.fn_scopes`.
2725// 3. Run `propagate_types(files)` for non-arm64 backends.
2726//
2727// These three steps are NOT part of `post_pass_to_flat` because they don't
2728// touch the flat output. They are factored here so any caller routing
2729// through `post_pass_to_flat` (s162's `transform_files_to_flat_via_driver`
2730// and s163's parallel counterpart) can share a single tail implementation
2731// instead of duplicating private-field access at each call site.
2732pub fn (mut t Transformer) apply_post_pass_tail(files []ast.File) {
2733 for id, typ in t.synth_types {
2734 t.env.set_expr_type(id, typ)
2735 }
2736 lock t.env.fn_scopes {
2737 fn_scope_keys := t.cached_fn_scopes.keys()
2738 for k in fn_scope_keys {
2739 v := t.cached_fn_scopes[k] or { continue }
2740 t.env.fn_scopes[k] = v
2741 }
2742 }
2743 if t.pref == unsafe { nil } || t.pref.backend != .arm64 {
2744 t.propagate_types(files)
2745 }
2746}
2747
2748// apply_post_pass_tail_from_flat is the FlatAst-input counterpart to
2749// `apply_post_pass_tail`. Same three steps (synth_types accumulator →
2750// `t.env.set_expr_type`, `cached_fn_scopes` propagation → `t.env.fn_scopes`,
2751// `propagate_types_from_flat(flat)` for non-arm64 backends), only the third
2752// step's input changes from `[]ast.File` to `&FlatAst`. Steps 1+2 don't
2753// touch the file/flat input at all — they only mutate `t.env` from
2754// pre-accumulated state on `t`. Lets `_via_driver` wedges drop the
2755// `apply_post_pass_tail(result)` call site (last `[]ast.File` consumer in
2756// the post_pass tail) in favour of `apply_post_pass_tail_from_flat(&builder.flat)`.
2757pub fn (mut t Transformer) apply_post_pass_tail_from_flat(flat &ast.FlatAst) {
2758 for id, typ in t.synth_types {
2759 t.env.set_expr_type(id, typ)
2760 }
2761 lock t.env.fn_scopes {
2762 fn_scope_keys := t.cached_fn_scopes.keys()
2763 for k in fn_scope_keys {
2764 v := t.cached_fn_scopes[k] or { continue }
2765 t.env.fn_scopes[k] = v
2766 }
2767 }
2768 if t.pref == unsafe { nil } || t.pref.backend != .arm64 {
2769 t.propagate_types_from_flat(flat)
2770 }
2771}
2772
2773fn embed_file_helper_receiver() ast.Parameter {
2774 return ast.Parameter{
2775 name: 'ed'
2776 typ: embed_file_helper_type_expr(token.Pos{})
2777 }
2778}
2779
2780fn embed_file_helper_selector(field_name string) ast.Expr {
2781 return ast.Expr(ast.SelectorExpr{
2782 lhs: ast.Expr(ast.Ident{
2783 name: 'ed'
2784 })
2785 rhs: ast.Ident{
2786 name: field_name
2787 }
2788 })
2789}
2790
2791fn embed_file_helper_method(name string, return_type ast.Expr, body []ast.Stmt) ast.Stmt {
2792 return ast.Stmt(ast.FnDecl{
2793 is_public: true
2794 is_method: true
2795 receiver: embed_file_helper_receiver()
2796 name: name
2797 typ: ast.FnType{
2798 return_type: return_type
2799 }
2800 stmts: body
2801 })
2802}
2803
2804fn embed_file_helper_stmts() []ast.Stmt {
2805 return [
2806 ast.Stmt(ast.StructDecl{
2807 is_public: true
2808 name: embed_file_helper_type_name
2809 fields: [
2810 ast.FieldDecl{
2811 name: '_data'
2812 typ: ast.Expr(ast.Ident{
2813 name: 'string'
2814 })
2815 },
2816 ast.FieldDecl{
2817 name: 'len'
2818 typ: ast.Expr(ast.Ident{
2819 name: 'int'
2820 })
2821 },
2822 ast.FieldDecl{
2823 name: 'path'
2824 typ: ast.Expr(ast.Ident{
2825 name: 'string'
2826 })
2827 },
2828 ast.FieldDecl{
2829 name: 'apath'
2830 typ: ast.Expr(ast.Ident{
2831 name: 'string'
2832 })
2833 },
2834 ]
2835 }),
2836 embed_file_helper_method('to_string', ast.Expr(ast.Ident{
2837 name: 'string'
2838 }), [
2839 ast.Stmt(ast.ReturnStmt{
2840 exprs: [embed_file_helper_selector('_data')]
2841 }),
2842 ]),
2843 embed_file_helper_method('to_bytes', ast.Expr(ast.Type(ast.ArrayType{
2844 elem_type: ast.Expr(ast.Ident{
2845 name: 'u8'
2846 })
2847 })), [
2848 ast.Stmt(ast.ReturnStmt{
2849 exprs: [
2850 ast.Expr(ast.CallExpr{
2851 lhs: ast.Expr(ast.SelectorExpr{
2852 lhs: embed_file_helper_selector('_data')
2853 rhs: ast.Ident{
2854 name: 'bytes'
2855 }
2856 })
2857 }),
2858 ]
2859 }),
2860 ]),
2861 embed_file_helper_method('data', ast.Expr(ast.PrefixExpr{
2862 op: .amp
2863 expr: ast.Expr(ast.Ident{
2864 name: 'u8'
2865 })
2866 }), [
2867 ast.Stmt(ast.ReturnStmt{
2868 exprs: [
2869 ast.Expr(ast.SelectorExpr{
2870 lhs: embed_file_helper_selector('_data')
2871 rhs: ast.Ident{
2872 name: 'str'
2873 }
2874 }),
2875 ]
2876 }),
2877 ]),
2878 embed_file_helper_method('free', ast.empty_expr, []ast.Stmt{}),
2879 embed_file_helper_method('str', ast.Expr(ast.Ident{
2880 name: 'string'
2881 }), [
2882 ast.Stmt(ast.ReturnStmt{
2883 exprs: [
2884 embed_file_helper_selector('path'),
2885 ]
2886 }),
2887 ]),
2888 ]
2889}
2890
2891// EmbedFileHelperParts is the pure-computation bundle produced by
2892// `inject_embed_file_helper_parts`. `builtin_idx` is the index of the
2893// `builtin` module file in the caller's `[]ast.File`; `extra_stmts` is
2894// the helper-decl set (StructDecl + 5 helper FnDecls) that should be
2895// appended to that file's stmts.
2896pub struct EmbedFileHelperParts {
2897pub:
2898 builtin_idx int
2899 extra_stmts []ast.Stmt
2900}
2901
2902// inject_embed_file_helper_parts is the pure-computation extraction of
2903// the pre-splice state previously inline in `inject_embed_file_helper`.
2904// Locates the `builtin` module file (returns `none` if not present),
2905// scans its existing stmts for a `StructDecl` already named
2906// `embed_file_helper_type_name` (returns `none` if the helper is
2907// already injected — re-entry guard), and otherwise pairs the file
2908// index with a fresh `embed_file_helper_stmts()` payload.
2909//
2910// Does NOT mutate `files` or any caller state. Caller decides whether
2911// to splice `extra_stmts` into a legacy `[]ast.File` (via
2912// `inject_embed_file_helper`) or into a `FlatBuilder` (future
2913// `post_pass_to_flat`).
2914//
2915// Sixth Phase 5 (post_pass port) `_parts` extraction (closes the
2916// "every post_pass step has a `_parts` handle" symmetry — sibling to
2917// s144/s145/s146/s147/s148). Bit-equal: identical builtin-file lookup
2918// + identical re-entry guard + identical `embed_file_helper_stmts()`
2919// payload.
2920pub fn (mut t Transformer) inject_embed_file_helper_parts(files []ast.File) ?EmbedFileHelperParts {
2921 mut builtin_idx := -1
2922 for i, file in files {
2923 if file.mod == 'builtin' {
2924 builtin_idx = i
2925 break
2926 }
2927 }
2928 if builtin_idx < 0 {
2929 return none
2930 }
2931 file := files[builtin_idx]
2932 for stmt in file.stmts {
2933 if stmt is ast.StructDecl && stmt.name == embed_file_helper_type_name {
2934 return none
2935 }
2936 }
2937 return EmbedFileHelperParts{
2938 builtin_idx: builtin_idx
2939 extra_stmts: embed_file_helper_stmts()
2940 }
2941}
2942
2943fn (mut t Transformer) inject_embed_file_helper(mut result []ast.File) {
2944 parts := t.inject_embed_file_helper_parts(result) or { return }
2945 file := result[parts.builtin_idx]
2946 mut new_stmts := []ast.Stmt{cap: file.stmts.len + parts.extra_stmts.len}
2947 for stmt in file.stmts {
2948 new_stmts << stmt
2949 }
2950 for stmt in parts.extra_stmts {
2951 new_stmts << stmt
2952 }
2953 result[parts.builtin_idx] = ast.File{
2954 attributes: file.attributes
2955 mod: file.mod
2956 name: file.name
2957 stmts: new_stmts
2958 imports: file.imports
2959 }
2960}
2961
2962// inject_embed_file_helper_parts_from_flat is the flat-cursor sibling of
2963// `inject_embed_file_helper_parts`. Walks `flat.files` for the `builtin`
2964// module entry and scans its stmts via cursor for an existing StructDecl
2965// named `embed_file_helper_type_name` (re-entry guard). Returns the same
2966// `EmbedFileHelperParts` shape; the `builtin_idx` is into `flat.files`
2967// (which the legacy callers' `[]ast.File` and the FlatBuilder's
2968// `flat.files` share by construction).
2969//
2970// First flat-aware port of a post_pass `_parts` helper (s151, opens the
2971// per-step flat-aware sweep). Pinned by `flat_append_file_stmts_test.v`'s
2972// signature-parity invariant when wired in via `inject_embed_file_helper_to_flat`.
2973pub fn (mut t Transformer) inject_embed_file_helper_parts_from_flat(flat &ast.FlatAst) ?EmbedFileHelperParts {
2974 mut builtin_idx := -1
2975 for i in 0 .. flat.files.len {
2976 if flat.string_at(flat.files[i].mod_idx) == 'builtin' {
2977 builtin_idx = i
2978 break
2979 }
2980 }
2981 if builtin_idx < 0 {
2982 return none
2983 }
2984 stmts := flat.file_cursor(builtin_idx).stmts()
2985 for i in 0 .. stmts.len() {
2986 c := stmts.at(i)
2987 if c.kind() == .stmt_struct_decl && c.name() == embed_file_helper_type_name {
2988 return none
2989 }
2990 }
2991 return EmbedFileHelperParts{
2992 builtin_idx: builtin_idx
2993 extra_stmts: embed_file_helper_stmts()
2994 }
2995}
2996
2997// inject_embed_file_helper_to_flat is the FlatBuilder-side splice
2998// counterpart to `inject_embed_file_helper` (legacy `[]ast.File` mutator).
2999// Emits each `parts.extra_stmts` into `out` via `out.emit_stmt`, then
3000// folds the resulting stmt ids into the file root via
3001// `out.append_file_stmts(builtin_idx, extra_ids)` (s150's seed primitive).
3002//
3003// Bit-equal w.r.t. `signature()` to running legacy
3004// `inject_embed_file_helper(mut files)` followed by
3005// `ast.flatten_files(files)`: same builtin-file routing, same helper
3006// stmt payload, same final file root edges (attrs / imports unchanged,
3007// stmts list extended by the same stmt ids).
3008pub fn (mut t Transformer) inject_embed_file_helper_to_flat(mut out ast.FlatBuilder) {
3009 parts := t.inject_embed_file_helper_parts_from_flat(&out.flat) or { return }
3010 mut extra_ids := []ast.FlatNodeId{cap: parts.extra_stmts.len}
3011 for stmt in parts.extra_stmts {
3012 extra_ids << out.emit_stmt(stmt)
3013 }
3014 out.append_file_stmts(parts.builtin_idx, extra_ids)
3015}
3016
3017// transform_files transforms all files and returns transformed copies
3018pub fn (mut t Transformer) transform_files(files []ast.File) []ast.File {
3019 timing := os.getenv('V2_TTIME') != ''
3020 mut sw := time.new_stopwatch()
3021 t_print_mem('enter')
3022 t.pre_pass(files)
3023 t_print_mem('after pre_pass')
3024 if timing {
3025 eprintln(' [ttime] sequential pre_pass: ${sw.elapsed().milliseconds()}ms')
3026 sw = time.new_stopwatch()
3027 }
3028 files_to_transform := t.prepare_files_for_transform(files)
3029 t_print_mem('after prepare/monomorphize')
3030 if timing {
3031 eprintln(' [ttime] sequential prepare: ${sw.elapsed().milliseconds()}ms')
3032 sw = time.new_stopwatch()
3033 }
3034 mut result := []ast.File{cap: files_to_transform.len}
3035 for file in files_to_transform {
3036 result << t.transform_file(file)
3037 }
3038 t_print_mem('after per-file loop')
3039 if timing {
3040 eprintln(' [ttime] sequential per-file: ${sw.elapsed().milliseconds()}ms')
3041 sw = time.new_stopwatch()
3042 }
3043 t.post_pass(mut result)
3044 t_print_mem('after post_pass')
3045 if timing {
3046 eprintln(' [ttime] sequential post_pass: ${sw.elapsed().milliseconds()}ms')
3047 }
3048 return result
3049}
3050
3051// transform_files_from_flat drives the same per-file transform pipeline
3052// but seeds the pre-pass accumulators from FlatAst rather than from the
3053// legacy ast.File list.
3054//
3055// Monomorphization needs the full file set up front (it builds a
3056// cross-file generic-decl index), so flat input is prepared once and then
3057// rehydrated one file at a time when `files` is empty.
3058pub fn (mut t Transformer) transform_files_from_flat(flat &ast.FlatAst, files []ast.File) []ast.File {
3059 mut result := t.transform_files_from_flat_no_post_pass(flat, files)
3060 t.post_pass(mut result)
3061 return result
3062}
3063
3064// transform_files_from_flat_no_post_pass is the extraction of
3065// `transform_files_from_flat`'s per-file transform loop, without the
3066// trailing `post_pass` call. Both `transform_files_from_flat` (legacy
3067// wrapper, calls `post_pass` after) and
3068// `transform_files_to_flat_via_driver` (new flat-output path, calls
3069// `post_pass_to_flat` on the flattened output instead) call this helper.
3070fn (mut t Transformer) transform_files_from_flat_no_post_pass(flat &ast.FlatAst, files []ast.File) []ast.File {
3071 timing := os.getenv('V2_TTIME') != ''
3072 file_timing := os.getenv('V2_TTIME_FILES') != ''
3073 mut sw := time.new_stopwatch()
3074 t_print_mem('flat no-post enter')
3075 t.pre_pass_from_flat(flat)
3076 t_print_mem('flat no-post after pre_pass')
3077 if timing {
3078 eprintln(' [ttime] flat no-post pre_pass: ${sw.elapsed().milliseconds()}ms')
3079 sw = time.new_stopwatch()
3080 }
3081 if t.needs_full_files_for_transform() {
3082 if files.len == 0 {
3083 extra_stmts := t.prepare_flat_for_transform(flat)
3084 t_print_mem('flat no-post after prepare/monomorphize')
3085 if timing {
3086 eprintln(' [ttime] flat no-post prepare: ${sw.elapsed().milliseconds()}ms')
3087 sw = time.new_stopwatch()
3088 }
3089 mut result := []ast.File{cap: flat.files.len}
3090 for i in 0 .. flat.files.len {
3091 mut fsw := time.new_stopwatch()
3092 src_file := prepared_flat_file_for_transform(flat, extra_stmts, i) or { continue }
3093 result << t.transform_file(src_file)
3094 if file_timing {
3095 eprintln(' [ttime-file] flat no-post: ${src_file.name} ${fsw.elapsed().milliseconds()}ms')
3096 }
3097 }
3098 t_print_mem('flat no-post after per-file loop')
3099 if timing {
3100 eprintln(' [ttime] flat no-post per-file: ${sw.elapsed().milliseconds()}ms')
3101 }
3102 return result
3103 }
3104 files_to_transform := t.prepare_files_for_transform(files)
3105 t_print_mem('flat no-post after prepare/monomorphize')
3106 if timing {
3107 eprintln(' [ttime] flat no-post prepare: ${sw.elapsed().milliseconds()}ms')
3108 sw = time.new_stopwatch()
3109 }
3110 mut result := []ast.File{cap: files_to_transform.len}
3111 for file in files_to_transform {
3112 mut fsw := time.new_stopwatch()
3113 result << t.transform_file(file)
3114 if file_timing {
3115 eprintln(' [ttime-file] flat no-post: ${file.name} ${fsw.elapsed().milliseconds()}ms')
3116 }
3117 }
3118 t_print_mem('flat no-post after per-file loop')
3119 if timing {
3120 eprintln(' [ttime] flat no-post per-file: ${sw.elapsed().milliseconds()}ms')
3121 }
3122 return result
3123 }
3124 if files.len > 0 {
3125 mut result := []ast.File{cap: files.len}
3126 for file in files {
3127 mut fsw := time.new_stopwatch()
3128 result << t.transform_file(file)
3129 if file_timing {
3130 eprintln(' [ttime-file] flat no-post: ${file.name} ${fsw.elapsed().milliseconds()}ms')
3131 }
3132 }
3133 t_print_mem('flat no-post after per-file loop')
3134 if timing {
3135 eprintln(' [ttime] flat no-post per-file: ${sw.elapsed().milliseconds()}ms')
3136 }
3137 return result
3138 }
3139 mut result := []ast.File{cap: flat.files.len}
3140 for i in 0 .. flat.files.len {
3141 mut fsw := time.new_stopwatch()
3142 src_file := flat_file_for_transform(flat, i, []ast.Stmt{}) or { continue }
3143 result << t.transform_file(src_file)
3144 if file_timing {
3145 eprintln(' [ttime-file] flat no-post: ${src_file.name} ${fsw.elapsed().milliseconds()}ms')
3146 }
3147 }
3148 t_print_mem('flat no-post after per-file loop')
3149 if timing {
3150 eprintln(' [ttime] flat no-post per-file: ${sw.elapsed().milliseconds()}ms')
3151 }
3152 return result
3153}
3154
3155// transform_files_to_flat is the legacy-compatible flat-output entry point.
3156// It wraps transform_files_from_flat + ast.flatten_files and returns both the
3157// FlatAst and transformed files for callers that still need []ast.File.
3158//
3159// NO PRODUCTION CALLERS remain: the builder uses transform_flat_to_flat_direct
3160// (sequential) / transform_files_parallel_flat_direct (parallel) for every
3161// backend, and .v/eval rehydrate via flat.to_files() at the codegen boundary.
3162// Kept only as the legacy parity reference for the flat-diff test suite; delete
3163// together with the remaining decode-fallback arms once the cursor-native
3164// transform is complete.
3165pub fn (mut t Transformer) transform_files_to_flat(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File) {
3166 result := t.transform_files_from_flat(flat, files)
3167 return ast.flatten_files(result), result
3168}
3169
3170// transform_files_to_flat_via_driver is the post_pass_to_flat-based
3171// counterpart to `transform_files_to_flat`. Same external shape (returns
3172// `(ast.FlatAst, []ast.File)`) but uses the s161 driver instead of the
3173// `legacy post_pass + flatten_files` boundary:
3174//
3175// 1. Per-file transform via `transform_files_from_flat_no_post_pass`
3176// (skips legacy `post_pass`).
3177// 2. Compute `generated_fns_parts` against the un-post_pass'd result
3178// (the parts helper walks `[]ast.File` to determine module routing
3179// for generated fns; the file set is the same as legacy at this point).
3180// 3. Flatten the un-post_pass'd files into a fresh FlatBuilder.
3181// 4. Run `post_pass_to_flat(mut builder, generated_parts)` — the s161
3182// driver appends/prepends/replaces stmts on the flat directly.
3183// 5. Apply the non-file post_pass tail (synth_types accumulator,
3184// fn_scopes propagation, `propagate_types`) which mutates `t.env`
3185// and `result` but not the flat output.
3186//
3187// Bit-equivalent to legacy in TREE STRUCTURE (each file's stmts list
3188// holds the same content) but NOT bit-equal in `signature()`: the
3189// `.file.extra` slot stores `intern(mod)` as a raw intern index, and the
3190// two paths intern strings in different orders (legacy interns
3191// post-pass-added strings DURING `flatten_files` walk, leaving later
3192// files' mod strings at large indices; new path interns all bare files
3193// first then post-pass-added strings AFTER, leaving later files' mod
3194// strings at small indices). Compare via per-file `subtree_signature` of
3195// the stmts list (file root edge 2) to assert structural parity.
3196//
3197// Memory: this avoids the legacy post_pass + flatten_files boundary but still
3198// returns []ast.File for compatibility consumers.
3199//
3200// NO PRODUCTION CALLERS remain (every backend transforms flat-direct; .v/eval
3201// rehydrate at the codegen boundary). Kept only as the legacy parity reference
3202// for tests; delete once the cursor-native transform is complete.
3203pub fn (mut t Transformer) transform_files_to_flat_via_driver(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File) {
3204 timing := os.getenv('V2_TTIME') != ''
3205 mut sw := time.new_stopwatch()
3206 mut result := t.transform_files_from_flat_no_post_pass(flat, files)
3207 t_print_mem('flat driver after no-post')
3208 if timing {
3209 eprintln(' [ttime] flat driver no-post: ${sw.elapsed().milliseconds()}ms')
3210 sw = time.new_stopwatch()
3211 }
3212 mut builder := ast.new_flat_builder()
3213 for file in result {
3214 builder.append_file(file)
3215 }
3216 t_print_mem('flat driver after append')
3217 if timing {
3218 eprintln(' [ttime] flat driver append: ${sw.elapsed().milliseconds()}ms')
3219 sw = time.new_stopwatch()
3220 }
3221 // Compute parts against the freshly-appended flat (s166): no longer
3222 // needs `result []ast.File` for explicit_str / module routing — both
3223 // `explicit_str_method_fn_names_from_flat` (s165) and
3224 // `generated_fn_module_from_flat` (s164) walk `builder.flat` directly.
3225 generated_parts := t.generated_fns_parts_from_flat(&builder.flat)
3226 t_print_mem('flat driver after generated parts')
3227 if timing {
3228 eprintln(' [ttime] flat driver generated parts: ${sw.elapsed().milliseconds()}ms')
3229 sw = time.new_stopwatch()
3230 }
3231 t.post_pass_to_flat(mut builder, generated_parts)
3232 t_print_mem('flat driver after post_pass_to_flat')
3233 if timing {
3234 eprintln(' [ttime] flat driver post_pass_to_flat: ${sw.elapsed().milliseconds()}ms')
3235 sw = time.new_stopwatch()
3236 }
3237 t.post_pass_files_with_generated_parts(mut result, generated_parts)
3238 t_print_mem('flat driver after files generated parts')
3239 if timing {
3240 eprintln(' [ttime] flat driver files generated parts: ${sw.elapsed().milliseconds()}ms')
3241 sw = time.new_stopwatch()
3242 }
3243 // The compatibility files now receive the same file-mutating post-pass
3244 // edits as the flat output, so run the non-file tail on those files for
3245 // downstream legacy consumers.
3246 t.apply_post_pass_tail(result)
3247 t_print_mem('flat driver after post_pass tail')
3248 if timing {
3249 eprintln(' [ttime] flat driver post_pass tail: ${sw.elapsed().milliseconds()}ms')
3250 }
3251 return builder.flat, result
3252}
3253
3254// transform_files_to_flat_direct transforms `files` into a FlatAst without
3255// collecting the transformed legacy []ast.File result. The input still goes
3256// through the existing whole-program generic preparation, but each prepared file
3257// is emitted through transform_file_to_flat and is then immediately consumed by
3258// the FlatBuilder. This is the low-memory path used by native backends that can
3259// consume post-transform FlatAst directly.
3260pub fn (mut t Transformer) transform_files_to_flat_direct(files []ast.File) ast.FlatAst {
3261 timing := os.getenv('V2_TTIME') != ''
3262 mut sw := time.new_stopwatch()
3263 t_print_mem('enter')
3264 t.pre_pass(files)
3265 t_print_mem('after pre_pass')
3266 if timing {
3267 eprintln(' [ttime] flat direct pre_pass: ${sw.elapsed().milliseconds()}ms')
3268 sw = time.new_stopwatch()
3269 }
3270 files_to_transform := t.prepare_files_for_transform(files)
3271 t_print_mem('after prepare/monomorphize')
3272 if timing {
3273 eprintln(' [ttime] flat direct prepare: ${sw.elapsed().milliseconds()}ms')
3274 sw = time.new_stopwatch()
3275 }
3276 mut builder := new_transform_output_flat_builder(files_to_transform)
3277 for file in files_to_transform {
3278 t.transform_file_to_flat(t.file_without_open_generic_fn_decls(file), mut builder)
3279 }
3280 t_print_mem('after per-file flat loop')
3281 if timing {
3282 eprintln(' [ttime] flat direct per-file: ${sw.elapsed().milliseconds()}ms')
3283 sw = time.new_stopwatch()
3284 }
3285 generated_parts := t.generated_fns_parts_from_flat(&builder.flat)
3286 t.post_pass_to_flat(mut builder, generated_parts)
3287 t.apply_post_pass_tail_from_flat(&builder.flat)
3288 t_print_mem('after post_pass')
3289 if timing {
3290 eprintln(' [ttime] flat direct post_pass: ${sw.elapsed().milliseconds()}ms')
3291 }
3292 return builder.flat
3293}
3294
3295// transform_flat_to_flat_direct transforms flat input into flat output without
3296// collecting a whole legacy []ast.File input or the transformed legacy output.
3297// Generic monomorphization is append-only, so flat preparation keeps cloned
3298// declarations in a per-file side table and the per-file loop reads top-level
3299// statements from cursors, decoding only each statement handed to legacy
3300// lowering helpers.
3301pub fn (mut t Transformer) transform_flat_to_flat_direct(flat &ast.FlatAst, files []ast.File) ast.FlatAst {
3302 if files.len > 0 {
3303 return t.transform_files_to_flat_direct(files)
3304 }
3305 timing := os.getenv('V2_TTIME') != ''
3306 mut sw := time.new_stopwatch()
3307 t_print_mem('enter')
3308 t.pre_pass_from_flat(flat)
3309 t_print_mem('after pre_pass')
3310 if timing {
3311 eprintln(' [ttime] flat input direct pre_pass: ${sw.elapsed().milliseconds()}ms')
3312 sw = time.new_stopwatch()
3313 }
3314 extra_stmts := t.prepare_flat_for_transform(flat)
3315 t_print_mem('after prepare/monomorphize')
3316 if timing {
3317 eprintln(' [ttime] flat input direct prepare: ${sw.elapsed().milliseconds()}ms')
3318 sw = time.new_stopwatch()
3319 }
3320 mut builder := new_transform_output_flat_builder_from_flat(flat)
3321 for i in 0 .. flat.files.len {
3322 extra := extra_stmts[i] or { []ast.Stmt{} }
3323 t.transform_flat_file_index_to_flat(flat, i, extra, mut builder)
3324 }
3325 t_print_mem('after per-file flat loop')
3326 if timing {
3327 eprintln(' [ttime] flat input direct per-file: ${sw.elapsed().milliseconds()}ms')
3328 sw = time.new_stopwatch()
3329 }
3330 generated_parts := t.generated_fns_parts_from_flat(&builder.flat)
3331 t.post_pass_to_flat(mut builder, generated_parts)
3332 t.apply_post_pass_tail_from_flat(&builder.flat)
3333 t_print_mem('after post_pass')
3334 if timing {
3335 eprintln(' [ttime] flat input direct post_pass: ${sw.elapsed().milliseconds()}ms')
3336 }
3337 t.dump_flat_fallback_stats()
3338 return builder.flat
3339}
3340
3341fn (t &Transformer) file_without_open_generic_fn_decls(file ast.File) ast.File {
3342 mut has_open_generic_fn := false
3343 for stmt in file.stmts {
3344 if stmt is ast.FnDecl && t.omit_backend_generic_decl(stmt) {
3345 has_open_generic_fn = true
3346 break
3347 }
3348 }
3349 if !has_open_generic_fn {
3350 return file
3351 }
3352 mut stmts := []ast.Stmt{cap: file.stmts.len}
3353 for stmt in file.stmts {
3354 if stmt is ast.FnDecl && t.omit_backend_generic_decl(stmt) {
3355 continue
3356 }
3357 stmts << stmt
3358 }
3359 return ast.File{
3360 attributes: file.attributes
3361 mod: file.mod
3362 name: file.name
3363 stmts: stmts
3364 imports: file.imports
3365 selector_names: file.selector_names
3366 }
3367}
3368
3369// count_flat_fallback records one legacy-decode fallback hit for a cursor
3370// transform arm. Used to prioritize the remaining flat-native migration.
3371@[inline]
3372fn (mut t Transformer) count_flat_fallback(key string) {
3373 t.flat_fb_counts[key]++
3374}
3375
3376// dump_flat_fallback_stats prints the per-arm legacy-decode fallback counts
3377// recorded during the transform, gated on V2_FLAT_FB_STATS.
3378fn (t &Transformer) dump_flat_fallback_stats() {
3379 if os.getenv('V2_FLAT_FB_STATS') == '' {
3380 return
3381 }
3382 mut keys := t.flat_fb_counts.keys()
3383 keys.sort_with_compare(fn [t] (a &string, b &string) int {
3384 return t.flat_fb_counts[*b] - t.flat_fb_counts[*a]
3385 })
3386 mut total := 0
3387 for k in keys {
3388 total += t.flat_fb_counts[k]
3389 }
3390 eprintln('[flat-fb] total legacy-decode fallbacks: ${total}')
3391 for k in keys {
3392 eprintln('[flat-fb] ${t.flat_fb_counts[k]:8} ${k}')
3393 }
3394}
3395
3396fn new_transform_output_flat_builder(files []ast.File) ast.FlatBuilder {
3397 mut total_bytes := i64(0)
3398 for file in files {
3399 if file.name == '' || !os.exists(file.name) {
3400 continue
3401 }
3402 total_bytes += os.file_size(file.name)
3403 }
3404 nodes_cap, edges_cap, strings_cap := ast.arena_caps_for_bytes(total_bytes * 2)
3405 return ast.new_flat_builder_with_capacity(nodes_cap, edges_cap, strings_cap)
3406}
3407
3408fn new_transform_output_flat_builder_from_flat(flat &ast.FlatAst) ast.FlatBuilder {
3409 mut total_bytes := i64(0)
3410 for ff in flat.files {
3411 name := flat.file_name(ff)
3412 if name == '' || !os.exists(name) {
3413 continue
3414 }
3415 total_bytes += os.file_size(name)
3416 }
3417 nodes_cap, edges_cap, strings_cap := ast.arena_caps_for_bytes(total_bytes * 2)
3418 return ast.new_flat_builder_with_capacity(nodes_cap, edges_cap, strings_cap)
3419}
3420
3421fn prepared_flat_file_for_transform(flat &ast.FlatAst, extra_stmts map[int][]ast.Stmt, fi int) ?ast.File {
3422 extra := extra_stmts[fi] or { []ast.Stmt{} }
3423 return flat_file_for_transform(flat, fi, extra)
3424}
3425
3426fn flat_file_for_transform(flat &ast.FlatAst, fi int, extra_stmts []ast.Stmt) ?ast.File {
3427 if fi < 0 || fi >= flat.files.len {
3428 return none
3429 }
3430 fc := flat.file_cursor(fi)
3431 stmt_list := fc.stmts()
3432 mut stmts := []ast.Stmt{cap: stmt_list.len() + extra_stmts.len}
3433 for i in 0 .. stmt_list.len() {
3434 stmts << stmt_list.at(i).stmt()
3435 }
3436 if extra_stmts.len > 0 {
3437 stmts << extra_stmts
3438 }
3439 return ast.File{
3440 name: fc.name()
3441 mod: fc.mod()
3442 selector_names: fc.selector_names()
3443 attributes: fc.attrs().attributes()
3444 imports: fc.imports().import_stmts()
3445 stmts: stmts
3446 }
3447}
3448
3449fn runtime_const_init_base_name(mod string) string {
3450 mut suffix := if mod == '' { 'main' } else { mod }
3451 suffix = suffix.replace('.', '_').replace('-', '_')
3452 return '__v_init_consts_${suffix}'
3453}
3454
3455fn runtime_const_init_call_name(mod string, fn_name string) string {
3456 if mod != '' && mod != 'main' && mod != 'builtin' {
3457 return '${mod}__${fn_name}'
3458 }
3459 return fn_name
3460}
3461
3462fn module_init_call_name(mod string) string {
3463 if mod != '' && mod != 'main' && mod != 'builtin' {
3464 return '${mod}__init'
3465 }
3466 return 'init'
3467}
3468
3469fn (t &Transformer) uses_minimal_windows_x64_runtime() bool {
3470 return t.pref != unsafe { nil } && t.pref.backend == .x64 && t.pref.get_effective_arch() == .x64
3471 && t.pref.target_os_or_host().to_lower() == 'windows'
3472}
3473
3474fn (t &Transformer) uses_minimal_linux_x64_runtime() bool {
3475 return t.pref != unsafe { nil } && t.pref.backend == .x64 && t.pref.get_effective_arch() == .x64
3476 && t.pref.target_os_or_host().to_lower() == 'linux'
3477}
3478
3479fn (t &Transformer) uses_minimal_x64_runtime() bool {
3480 return t.uses_minimal_windows_x64_runtime() || t.uses_minimal_linux_x64_runtime()
3481 || t.macos_tiny_candidate_graph
3482}
3483
3484fn (t &Transformer) collect_runtime_init_imports_from_if_expr(node ast.IfExpr, mut imports []ast.ImportStmt) {
3485 if t.eval_comptime_cond(node.cond) {
3486 t.collect_runtime_init_imports_from_stmts(node.stmts, mut imports)
3487 return
3488 }
3489 match node.else_expr {
3490 ast.IfExpr {
3491 if node.else_expr.cond is ast.EmptyExpr {
3492 t.collect_runtime_init_imports_from_stmts(node.else_expr.stmts, mut imports)
3493 } else {
3494 t.collect_runtime_init_imports_from_if_expr(node.else_expr, mut imports)
3495 }
3496 }
3497 else {}
3498 }
3499}
3500
3501fn (t &Transformer) collect_runtime_init_imports_from_stmts(stmts []ast.Stmt, mut imports []ast.ImportStmt) {
3502 for stmt in stmts {
3503 match stmt {
3504 ast.ImportStmt {
3505 imports << stmt
3506 }
3507 ast.ExprStmt {
3508 if stmt.expr is ast.ComptimeExpr && stmt.expr.expr is ast.IfExpr {
3509 t.collect_runtime_init_imports_from_if_expr(stmt.expr.expr, mut imports)
3510 }
3511 }
3512 else {}
3513 }
3514 }
3515}
3516
3517fn (t &Transformer) active_runtime_init_imports(file ast.File) []ast.ImportStmt {
3518 mut imports := file.imports.clone()
3519 t.collect_runtime_init_imports_from_stmts(file.stmts, mut imports)
3520 return imports
3521}
3522
3523fn runtime_init_available_modules(files []ast.File) map[string]bool {
3524 mut available := map[string]bool{}
3525 available[''] = true
3526 available['main'] = true
3527 for file in files {
3528 if file.mod != '' {
3529 available[file.mod] = true
3530 }
3531 }
3532 return available
3533}
3534
3535fn add_runtime_init_module_key(mut modules map[string]bool, name string, available map[string]bool) bool {
3536 if name == '' {
3537 return false
3538 }
3539 mut changed := false
3540 mut matched_available := false
3541 mut candidates := [name]
3542 if name.contains('.') {
3543 short_name := name.all_after_last('.')
3544 if short_name != '' {
3545 candidates << short_name
3546 }
3547 }
3548 for candidate in candidates {
3549 if candidate !in available {
3550 continue
3551 }
3552 matched_available = true
3553 if candidate !in modules {
3554 modules[candidate] = true
3555 changed = true
3556 }
3557 }
3558 if !matched_available && name !in modules {
3559 modules[name] = true
3560 changed = true
3561 }
3562 return changed
3563}
3564
3565fn (t &Transformer) imported_runtime_init_modules(files []ast.File) map[string]bool {
3566 mut modules := map[string]bool{}
3567 modules[''] = true
3568 available_modules := runtime_init_available_modules(files)
3569 add_runtime_init_module_key(mut modules, 'main', available_modules)
3570 mut changed := true
3571 for changed {
3572 changed = false
3573 for file in files {
3574 if file.mod !in modules {
3575 continue
3576 }
3577 for imp in t.active_runtime_init_imports(file) {
3578 changed = add_runtime_init_module_key(mut modules, imp.name, available_modules)
3579 || changed
3580 }
3581 }
3582 }
3583 return modules
3584}
3585
3586fn runtime_init_available_modules_from_flat(flat &ast.FlatAst) map[string]bool {
3587 mut available := map[string]bool{}
3588 available[''] = true
3589 available['main'] = true
3590 for ff in flat.files {
3591 mod_name := flat.file_mod(ff)
3592 if mod_name != '' {
3593 available[mod_name] = true
3594 }
3595 }
3596 return available
3597}
3598
3599fn (t &Transformer) collect_runtime_init_imports_from_if_expr_cursor(node ast.Cursor, mut imports []ast.ImportStmt) {
3600 if !node.is_valid() || node.kind() != .expr_if {
3601 return
3602 }
3603 cond := node.edge(0)
3604 if t.eval_comptime_cond_cursor(cond) {
3605 t.collect_runtime_init_imports_from_if_cursor_stmts(node, mut imports)
3606 return
3607 }
3608 else_expr := node.edge(1)
3609 if !else_expr.is_valid() || else_expr.kind() != .expr_if {
3610 return
3611 }
3612 else_cond := else_expr.edge(0)
3613 if !else_cond.is_valid() || else_cond.kind() == .expr_empty {
3614 t.collect_runtime_init_imports_from_if_cursor_stmts(else_expr, mut imports)
3615 return
3616 }
3617 t.collect_runtime_init_imports_from_if_expr_cursor(else_expr, mut imports)
3618}
3619
3620fn (t &Transformer) collect_runtime_init_imports_from_if_cursor_stmts(node ast.Cursor, mut imports []ast.ImportStmt) {
3621 for i in 2 .. node.edge_count() {
3622 t.collect_runtime_init_imports_from_stmt_cursor(node.edge(i), mut imports)
3623 }
3624}
3625
3626fn (t &Transformer) collect_runtime_init_imports_from_stmt_cursor(stmt ast.Cursor, mut imports []ast.ImportStmt) {
3627 if !stmt.is_valid() {
3628 return
3629 }
3630 match stmt.kind() {
3631 .stmt_import {
3632 imports << stmt.import_stmt()
3633 }
3634 .stmt_expr {
3635 expr := stmt.edge(0)
3636 if !expr.is_valid() || expr.kind() != .expr_comptime {
3637 return
3638 }
3639 inner := expr.edge(0)
3640 if inner.is_valid() && inner.kind() == .expr_if {
3641 t.collect_runtime_init_imports_from_if_expr_cursor(inner, mut imports)
3642 }
3643 }
3644 else {}
3645 }
3646}
3647
3648fn (t &Transformer) collect_runtime_init_imports_from_stmt_cursors(stmts ast.CursorList, mut imports []ast.ImportStmt) {
3649 for i in 0 .. stmts.len() {
3650 t.collect_runtime_init_imports_from_stmt_cursor(stmts.at(i), mut imports)
3651 }
3652}
3653
3654fn (t &Transformer) active_runtime_init_imports_from_flat_file(fc ast.FileCursor) []ast.ImportStmt {
3655 file_imports := fc.imports()
3656 mut imports := []ast.ImportStmt{cap: file_imports.len()}
3657