v / vlib / v2 / transformer / transformer.v
18649 lines · 17973 sloc · 536.61 KB
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
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 for i in 0 .. file_imports.len() {
3658 imp := file_imports.at(i)
3659 if imp.kind() == .stmt_import {
3660 imports << imp.import_stmt()
3661 }
3662 }
3663 t.collect_runtime_init_imports_from_stmt_cursors(fc.stmts(), mut imports)
3664 return imports
3665}
3666
3667fn (t &Transformer) imported_runtime_init_modules_from_flat(flat &ast.FlatAst) map[string]bool {
3668 mut modules := map[string]bool{}
3669 modules[''] = true
3670 available_modules := runtime_init_available_modules_from_flat(flat)
3671 add_runtime_init_module_key(mut modules, 'main', available_modules)
3672 mut changed := true
3673 for changed {
3674 changed = false
3675 for i in 0 .. flat.files.len {
3676 fc := flat.file_cursor(i)
3677 if fc.mod() !in modules {
3678 continue
3679 }
3680 for imp in t.active_runtime_init_imports_from_flat_file(fc) {
3681 changed = add_runtime_init_module_key(mut modules, imp.name, available_modules)
3682 || changed
3683 }
3684 }
3685 }
3686 return modules
3687}
3688
3689fn is_builtin_main_arg_global(mod string, name string) bool {
3690 return mod == 'builtin' && (name == 'g_main_argc' || name == 'g_main_argv')
3691}
3692
3693fn runtime_const_known_key(mod string, name string) string {
3694 return '${mod.len}:${mod}:${name}'
3695}
3696
3697fn (mut t Transformer) record_runtime_const_init(mod string, name string, expr ast.Expr) {
3698 if name == '' {
3699 return
3700 }
3701 known_key := runtime_const_known_key(mod, name)
3702 t.runtime_const_storage_known[known_key] = true
3703 if known_key in t.runtime_const_known {
3704 return
3705 }
3706 t.runtime_const_known[known_key] = true
3707 if mod !in t.runtime_const_inits_by_mod {
3708 t.runtime_const_modules << mod
3709 }
3710 mut inits := t.runtime_const_inits_by_mod[mod] or { []RuntimeConstInit{} }
3711 inits << RuntimeConstInit{
3712 name: name
3713 expr: expr
3714 }
3715 t.runtime_const_inits_by_mod[mod] = inits
3716}
3717
3718fn (mut t Transformer) record_runtime_const_init_cursor(mod string, name string, expr ast.Cursor) {
3719 if name == '' {
3720 return
3721 }
3722 known_key := runtime_const_known_key(mod, name)
3723 t.runtime_const_storage_known[known_key] = true
3724 if known_key in t.runtime_const_known {
3725 return
3726 }
3727 t.runtime_const_known[known_key] = true
3728 if mod !in t.runtime_const_inits_by_mod {
3729 t.runtime_const_modules << mod
3730 }
3731 mut inits := t.runtime_const_inits_by_mod[mod] or { []RuntimeConstInit{} }
3732 inits << RuntimeConstInit{
3733 name: name
3734 expr_cursor: expr
3735 }
3736 t.runtime_const_inits_by_mod[mod] = inits
3737}
3738
3739fn (t &Transformer) expr_depends_on_runtime_const(mod string, expr ast.Expr) bool {
3740 match expr {
3741 ast.Ident {
3742 key := runtime_const_known_key(mod, expr.name)
3743 return key in t.runtime_const_known || key in t.runtime_const_storage_known
3744 }
3745 ast.SelectorExpr {
3746 if t.expr_depends_on_runtime_const(mod, expr.lhs) {
3747 return true
3748 }
3749 if expr.lhs is ast.Ident {
3750 key := runtime_const_known_key(expr.lhs.name, expr.rhs.name)
3751 return key in t.runtime_const_known || key in t.runtime_const_storage_known
3752 }
3753 return false
3754 }
3755 ast.CallExpr {
3756 if t.expr_depends_on_runtime_const(mod, expr.lhs) {
3757 return true
3758 }
3759 for arg in expr.args {
3760 if t.expr_depends_on_runtime_const(mod, arg) {
3761 return true
3762 }
3763 }
3764 return false
3765 }
3766 ast.CallOrCastExpr {
3767 return t.expr_depends_on_runtime_const(mod, expr.lhs)
3768 || t.expr_depends_on_runtime_const(mod, expr.expr)
3769 }
3770 ast.CastExpr {
3771 return t.expr_depends_on_runtime_const(mod, expr.expr)
3772 }
3773 ast.ParenExpr {
3774 return t.expr_depends_on_runtime_const(mod, expr.expr)
3775 }
3776 ast.PrefixExpr {
3777 return t.expr_depends_on_runtime_const(mod, expr.expr)
3778 }
3779 ast.PostfixExpr {
3780 return t.expr_depends_on_runtime_const(mod, expr.expr)
3781 }
3782 ast.ModifierExpr {
3783 return t.expr_depends_on_runtime_const(mod, expr.expr)
3784 }
3785 ast.IndexExpr {
3786 return t.expr_depends_on_runtime_const(mod, expr.lhs)
3787 || t.expr_depends_on_runtime_const(mod, expr.expr)
3788 }
3789 ast.InfixExpr {
3790 return t.expr_depends_on_runtime_const(mod, expr.lhs)
3791 || t.expr_depends_on_runtime_const(mod, expr.rhs)
3792 }
3793 ast.IfExpr {
3794 if t.expr_depends_on_runtime_const(mod, expr.cond)
3795 || t.expr_depends_on_runtime_const(mod, expr.else_expr) {
3796 return true
3797 }
3798 for stmt in expr.stmts {
3799 if stmt is ast.ExprStmt && t.expr_depends_on_runtime_const(mod, stmt.expr) {
3800 return true
3801 }
3802 }
3803 return false
3804 }
3805 ast.OrExpr {
3806 if t.expr_depends_on_runtime_const(mod, expr.expr) {
3807 return true
3808 }
3809 for stmt in expr.stmts {
3810 if stmt is ast.ExprStmt && t.expr_depends_on_runtime_const(mod, stmt.expr) {
3811 return true
3812 }
3813 }
3814 return false
3815 }
3816 ast.StringInterLiteral {
3817 for inter in expr.inters {
3818 if t.expr_depends_on_runtime_const(mod, inter.expr)
3819 || t.expr_depends_on_runtime_const(mod, inter.format_expr) {
3820 return true
3821 }
3822 }
3823 return false
3824 }
3825 ast.ComptimeExpr {
3826 return t.expr_depends_on_runtime_const(mod, expr.expr)
3827 }
3828 ast.UnsafeExpr {
3829 for stmt in expr.stmts {
3830 if stmt is ast.ExprStmt && t.expr_depends_on_runtime_const(mod, stmt.expr) {
3831 return true
3832 }
3833 }
3834 return false
3835 }
3836 ast.ArrayInitExpr {
3837 for e in expr.exprs {
3838 if t.expr_depends_on_runtime_const(mod, e) {
3839 return true
3840 }
3841 }
3842 return (expr.init !is ast.EmptyExpr && t.expr_depends_on_runtime_const(mod, expr.init))
3843 || (expr.len !is ast.EmptyExpr && t.expr_depends_on_runtime_const(mod, expr.len))
3844 || (expr.cap !is ast.EmptyExpr && t.expr_depends_on_runtime_const(mod, expr.cap))
3845 }
3846 ast.InitExpr {
3847 for field in expr.fields {
3848 if t.expr_depends_on_runtime_const(mod, field.value) {
3849 return true
3850 }
3851 }
3852 return false
3853 }
3854 ast.MapInitExpr {
3855 for key in expr.keys {
3856 if t.expr_depends_on_runtime_const(mod, key) {
3857 return true
3858 }
3859 }
3860 for val in expr.vals {
3861 if t.expr_depends_on_runtime_const(mod, val) {
3862 return true
3863 }
3864 }
3865 return false
3866 }
3867 else {
3868 return false
3869 }
3870 }
3871}
3872
3873fn expr_cursor_is_empty(c ast.Cursor) bool {
3874 return !c.is_valid() || c.kind() == .expr_empty
3875}
3876
3877fn (t &Transformer) expr_depends_on_runtime_const_cursor(mod string, expr ast.Cursor) bool {
3878 if !expr.is_valid() {
3879 return false
3880 }
3881 match expr.kind() {
3882 .expr_ident {
3883 key := runtime_const_known_key(mod, expr.name())
3884 return key in t.runtime_const_known || key in t.runtime_const_storage_known
3885 }
3886 .expr_selector {
3887 lhs := expr.edge(0)
3888 if t.expr_depends_on_runtime_const_cursor(mod, lhs) {
3889 return true
3890 }
3891 rhs := expr.edge(1)
3892 if lhs.kind() == .expr_ident && rhs.kind() == .expr_ident {
3893 key := runtime_const_known_key(lhs.name(), rhs.name())
3894 return key in t.runtime_const_known || key in t.runtime_const_storage_known
3895 }
3896 return false
3897 }
3898 .expr_call {
3899 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(0)) {
3900 return true
3901 }
3902 for i in 1 .. expr.edge_count() {
3903 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(i)) {
3904 return true
3905 }
3906 }
3907 return false
3908 }
3909 .expr_call_or_cast, .expr_index, .expr_infix {
3910 return t.expr_depends_on_runtime_const_cursor(mod, expr.edge(0))
3911 || t.expr_depends_on_runtime_const_cursor(mod, expr.edge(1))
3912 }
3913 .expr_cast {
3914 return t.expr_depends_on_runtime_const_cursor(mod, expr.edge(1))
3915 }
3916 .expr_comptime, .expr_modifier, .expr_paren, .expr_postfix, .expr_prefix {
3917 return t.expr_depends_on_runtime_const_cursor(mod, expr.edge(0))
3918 }
3919 .expr_if {
3920 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(0))
3921 || t.expr_depends_on_runtime_const_cursor(mod, expr.edge(1)) {
3922 return true
3923 }
3924 for i in 2 .. expr.edge_count() {
3925 if t.expr_stmt_depends_on_runtime_const_cursor(mod, expr.edge(i)) {
3926 return true
3927 }
3928 }
3929 return false
3930 }
3931 .expr_or {
3932 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(0)) {
3933 return true
3934 }
3935 for i in 1 .. expr.edge_count() {
3936 if t.expr_stmt_depends_on_runtime_const_cursor(mod, expr.edge(i)) {
3937 return true
3938 }
3939 }
3940 return false
3941 }
3942 .expr_string_inter {
3943 inters := expr.list_at(1)
3944 for i in 0 .. inters.len() {
3945 inter := inters.at(i)
3946 if t.expr_depends_on_runtime_const_cursor(mod, inter.edge(0))
3947 || t.expr_depends_on_runtime_const_cursor(mod, inter.edge(1)) {
3948 return true
3949 }
3950 }
3951 return false
3952 }
3953 .expr_unsafe {
3954 for i in 0 .. expr.edge_count() {
3955 if t.expr_stmt_depends_on_runtime_const_cursor(mod, expr.edge(i)) {
3956 return true
3957 }
3958 }
3959 return false
3960 }
3961 .expr_array_init {
3962 for i in 5 .. expr.edge_count() {
3963 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(i)) {
3964 return true
3965 }
3966 }
3967 return (!expr_cursor_is_empty(expr.edge(1))
3968 && t.expr_depends_on_runtime_const_cursor(mod, expr.edge(1)))
3969 || (!expr_cursor_is_empty(expr.edge(3))
3970 && t.expr_depends_on_runtime_const_cursor(mod, expr.edge(3)))
3971 || (!expr_cursor_is_empty(expr.edge(2))
3972 && t.expr_depends_on_runtime_const_cursor(mod, expr.edge(2)))
3973 }
3974 .expr_init {
3975 for i in 1 .. expr.edge_count() {
3976 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(i).edge(0)) {
3977 return true
3978 }
3979 }
3980 return false
3981 }
3982 .expr_map_init {
3983 keys_len := expr.extra_int()
3984 for i in 0 .. keys_len {
3985 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(1 + i)) {
3986 return true
3987 }
3988 }
3989 for i in (1 + keys_len) .. expr.edge_count() {
3990 if t.expr_depends_on_runtime_const_cursor(mod, expr.edge(i)) {
3991 return true
3992 }
3993 }
3994 return false
3995 }
3996 else {
3997 return false
3998 }
3999 }
4000}
4001
4002fn (t &Transformer) expr_stmt_depends_on_runtime_const_cursor(mod string, stmt ast.Cursor) bool {
4003 return stmt.is_valid() && stmt.kind() == .stmt_expr
4004 && t.expr_depends_on_runtime_const_cursor(mod, stmt.edge(0))
4005}
4006
4007fn (mut t Transformer) collect_runtime_const_inits(files []ast.File) {
4008 is_native := t.pref != unsafe { nil } && (t.is_native_be || t.pref.backend == .c)
4009 t.runtime_const_inits_by_mod.clear()
4010 t.runtime_const_modules.clear()
4011 t.runtime_const_init_fn_name.clear()
4012 t.runtime_const_known = map[string]bool{}
4013 t.runtime_const_storage_known = map[string]bool{}
4014 for file in files {
4015 for stmt in file.stmts {
4016 if stmt is ast.ConstDecl {
4017 for field in stmt.fields {
4018 if t.const_initializer_emits_storage(field.value, is_native) {
4019 t.runtime_const_storage_known[runtime_const_known_key(file.mod, field.name)] = true
4020 }
4021 }
4022 }
4023 }
4024 }
4025 for file in files {
4026 for stmt in file.stmts {
4027 if stmt is ast.ConstDecl {
4028 for field in stmt.fields {
4029 if !t.needs_runtime_const_init(field.value, is_native) {
4030 continue
4031 }
4032 t.record_runtime_const_init(file.mod, field.name, field.value)
4033 }
4034 }
4035 if stmt is ast.GlobalDecl {
4036 for field in stmt.fields {
4037 if is_builtin_main_arg_global(file.mod, field.name) {
4038 continue
4039 }
4040 if !t.needs_runtime_global_init(field.value) {
4041 continue
4042 }
4043 t.record_runtime_const_init(file.mod, field.name, field.value)
4044 }
4045 }
4046 }
4047 }
4048 mut changed := true
4049 for changed {
4050 changed = false
4051 for file in files {
4052 for stmt in file.stmts {
4053 if stmt is ast.ConstDecl {
4054 for field in stmt.fields {
4055 if runtime_const_known_key(file.mod, field.name) in t.runtime_const_known {
4056 continue
4057 }
4058 if !t.expr_depends_on_runtime_const(file.mod, field.value) {
4059 continue
4060 }
4061 t.record_runtime_const_init(file.mod, field.name, field.value)
4062 changed = true
4063 }
4064 }
4065 }
4066 }
4067 }
4068}
4069
4070fn (mut t Transformer) collect_runtime_const_inits_from_flat(flat &ast.FlatAst) {
4071 is_native := t.pref != unsafe { nil } && (t.is_native_be || t.pref.backend == .c)
4072 t.runtime_const_inits_by_mod.clear()
4073 t.runtime_const_modules.clear()
4074 t.runtime_const_init_fn_name.clear()
4075 t.runtime_const_known = map[string]bool{}
4076 t.runtime_const_storage_known = map[string]bool{}
4077 for ff in flat.files {
4078 mod := flat.file_mod(ff)
4079 // Walk const field cursors directly. The analysis still materializes
4080 // the specific initializer expressions it needs, but no full ConstDecl.
4081 cdecls := ast.Cursor{
4082 flat: unsafe { flat }
4083 id: ff.file_id
4084 }.list_at(2)
4085 for ci in 0 .. cdecls.len() {
4086 cc := cdecls.at(ci)
4087 if cc.kind() != .stmt_const_decl {
4088 continue
4089 }
4090 fields := cc.list_at(0)
4091 for fi in 0 .. fields.len() {
4092 field := fields.at(fi)
4093 field_name := field.name()
4094 if field_name == '' {
4095 continue
4096 }
4097 value_c := field.edge(0)
4098 if t.const_initializer_emits_storage_cursor(value_c, is_native) {
4099 t.runtime_const_storage_known[runtime_const_known_key(mod, field_name)] = true
4100 }
4101 }
4102 }
4103 }
4104 for ff in flat.files {
4105 mod := flat.file_mod(ff)
4106 // Decode only initializer expressions from const/global field cursors.
4107 cdecls := ast.Cursor{
4108 flat: unsafe { flat }
4109 id: ff.file_id
4110 }.list_at(2)
4111 for ci in 0 .. cdecls.len() {
4112 cc := cdecls.at(ci)
4113 ck := cc.kind()
4114 if ck != .stmt_const_decl && ck != .stmt_global_decl {
4115 continue
4116 }
4117 if ck == .stmt_const_decl {
4118 fields := cc.list_at(0)
4119 for fi in 0 .. fields.len() {
4120 field := fields.at(fi)
4121 field_name := field.name()
4122 if field_name == '' {
4123 continue
4124 }
4125 value_c := field.edge(0)
4126 if !t.needs_runtime_const_init_cursor(value_c, is_native) {
4127 continue
4128 }
4129 t.record_runtime_const_init_cursor(mod, field_name, value_c)
4130 }
4131 }
4132 if ck == .stmt_global_decl {
4133 fields := cc.list_at(1)
4134 for fi in 0 .. fields.len() {
4135 field := fields.at(fi)
4136 field_name := field.name()
4137 if field_name == '' {
4138 continue
4139 }
4140 if is_builtin_main_arg_global(mod, field_name) {
4141 continue
4142 }
4143 value_c := field.edge(1)
4144 if !t.needs_runtime_global_init_cursor(value_c) {
4145 continue
4146 }
4147 t.record_runtime_const_init_cursor(mod, field_name, value_c)
4148 }
4149 }
4150 }
4151 }
4152 mut changed := true
4153 for changed {
4154 changed = false
4155 for ff in flat.files {
4156 mod := flat.file_mod(ff)
4157 // s260: fixpoint loop — decode only const decls each iteration instead
4158 // of re-decoding the whole file (incl. fn bodies) every pass.
4159 cdecls := ast.Cursor{
4160 flat: unsafe { flat }
4161 id: ff.file_id
4162 }.list_at(2)
4163 for ci in 0 .. cdecls.len() {
4164 cc := cdecls.at(ci)
4165 if cc.kind() != .stmt_const_decl {
4166 continue
4167 }
4168 fields := cc.list_at(0)
4169 for fi in 0 .. fields.len() {
4170 field := fields.at(fi)
4171 field_name := field.name()
4172 if field_name == '' {
4173 continue
4174 }
4175 if runtime_const_known_key(mod, field_name) in t.runtime_const_known {
4176 continue
4177 }
4178 value_c := field.edge(0)
4179 if !t.expr_depends_on_runtime_const_cursor(mod, value_c) {
4180 continue
4181 }
4182 t.record_runtime_const_init_cursor(mod, field_name, value_c)
4183 changed = true
4184 }
4185 }
4186 }
4187 }
4188}
4189
4190fn (t &Transformer) const_initializer_emits_storage(expr ast.Expr, is_native bool) bool {
4191 if t.needs_runtime_const_init(expr, is_native) {
4192 return true
4193 }
4194 if expr is ast.StringLiteral || expr is ast.StringInterLiteral {
4195 return true
4196 }
4197 if typ := t.get_expr_type(expr) {
4198 c_type := t.type_to_c_name(typ)
4199 return c_type == 'string' || c_type == 'array' || c_type.starts_with('Array_')
4200 || c_type.starts_with('Map_')
4201 }
4202 return false
4203}
4204
4205fn (t &Transformer) const_initializer_emits_storage_cursor(expr ast.Cursor, is_native bool) bool {
4206 if t.needs_runtime_const_init_cursor(expr, is_native) {
4207 return true
4208 }
4209 if expr.is_valid() && (expr.kind() == .expr_string || expr.kind() == .expr_string_inter) {
4210 return true
4211 }
4212 if typ := t.get_expr_type_cursor(expr) {
4213 c_type := t.type_to_c_name(typ)
4214 return c_type == 'string' || c_type == 'array' || c_type.starts_with('Array_')
4215 || c_type.starts_with('Map_')
4216 }
4217 return false
4218}
4219
4220fn (t &Transformer) contains_runtime_const_literal(expr ast.Expr, is_native bool) bool {
4221 match expr {
4222 ast.MapInitExpr {
4223 return true
4224 }
4225 ast.ArrayInitExpr {
4226 if is_native {
4227 return true
4228 }
4229 for e in expr.exprs {
4230 if t.contains_runtime_const_literal(e, is_native) {
4231 return true
4232 }
4233 }
4234 return (expr.init !is ast.EmptyExpr
4235 && t.contains_runtime_const_literal(expr.init, is_native))
4236 || (expr.len !is ast.EmptyExpr
4237 && t.contains_runtime_const_literal(expr.len, is_native))
4238 || (expr.cap !is ast.EmptyExpr
4239 && t.contains_runtime_const_literal(expr.cap, is_native))
4240 }
4241 ast.InitExpr {
4242 if is_native {
4243 return true
4244 }
4245 for field in expr.fields {
4246 if t.contains_runtime_const_literal(field.value, is_native) {
4247 return true
4248 }
4249 }
4250 return false
4251 }
4252 ast.CastExpr {
4253 return t.contains_runtime_const_literal(expr.expr, is_native)
4254 }
4255 ast.ParenExpr {
4256 return t.contains_runtime_const_literal(expr.expr, is_native)
4257 }
4258 ast.PrefixExpr {
4259 return t.contains_runtime_const_literal(expr.expr, is_native)
4260 }
4261 ast.PostfixExpr {
4262 return t.contains_runtime_const_literal(expr.expr, is_native)
4263 }
4264 ast.ModifierExpr {
4265 return t.contains_runtime_const_literal(expr.expr, is_native)
4266 }
4267 ast.IndexExpr {
4268 return t.contains_runtime_const_literal(expr.lhs, is_native)
4269 || t.contains_runtime_const_literal(expr.expr, is_native)
4270 }
4271 ast.InfixExpr {
4272 return t.contains_runtime_const_literal(expr.lhs, is_native)
4273 || t.contains_runtime_const_literal(expr.rhs, is_native)
4274 }
4275 ast.IfExpr {
4276 return true
4277 }
4278 ast.OrExpr, ast.StringInterLiteral, ast.UnsafeExpr {
4279 return true
4280 }
4281 ast.ComptimeExpr {
4282 return t.contains_runtime_const_literal(expr.expr, is_native)
4283 }
4284 ast.CallExpr {
4285 if t.contains_runtime_const_literal(expr.lhs, is_native) {
4286 return true
4287 }
4288 for arg in expr.args {
4289 if t.contains_runtime_const_literal(arg, is_native) {
4290 return true
4291 }
4292 }
4293 return false
4294 }
4295 ast.CallOrCastExpr {
4296 return t.contains_runtime_const_literal(expr.lhs, is_native)
4297 || t.contains_runtime_const_literal(expr.expr, is_native)
4298 }
4299 ast.SelectorExpr {
4300 return t.contains_runtime_const_literal(expr.lhs, is_native)
4301 }
4302 else {
4303 return false
4304 }
4305 }
4306}
4307
4308fn (t &Transformer) contains_runtime_const_literal_cursor(expr ast.Cursor, is_native bool) bool {
4309 if !expr.is_valid() {
4310 return false
4311 }
4312 match expr.kind() {
4313 .expr_map_init {
4314 return true
4315 }
4316 .expr_array_init {
4317 if is_native {
4318 return true
4319 }
4320 for i in 5 .. expr.edge_count() {
4321 if t.contains_runtime_const_literal_cursor(expr.edge(i), is_native) {
4322 return true
4323 }
4324 }
4325 return (!expr_cursor_is_empty(expr.edge(1))
4326 && t.contains_runtime_const_literal_cursor(expr.edge(1), is_native))
4327 || (!expr_cursor_is_empty(expr.edge(3))
4328 && t.contains_runtime_const_literal_cursor(expr.edge(3), is_native))
4329 || (!expr_cursor_is_empty(expr.edge(2))
4330 && t.contains_runtime_const_literal_cursor(expr.edge(2), is_native))
4331 }
4332 .expr_init {
4333 if is_native {
4334 return true
4335 }
4336 for i in 1 .. expr.edge_count() {
4337 if t.contains_runtime_const_literal_cursor(expr.edge(i).edge(0), is_native) {
4338 return true
4339 }
4340 }
4341 return false
4342 }
4343 .expr_cast {
4344 return t.contains_runtime_const_literal_cursor(expr.edge(1), is_native)
4345 }
4346 .expr_comptime, .expr_modifier, .expr_paren, .expr_postfix, .expr_prefix {
4347 return t.contains_runtime_const_literal_cursor(expr.edge(0), is_native)
4348 }
4349 .expr_index, .expr_infix {
4350 return t.contains_runtime_const_literal_cursor(expr.edge(0), is_native)
4351 || t.contains_runtime_const_literal_cursor(expr.edge(1), is_native)
4352 }
4353 .expr_if, .expr_or, .expr_string_inter, .expr_unsafe {
4354 return true
4355 }
4356 .expr_call {
4357 if t.contains_runtime_const_literal_cursor(expr.edge(0), is_native) {
4358 return true
4359 }
4360 for i in 1 .. expr.edge_count() {
4361 if t.contains_runtime_const_literal_cursor(expr.edge(i), is_native) {
4362 return true
4363 }
4364 }
4365 return false
4366 }
4367 .expr_call_or_cast {
4368 return t.contains_runtime_const_literal_cursor(expr.edge(0), is_native)
4369 || t.contains_runtime_const_literal_cursor(expr.edge(1), is_native)
4370 }
4371 .expr_selector {
4372 return t.contains_runtime_const_literal_cursor(expr.edge(0), is_native)
4373 }
4374 else {
4375 return false
4376 }
4377 }
4378}
4379
4380// needs_runtime_const_init checks whether a const initializer requires runtime
4381// initialization. For C/cleanc backends, direct calls and value-position if
4382// expressions need it because they cannot be emitted as C compile-time
4383// constants. Map literals are lowered to constructor calls, so they also need
4384// an injected const init assignment. For SSA-based backends (arm64, x64, c),
4385// array and struct literals also need runtime init because the SSA builder
4386// cannot embed them in the data segment.
4387fn (t &Transformer) needs_runtime_const_init(expr ast.Expr, is_native bool) bool {
4388 if t.contains_runtime_const_literal(expr, is_native) {
4389 return true
4390 }
4391 if t.contains_call_expr(expr) {
4392 return true
4393 }
4394 if expr is ast.IfExpr {
4395 return true
4396 }
4397 if expr is ast.OrExpr || expr is ast.StringInterLiteral || expr is ast.UnsafeExpr {
4398 return true
4399 }
4400 if expr is ast.ComptimeExpr {
4401 return t.needs_runtime_const_init(expr.expr, is_native)
4402 }
4403 // CallOrCastExpr: distinguish function calls from type casts.
4404 // Function calls (lowercase lhs, not a primitive type) need runtime init.
4405 // Type casts (uppercase lhs like IError(...), or primitives like int(...)) are compile-time.
4406 if expr is ast.CallOrCastExpr {
4407 if is_native {
4408 return true
4409 }
4410 if expr.lhs is ast.Ident {
4411 name := expr.lhs.name
4412 if name.len > 0 && name[0] >= `a` && name[0] <= `z`
4413 && name !in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'rune', 'isize', 'usize', 'byte', 'voidptr', 'charptr', 'byteptr', 'string'] {
4414 return true
4415 }
4416 } else if expr.lhs is ast.SelectorExpr {
4417 // Module-qualified call like module.func(...)
4418 return true
4419 }
4420 }
4421 if is_native {
4422 if expr is ast.CastExpr {
4423 return true
4424 }
4425 if expr is ast.ArrayInitExpr {
4426 return true
4427 }
4428 if expr is ast.InitExpr {
4429 return true
4430 }
4431 }
4432 return false
4433}
4434
4435fn (t &Transformer) needs_runtime_const_init_cursor(expr ast.Cursor, is_native bool) bool {
4436 if t.contains_runtime_const_literal_cursor(expr, is_native) {
4437 return true
4438 }
4439 if t.contains_call_expr_cursor(expr) {
4440 return true
4441 }
4442 if !expr.is_valid() {
4443 return false
4444 }
4445 if expr.kind() == .expr_if {
4446 return true
4447 }
4448 if expr.kind() in [.expr_or, .expr_string_inter, .expr_unsafe] {
4449 return true
4450 }
4451 if expr.kind() == .expr_comptime {
4452 return t.needs_runtime_const_init_cursor(expr.edge(0), is_native)
4453 }
4454 if expr.kind() == .expr_call_or_cast {
4455 if is_native {
4456 return true
4457 }
4458 lhs := expr.edge(0)
4459 if lhs.kind() == .expr_ident {
4460 name := lhs.name()
4461 if name.len > 0 && name[0] >= `a` && name[0] <= `z`
4462 && name !in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'rune', 'isize', 'usize', 'byte', 'voidptr', 'charptr', 'byteptr', 'string'] {
4463 return true
4464 }
4465 } else if lhs.kind() == .expr_selector {
4466 return true
4467 }
4468 }
4469 if is_native && expr.kind() in [.expr_cast, .expr_array_init, .expr_init] {
4470 return true
4471 }
4472 return false
4473}
4474
4475fn (t &Transformer) needs_runtime_global_init(expr ast.Expr) bool {
4476 match expr {
4477 ast.EmptyExpr {
4478 return false
4479 }
4480 ast.InitExpr, ast.MapInitExpr, ast.IfExpr, ast.CallOrCastExpr {
4481 return true
4482 }
4483 ast.OrExpr, ast.StringInterLiteral, ast.UnsafeExpr, ast.LockExpr, ast.MatchExpr {
4484 return true
4485 }
4486 ast.ComptimeExpr {
4487 return t.needs_runtime_global_init(expr.expr)
4488 }
4489 ast.ArrayInitExpr {
4490 if expr.typ is ast.Type && expr.typ is ast.ArrayFixedType {
4491 return t.contains_call_expr(expr)
4492 }
4493 return true
4494 }
4495 ast.CastExpr {
4496 return t.needs_runtime_global_init(expr.expr)
4497 }
4498 ast.ParenExpr {
4499 return t.needs_runtime_global_init(expr.expr)
4500 }
4501 ast.PrefixExpr {
4502 return t.needs_runtime_global_init(expr.expr)
4503 }
4504 ast.PostfixExpr {
4505 return t.needs_runtime_global_init(expr.expr)
4506 }
4507 ast.ModifierExpr {
4508 return t.needs_runtime_global_init(expr.expr)
4509 }
4510 ast.IndexExpr {
4511 return t.needs_runtime_global_init(expr.lhs) || t.needs_runtime_global_init(expr.expr)
4512 }
4513 ast.InfixExpr {
4514 return t.needs_runtime_global_init(expr.lhs) || t.needs_runtime_global_init(expr.rhs)
4515 }
4516 else {
4517 return t.contains_call_expr(expr)
4518 }
4519 }
4520}
4521
4522fn (t &Transformer) needs_runtime_global_init_cursor(expr ast.Cursor) bool {
4523 if !expr.is_valid() {
4524 return false
4525 }
4526 match expr.kind() {
4527 .expr_empty {
4528 return false
4529 }
4530 .expr_init, .expr_map_init, .expr_if, .expr_call_or_cast {
4531 return true
4532 }
4533 .expr_or, .expr_string_inter, .expr_unsafe, .expr_lock, .expr_match {
4534 return true
4535 }
4536 .expr_comptime {
4537 return t.needs_runtime_global_init_cursor(expr.edge(0))
4538 }
4539 .expr_array_init {
4540 if expr.edge(0).kind() == .typ_array_fixed {
4541 return t.contains_call_expr_cursor(expr)
4542 }
4543 return true
4544 }
4545 .expr_cast {
4546 return t.needs_runtime_global_init_cursor(expr.edge(1))
4547 }
4548 .expr_modifier, .expr_paren, .expr_postfix, .expr_prefix {
4549 return t.needs_runtime_global_init_cursor(expr.edge(0))
4550 }
4551 .expr_index, .expr_infix {
4552 return t.needs_runtime_global_init_cursor(expr.edge(0))
4553 || t.needs_runtime_global_init_cursor(expr.edge(1))
4554 }
4555 else {
4556 return t.contains_call_expr_cursor(expr)
4557 }
4558 }
4559}
4560
4561fn (t &Transformer) contains_call_expr_cursor(expr ast.Cursor) bool {
4562 return t.contains_call_expr_cursor_depth(0, expr)
4563}
4564
4565fn (t &Transformer) runtime_const_init_contains_call(item RuntimeConstInit) bool {
4566 if item.expr_cursor.is_valid() {
4567 return t.contains_call_expr_cursor(item.expr_cursor)
4568 }
4569 return t.contains_call_expr(item.expr)
4570}
4571
4572fn (t &Transformer) contains_call_expr_cursor_depth(depth int, expr ast.Cursor) bool {
4573 if depth > max_runtime_const_dep_expr_depth || !expr.is_valid() {
4574 return false
4575 }
4576 match expr.kind() {
4577 .expr_call {
4578 return true
4579 }
4580 .expr_cast {
4581 return t.contains_call_expr_cursor_depth(depth + 1, expr.edge(1))
4582 }
4583 .expr_call_or_cast {
4584 return t.contains_call_expr_cursor_depth(depth + 1, expr.edge(1))
4585 }
4586 .expr_paren, .expr_postfix, .expr_prefix {
4587 return t.contains_call_expr_cursor_depth(depth + 1, expr.edge(0))
4588 }
4589 .expr_infix, .expr_index {
4590 return t.contains_call_expr_cursor_depth(depth + 1, expr.edge(0))
4591 || t.contains_call_expr_cursor_depth(depth + 1, expr.edge(1))
4592 }
4593 .expr_array_init {
4594 for i in 5 .. expr.edge_count() {
4595 if t.contains_call_expr_cursor_depth(depth + 1, expr.edge(i)) {
4596 return true
4597 }
4598 }
4599 return (!expr_cursor_is_empty(expr.edge(1))
4600 && t.contains_call_expr_cursor_depth(depth + 1, expr.edge(1)))
4601 || (!expr_cursor_is_empty(expr.edge(3))
4602 && t.contains_call_expr_cursor_depth(depth + 1, expr.edge(3)))
4603 || (!expr_cursor_is_empty(expr.edge(2))
4604 && t.contains_call_expr_cursor_depth(depth + 1, expr.edge(2)))
4605 }
4606 .expr_init {
4607 for i in 1 .. expr.edge_count() {
4608 if t.contains_call_expr_cursor_depth(depth + 1, expr.edge(i).edge(0)) {
4609 return true
4610 }
4611 }
4612 return false
4613 }
4614 .expr_map_init {
4615 keys_len := expr.extra_int()
4616 for i in 0 .. keys_len {
4617 if t.contains_call_expr_cursor_depth(depth + 1, expr.edge(1 + i)) {
4618 return true
4619 }
4620 }
4621 for i in (1 + keys_len) .. expr.edge_count() {
4622 if t.contains_call_expr_cursor_depth(depth + 1, expr.edge(i)) {
4623 return true
4624 }
4625 }
4626 return false
4627 }
4628 .expr_selector {
4629 return t.contains_call_expr_cursor_depth(depth + 1, expr.edge(0))
4630 }
4631 else {
4632 return false
4633 }
4634 }
4635}
4636
4637fn (mut t Transformer) transform_expr_in_module(mod string, expr ast.Expr) ast.Expr {
4638 old_module := t.cur_module
4639 old_scope := t.scope
4640 t.cur_module = mod
4641 if scope := t.get_module_scope(mod) {
4642 t.scope = scope
4643 } else {
4644 t.scope = unsafe { nil }
4645 }
4646 transformed := t.transform_expr(expr)
4647 t.cur_module = old_module
4648 t.scope = old_scope
4649 return transformed
4650}
4651
4652fn (mut t Transformer) runtime_const_init_fn_stmt(mod string, fn_name string, inits []RuntimeConstInit) ast.Stmt {
4653 mut stmts := []ast.Stmt{cap: inits.len}
4654 for item in inits {
4655 expr := if item.expr_cursor.is_valid() { item.expr_cursor.expr() } else { item.expr }
4656 saved_pending := t.pending_stmts
4657 t.pending_stmts = []ast.Stmt{}
4658 old_skip_if := t.skip_if_value_lowering
4659 t.skip_if_value_lowering = true
4660 transformed_expr := t.transform_expr_in_module(mod, expr)
4661 t.skip_if_value_lowering = old_skip_if
4662 generated_pending := t.pending_stmts
4663 t.pending_stmts = saved_pending
4664 for pending_stmt in generated_pending {
4665 stmts << pending_stmt
4666 }
4667 stmts << ast.AssignStmt{
4668 op: .assign
4669 lhs: [ast.Expr(ast.Ident{
4670 name: item.name
4671 })]
4672 rhs: [transformed_expr]
4673 }
4674 }
4675 return ast.Stmt(ast.FnDecl{
4676 name: fn_name
4677 typ: ast.FnType{}
4678 stmts: stmts
4679 })
4680}
4681
4682fn (mut t Transformer) runtime_const_init_fn_stmt_to_flat(mod string, fn_name string, inits []RuntimeConstInit, mut out ast.FlatBuilder) ast.FlatNodeId {
4683 mut stmt_ids := []ast.FlatNodeId{cap: inits.len}
4684 old_module := t.cur_module
4685 old_scope := t.scope
4686 t.cur_module = mod
4687 if scope := t.get_module_scope(mod) {
4688 t.scope = scope
4689 } else {
4690 t.scope = unsafe { nil }
4691 }
4692 defer {
4693 t.cur_module = old_module
4694 t.scope = old_scope
4695 }
4696 for item in inits {
4697 saved_pending := t.pending_stmts
4698 t.pending_stmts = []ast.Stmt{}
4699 old_skip_if := t.skip_if_value_lowering
4700 t.skip_if_value_lowering = true
4701 rhs_id := if item.expr_cursor.is_valid() {
4702 t.transform_expr_cursor_to_flat(item.expr_cursor, mut out)
4703 } else {
4704 transformed_expr := t.transform_expr(item.expr)
4705 out.emit_expr(transformed_expr)
4706 }
4707 t.skip_if_value_lowering = old_skip_if
4708 generated_pending := t.pending_stmts
4709 t.pending_stmts = saved_pending
4710 for pending_stmt in generated_pending {
4711 stmt_ids << out.emit_stmt(pending_stmt)
4712 }
4713 lhs_id := out.emit_ident_by_name(item.name, token.Pos{})
4714 stmt_ids << out.emit_assign_stmt_by_ids(.assign, [lhs_id], [rhs_id], token.Pos{})
4715 }
4716 receiver_id := out.emit_parameter(ast.Parameter{
4717 typ: ast.empty_expr
4718 })
4719 typ_id := out.emit_type(ast.Type(ast.FnType{}))
4720 attrs_id := out.emit_aux_list_from_ids([])
4721 stmts_id := out.emit_aux_list_from_ids(stmt_ids)
4722 return out.emit_fn_decl_by_ids(fn_name, false, false, false, .v, token.Pos{}, receiver_id,
4723 typ_id, attrs_id, stmts_id)
4724}
4725
4726fn (t &Transformer) collect_ident_names_in_expr(mut names map[string]bool, expr ast.Expr) {
4727 t.collect_ident_names_in_expr_depth(0, mut names, expr)
4728}
4729
4730fn (t &Transformer) collect_ident_names_in_runtime_const_init(mut names map[string]bool, item RuntimeConstInit) {
4731 if item.expr_cursor.is_valid() {
4732 t.collect_ident_names_in_expr_cursor_depth(0, mut names, item.expr_cursor)
4733 return
4734 }
4735 t.collect_ident_names_in_expr(mut names, item.expr)
4736}
4737
4738fn (t &Transformer) collect_ident_names_in_expr_depth(depth int, mut names map[string]bool, expr ast.Expr) {
4739 if depth > max_runtime_const_dep_expr_depth || !expr_has_valid_data(expr) {
4740 return
4741 }
4742 match expr {
4743 ast.Ident {
4744 if expr.name != '' {
4745 names[expr.name] = true
4746 }
4747 }
4748 ast.SelectorExpr {
4749 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.lhs)
4750 }
4751 ast.InfixExpr {
4752 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.lhs)
4753 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.rhs)
4754 }
4755 ast.ParenExpr {
4756 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.expr)
4757 }
4758 ast.PrefixExpr {
4759 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.expr)
4760 }
4761 ast.PostfixExpr {
4762 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.expr)
4763 }
4764 ast.ModifierExpr {
4765 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.expr)
4766 }
4767 ast.CastExpr {
4768 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.expr)
4769 }
4770 ast.CallExpr {
4771 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.lhs)
4772 for arg in expr.args {
4773 t.collect_ident_names_in_expr_depth(depth + 1, mut names, arg)
4774 }
4775 }
4776 ast.CallOrCastExpr {
4777 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.lhs)
4778 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.expr)
4779 }
4780 ast.IfExpr {
4781 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.cond)
4782 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.else_expr)
4783 for stmt in expr.stmts {
4784 if stmt is ast.ExprStmt {
4785 t.collect_ident_names_in_expr_depth(depth + 1, mut names, stmt.expr)
4786 }
4787 }
4788 }
4789 ast.IndexExpr {
4790 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.lhs)
4791 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.expr)
4792 }
4793 ast.ArrayInitExpr {
4794 for e in expr.exprs {
4795 t.collect_ident_names_in_expr_depth(depth + 1, mut names, e)
4796 }
4797 if expr.init !is ast.EmptyExpr {
4798 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.init)
4799 }
4800 if expr.len !is ast.EmptyExpr {
4801 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.len)
4802 }
4803 if expr.cap !is ast.EmptyExpr {
4804 t.collect_ident_names_in_expr_depth(depth + 1, mut names, expr.cap)
4805 }
4806 }
4807 ast.InitExpr {
4808 for field in expr.fields {
4809 t.collect_ident_names_in_expr_depth(depth + 1, mut names, field.value)
4810 }
4811 }
4812 ast.MapInitExpr {
4813 for key in expr.keys {
4814 t.collect_ident_names_in_expr_depth(depth + 1, mut names, key)
4815 }
4816 for val in expr.vals {
4817 t.collect_ident_names_in_expr_depth(depth + 1, mut names, val)
4818 }
4819 }
4820 else {}
4821 }
4822}
4823
4824fn (t &Transformer) collect_ident_names_in_expr_cursor_depth(depth int, mut names map[string]bool, expr ast.Cursor) {
4825 if depth > max_runtime_const_dep_expr_depth || !expr.is_valid() {
4826 return
4827 }
4828 match expr.kind() {
4829 .expr_ident {
4830 if expr.name() != '' {
4831 names[expr.name()] = true
4832 }
4833 }
4834 .expr_selector {
4835 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(0))
4836 }
4837 .expr_infix, .expr_index, .expr_call_or_cast {
4838 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(0))
4839 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(1))
4840 }
4841 .expr_paren, .expr_prefix, .expr_postfix, .expr_modifier, .expr_cast, .expr_as_cast {
4842 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(0))
4843 }
4844 .expr_call {
4845 for i in 0 .. expr.edge_count() {
4846 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(i))
4847 }
4848 }
4849 .expr_if {
4850 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(0))
4851 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(1))
4852 for i in 2 .. expr.edge_count() {
4853 stmt := expr.edge(i)
4854 if stmt.kind() == .stmt_expr {
4855 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, stmt.edge(0))
4856 }
4857 }
4858 }
4859 .expr_array_init {
4860 for i in 5 .. expr.edge_count() {
4861 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(i))
4862 }
4863 for i in [1, 3, 2] {
4864 child := expr.edge(i)
4865 if !expr_cursor_is_empty(child) {
4866 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, child)
4867 }
4868 }
4869 }
4870 .expr_init {
4871 for i in 1 .. expr.edge_count() {
4872 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names,
4873 expr.edge(i).edge(0))
4874 }
4875 }
4876 .expr_map_init {
4877 keys_len := expr.extra_int()
4878 for i in 0 .. keys_len {
4879 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(1 + i))
4880 }
4881 for i in (1 + keys_len) .. expr.edge_count() {
4882 t.collect_ident_names_in_expr_cursor_depth(depth + 1, mut names, expr.edge(i))
4883 }
4884 }
4885 else {}
4886 }
4887}
4888
4889fn (t &Transformer) order_runtime_const_inits(inits []RuntimeConstInit) []RuntimeConstInit {
4890 if inits.len < 2 {
4891 return inits
4892 }
4893 // Use maps instead of parallel arrays to avoid ARM64 stack corruption
4894 // when multiple local arrays interact with array_set operations.
4895 mut index_by_name := map[string]int{}
4896 for i, item in inits {
4897 if item.name !in index_by_name {
4898 index_by_name[item.name] = i
4899 }
4900 }
4901 mut indegree := map[int]int{}
4902 mut dependents := map[int][]int{}
4903 for i, item in inits {
4904 mut ident_names := map[string]bool{}
4905 t.collect_ident_names_in_runtime_const_init(mut ident_names, item)
4906 for dep_name, _ in ident_names {
4907 if dep_name == item.name {
4908 continue
4909 }
4910 if dep_idx := index_by_name[dep_name] {
4911 mut dep := []int{}
4912 if dep_idx in dependents {
4913 dep = dependents[dep_idx]
4914 }
4915 dep << i
4916 dependents[dep_idx] = dep
4917 mut deg := 0
4918 if i in indegree {
4919 deg = indegree[i]
4920 }
4921 indegree[i] = deg + 1
4922 }
4923 }
4924 }
4925 mut ready := []int{}
4926 for i := 0; i < inits.len; i++ {
4927 mut deg := 0
4928 if i in indegree {
4929 deg = indegree[i]
4930 }
4931 if deg == 0 {
4932 ready << i
4933 }
4934 }
4935 mut ordered_idx := []int{cap: inits.len}
4936 for ready.len > 0 {
4937 // Prefer non-call initializers first when dependency order allows it.
4938 mut best_pos := 0
4939 for pos := 1; pos < ready.len; pos++ {
4940 a := ready[pos]
4941 b := ready[best_pos]
4942 a_has_call := t.runtime_const_init_contains_call(inits[a])
4943 b_has_call := t.runtime_const_init_contains_call(inits[b])
4944 if a_has_call != b_has_call {
4945 if !a_has_call && b_has_call {
4946 best_pos = pos
4947 }
4948 continue
4949 }
4950 if a < b {
4951 best_pos = pos
4952 }
4953 }
4954 cur := ready[best_pos]
4955 ready.delete(best_pos)
4956 ordered_idx << cur
4957 mut deps := []int{}
4958 if cur in dependents {
4959 deps = dependents[cur]
4960 }
4961 for dep in deps {
4962 mut deg := 0
4963 if dep in indegree {
4964 deg = indegree[dep]
4965 }
4966 deg--
4967 indegree[dep] = deg
4968 if deg == 0 {
4969 ready << dep
4970 }
4971 }
4972 }
4973 if ordered_idx.len != inits.len {
4974 return inits
4975 }
4976 mut ordered := []RuntimeConstInit{cap: inits.len}
4977 for idx in ordered_idx {
4978 ordered << inits[idx]
4979 }
4980 return ordered
4981}
4982
4983fn (mut t Transformer) inject_runtime_const_init_fns(mut files []ast.File) {
4984 for mod, fn_stmt in t.runtime_const_init_fn_stmts_parts() {
4985 for i, file in files {
4986 if file.mod != mod {
4987 continue
4988 }
4989 mut new_stmts := []ast.Stmt{cap: file.stmts.len + 1}
4990 for stmt in file.stmts {
4991 new_stmts << stmt
4992 }
4993 new_stmts << fn_stmt
4994 files[i] = ast.File{
4995 attributes: file.attributes
4996 mod: file.mod
4997 name: file.name
4998 stmts: new_stmts
4999 imports: file.imports
5000 }
5001 break
5002 }
5003 }
5004}
5005
5006// runtime_const_init_fn_stmts_parts is the pure-computation extraction of
5007// `inject_runtime_const_init_fns`'s per-module work. Returns a `(module
5008// name, fn_stmt)` map for each module that has runtime const inits, after
5009// running the per-module side effects (`runtime_const_init_fn_name[mod] = ...`
5010// + `order_runtime_const_inits`). Does NOT mutate any `[]ast.File` — the
5011// caller decides whether to splice each `(mod, fn_stmt)` pair into a legacy
5012// `[]ast.File` (via `inject_runtime_const_init_fns`) or into a
5013// `FlatBuilder` (future `post_pass_to_flat`).
5014//
5015// First Phase 5 (post_pass port) scaffolding — establishes the "_parts"
5016// extraction pattern (precedent: `transform_fn_decl_parts` in session 4,
5017// `transform_assign_stmt_parts` in session 59). Future post_pass-port
5018// sessions extract `_parts` from `inject_test_main`,
5019// `inject_embed_file_helper`, `inject_main_runtime_const_init_calls`,
5020// `inject_live_reload`, and `generate_*_functions` to compose a
5021// FlatBuilder-aware `post_pass_to_flat`.
5022pub fn (mut t Transformer) runtime_const_init_fn_stmts_parts() map[string]ast.Stmt {
5023 mut result := map[string]ast.Stmt{}
5024 for mod in t.runtime_const_modules {
5025 inits := t.runtime_const_inits_by_mod[mod] or { []RuntimeConstInit{} }
5026 if inits.len == 0 {
5027 continue
5028 }
5029 fn_name := runtime_const_init_base_name(mod)
5030 t.runtime_const_init_fn_name[mod] = fn_name
5031 ordered_inits := t.order_runtime_const_inits(inits)
5032 result[mod] = t.runtime_const_init_fn_stmt(mod, fn_name, ordered_inits)
5033 }
5034 return result
5035}
5036
5037// inject_runtime_const_init_fns_to_flat is the FlatBuilder-side splice
5038// counterpart to `inject_runtime_const_init_fns` (legacy `[]ast.File`
5039// mutator). It walks `runtime_const_modules` directly, emits each init
5040// function from flat cursors when available, then folds the resulting id into
5041// the first file whose module matches `mod`.
5042//
5043// Closes the 6-step post_pass port arc: every file-mutating step in
5044// legacy `post_pass` now has a flat-aware `_to_flat` variant. Sibling to
5045// s151's `inject_embed_file_helper_to_flat` (same "find file by mod /
5046// append fn_stmt" shape; s151 routes through `builtin_idx` instead of
5047// scanning by mod string).
5048pub fn (mut t Transformer) inject_runtime_const_init_fns_to_flat(mut out ast.FlatBuilder) {
5049 for mod in t.runtime_const_modules {
5050 inits := t.runtime_const_inits_by_mod[mod] or { []RuntimeConstInit{} }
5051 if inits.len == 0 {
5052 continue
5053 }
5054 fn_name := runtime_const_init_base_name(mod)
5055 t.runtime_const_init_fn_name[mod] = fn_name
5056 ordered_inits := t.order_runtime_const_inits(inits)
5057 mut file_idx := -1
5058 for i in 0 .. out.flat.files.len {
5059 if out.flat.string_at(out.flat.files[i].mod_idx) == mod {
5060 file_idx = i
5061 break
5062 }
5063 }
5064 if file_idx < 0 {
5065 continue
5066 }
5067 stmt_id := t.runtime_const_init_fn_stmt_to_flat(mod, fn_name, ordered_inits, mut out)
5068 out.append_file_stmts(file_idx, [stmt_id])
5069 }
5070}
5071
5072// inject_test_main synthesizes a main() function for test files that have
5073// test_ functions but no explicit main. The generated main calls each test
5074// function and prints progress messages. This is needed for all backends
5075// (cleanc has its own synthesis in the C emitter, but native backends rely
5076// on the transformer to provide main).
5077// TestMainParts is the pure-computation bundle produced by
5078// `inject_test_main_parts`. `main_fn` is the synthesised `ast.FnDecl` for
5079// `main`; `test_file_idx` is the index into `files` of the first file that
5080// declares a `test_*` function (where the synthesised main is spliced).
5081pub struct TestMainParts {
5082pub:
5083 test_file_idx int
5084 main_fn ast.Stmt
5085}
5086
5087// inject_test_main_parts is the pure-computation extraction of the
5088// pre-splice state previously inline in `inject_test_main`. Walks `files`
5089// to (a) detect an existing user `main` (returns `none` if found), (b)
5090// collect every top-level `test_*` fn name in declaration order, and (c)
5091// synthesise the test-runner `main` body — per-test (`println('Running
5092// test: <name>...')`, `<name>()`, `println(' OK')`) followed by a single
5093// summary `println('All N tests passed.')`. Returns `none` when no
5094// `test_*` fns are found.
5095//
5096// Does NOT mutate `files` or any caller state. Caller decides whether to
5097// splice the synthesised `main_fn` into a legacy `[]ast.File` (via
5098// `inject_test_main`) or into a `FlatBuilder` (future `post_pass_to_flat`).
5099//
5100// Fourth Phase 5 (post_pass port) `_parts` extraction (follows s144's
5101// `runtime_const_init_fn_stmts_parts`, s145's
5102// `runtime_const_init_main_calls_parts`, s146's `live_reload_parts`).
5103// Bit-equal: identical traversal order, identical `quote_v_string_literal`
5104// payloads, identical FnDecl shape (only `name` + `stmts` populated).
5105pub fn (mut t Transformer) inject_test_main_parts(files []ast.File) ?TestMainParts {
5106 for file in files {
5107 for stmt in file.stmts {
5108 if stmt is ast.FnDecl && !stmt.is_method && stmt.name == 'main' {
5109 return none
5110 }
5111 }
5112 }
5113
5114 mut test_fn_names := []string{}
5115 mut test_file_idx := -1
5116 for i, file in files {
5117 for stmt in file.stmts {
5118 if stmt is ast.FnDecl && !stmt.is_method && stmt.name.starts_with('test_') {
5119 test_fn_names << stmt.name
5120 if test_file_idx == -1 {
5121 test_file_idx = i
5122 }
5123 }
5124 }
5125 }
5126 if test_fn_names.len == 0 {
5127 return none
5128 }
5129
5130 return TestMainParts{
5131 test_file_idx: test_file_idx
5132 main_fn: synthesise_test_main_fn(test_fn_names)
5133 }
5134}
5135
5136// synthesise_test_main_fn builds the synthesised `fn main()` stmt used by
5137// both the legacy `inject_test_main_parts` and the flat-aware
5138// `inject_test_main_parts_from_flat`. The body is per-test
5139// (`println('Running test: <name>...')`, `<name>()`, `println(' OK')`)
5140// followed by a single summary `println('All N tests passed.')`. Pure
5141// function — no transformer / file state read.
5142fn synthesise_test_main_fn(test_fn_names []string) ast.Stmt {
5143 mut main_stmts := []ast.Stmt{}
5144 for test_fn in test_fn_names {
5145 main_stmts << ast.ExprStmt{
5146 expr: ast.CallExpr{
5147 lhs: ast.Ident{
5148 name: 'println'
5149 }
5150 args: [
5151 ast.Expr(ast.StringLiteral{
5152 kind: .v
5153 value: quote_v_string_literal('Running test: ${test_fn}...')
5154 }),
5155 ]
5156 }
5157 }
5158 main_stmts << ast.ExprStmt{
5159 expr: ast.CallExpr{
5160 lhs: ast.Ident{
5161 name: test_fn
5162 }
5163 }
5164 }
5165 main_stmts << ast.ExprStmt{
5166 expr: ast.CallExpr{
5167 lhs: ast.Ident{
5168 name: 'println'
5169 }
5170 args: [
5171 ast.Expr(ast.StringLiteral{
5172 kind: .v
5173 value: quote_v_string_literal(' OK')
5174 }),
5175 ]
5176 }
5177 }
5178 }
5179 main_stmts << ast.ExprStmt{
5180 expr: ast.CallExpr{
5181 lhs: ast.Ident{
5182 name: 'println'
5183 }
5184 args: [
5185 ast.Expr(ast.StringLiteral{
5186 kind: .v
5187 value: quote_v_string_literal('All ${test_fn_names.len} tests passed.')
5188 }),
5189 ]
5190 }
5191 }
5192 return ast.Stmt(ast.FnDecl{
5193 name: 'main'
5194 stmts: main_stmts
5195 })
5196}
5197
5198// inject_test_main_parts_from_flat is the flat-cursor sibling of
5199// `inject_test_main_parts`. Walks `flat.files` via FileCursor for (a) an
5200// existing top-level user `main` FnDecl (returns `none` if found) and
5201// (b) every top-level `test_*` FnDecl name in declaration order. Both
5202// scans use `.stmt_fn_decl` kind + `flag_is_method` filter + `name()` /
5203// `.starts_with('test_')`. Returns the same `TestMainParts` shape as the
5204// legacy `_parts`; reuses `synthesise_test_main_fn` for the body, so
5205// bit-equality is by-construction (same payload, same FnDecl shape).
5206//
5207// Second flat-aware port (s152, follows s151's
5208// `inject_embed_file_helper_parts_from_flat`). Pinned by 3-case test in
5209// `inject_test_main_to_flat_test.v`.
5210pub fn (mut t Transformer) inject_test_main_parts_from_flat(flat &ast.FlatAst) ?TestMainParts {
5211 for i in 0 .. flat.files.len {
5212 stmts := flat.file_cursor(i).stmts()
5213 for j in 0 .. stmts.len() {
5214 c := stmts.at(j)
5215 if c.kind() == .stmt_fn_decl && !c.flag(ast.flag_is_method) && c.name() == 'main' {
5216 return none
5217 }
5218 }
5219 }
5220
5221 mut test_fn_names := []string{}
5222 mut test_file_idx := -1
5223 for i in 0 .. flat.files.len {
5224 stmts := flat.file_cursor(i).stmts()
5225 for j in 0 .. stmts.len() {
5226 c := stmts.at(j)
5227 if c.kind() == .stmt_fn_decl && !c.flag(ast.flag_is_method)
5228 && c.name().starts_with('test_') {
5229 test_fn_names << c.name()
5230 if test_file_idx == -1 {
5231 test_file_idx = i
5232 }
5233 }
5234 }
5235 }
5236 if test_fn_names.len == 0 {
5237 return none
5238 }
5239
5240 return TestMainParts{
5241 test_file_idx: test_file_idx
5242 main_fn: synthesise_test_main_fn(test_fn_names)
5243 }
5244}
5245
5246// inject_test_main_to_flat is the FlatBuilder-side splice counterpart to
5247// the legacy `inject_test_main(mut []ast.File)` mutator. Emits the
5248// synthesised `main` FnDecl into `out` via `out.emit_stmt`, then folds
5249// the resulting stmt id into the test-file root via
5250// `out.append_file_stmts(test_file_idx, [main_id])` (s150's seed primitive).
5251//
5252// Bit-equal w.r.t. `signature()` to running legacy
5253// `inject_test_main(mut files)` followed by `ast.flatten_files(files)`:
5254// same test-file routing, same synthesised main payload, same final
5255// file root edges (attrs / imports unchanged, stmts list extended by
5256// the same single stmt id).
5257pub fn (mut t Transformer) inject_test_main_to_flat(mut out ast.FlatBuilder) {
5258 parts := t.inject_test_main_parts_from_flat(&out.flat) or { return }
5259 main_id := out.emit_stmt(parts.main_fn)
5260 out.append_file_stmts(parts.test_file_idx, [main_id])
5261}
5262
5263fn (mut t Transformer) inject_test_main(mut files []ast.File) {
5264 parts := t.inject_test_main_parts(files) or { return }
5265 file := files[parts.test_file_idx]
5266 mut new_stmts := []ast.Stmt{cap: file.stmts.len + 1}
5267 for stmt in file.stmts {
5268 new_stmts << stmt
5269 }
5270 new_stmts << parts.main_fn
5271 files[parts.test_file_idx] = ast.File{
5272 attributes: file.attributes
5273 mod: file.mod
5274 name: file.name
5275 stmts: new_stmts
5276 imports: file.imports
5277 }
5278}
5279
5280// runtime_const_init_main_calls_parts is the pure-computation extraction of
5281// the per-main init-call list previously inline in
5282// `inject_main_runtime_const_init_calls`. Returns the ordered list of
5283// `__v_init_consts_<mod>()` + `<mod>__init()` call ExprStmts that should be
5284// prepended to main(): first the runtime-const init calls (one per
5285// `t.runtime_const_modules` entry that has a registered fn_name; gated by
5286// `uses_minimal_x64_runtime` + `imported_runtime_init_modules` on minimal x64
5287// runtimes), then the module-level `init()` calls (one per module seen in
5288// files that declares a non-method `init` fn; gated by the same minimal
5289// runtime filter; de-duped per module).
5290//
5291// Does NOT mutate `files` or any caller state. Caller decides whether to
5292// splice the result into a legacy `[]ast.File` (via
5293// `inject_main_runtime_const_init_calls`) or into a `FlatBuilder` (future
5294// `post_pass_to_flat`).
5295//
5296// Second Phase 5 (post_pass port) `_parts` extraction (follows s144's
5297// `runtime_const_init_fn_stmts_parts`). Bit-equal: same iteration order
5298// over `runtime_const_modules` + `files`, same de-dup map semantics.
5299pub fn (mut t Transformer) runtime_const_init_main_calls_parts(files []ast.File) []ast.Stmt {
5300 mut init_calls := []ast.Stmt{}
5301 mut main_const_calls := []ast.Stmt{}
5302 mut seen_init_mods := map[string]bool{}
5303 minimal_x64_runtime := t.uses_minimal_x64_runtime()
5304 init_modules := if minimal_x64_runtime {
5305 t.imported_runtime_init_modules(files)
5306 } else {
5307 map[string]bool{}
5308 }
5309 main_mod := main_module_name_from_files(files)
5310 for mod in t.runtime_const_modules {
5311 if minimal_x64_runtime && mod !in init_modules {
5312 continue
5313 }
5314 fn_name := t.runtime_const_init_fn_name[mod] or { continue }
5315 call_name := runtime_const_init_call_name(mod, fn_name)
5316 call_stmt := ast.ExprStmt{
5317 expr: ast.CallExpr{
5318 lhs: ast.Ident{
5319 name: call_name
5320 }
5321 }
5322 }
5323 if normalized_module_name(mod) == main_mod {
5324 main_const_calls << call_stmt
5325 } else {
5326 init_calls << call_stmt
5327 }
5328 }
5329 for file in files {
5330 if file.mod in seen_init_mods {
5331 continue
5332 }
5333 if minimal_x64_runtime && file.mod !in init_modules {
5334 continue
5335 }
5336 for stmt in file.stmts {
5337 if stmt is ast.FnDecl && !stmt.is_method && stmt.name == 'init' {
5338 seen_init_mods[file.mod] = true
5339 init_calls << ast.ExprStmt{
5340 expr: ast.CallExpr{
5341 lhs: ast.Ident{
5342 name: module_init_call_name(file.mod)
5343 }
5344 }
5345 }
5346 break
5347 }
5348 }
5349 }
5350 init_calls << main_const_calls
5351 return init_calls
5352}
5353
5354fn normalized_module_name(mod string) string {
5355 return if mod == '' { 'main' } else { mod }
5356}
5357
5358fn main_module_name_from_files(files []ast.File) string {
5359 for file in files {
5360 for stmt in file.stmts {
5361 if stmt is ast.FnDecl && !stmt.is_method && stmt.name == 'main' {
5362 return normalized_module_name(file.mod)
5363 }
5364 }
5365 }
5366 return 'main'
5367}
5368
5369fn (mut t Transformer) inject_main_runtime_const_init_calls(mut files []ast.File) {
5370 init_calls := t.runtime_const_init_main_calls_parts(files)
5371 if init_calls.len == 0 {
5372 return
5373 }
5374 for i, file in files {
5375 mut changed := false
5376 mut new_stmts := []ast.Stmt{cap: file.stmts.len}
5377 for stmt in file.stmts {
5378 if stmt is ast.FnDecl && !stmt.is_method && stmt.name == 'main' {
5379 mut fn_stmts := []ast.Stmt{cap: init_calls.len + stmt.stmts.len}
5380 for call_stmt in init_calls {
5381 fn_stmts << call_stmt
5382 }
5383 for fn_stmt in stmt.stmts {
5384 fn_stmts << fn_stmt
5385 }
5386 new_stmts << ast.FnDecl{
5387 attributes: stmt.attributes
5388 is_public: stmt.is_public
5389 is_method: stmt.is_method
5390 is_static: stmt.is_static
5391 receiver: stmt.receiver
5392 language: stmt.language
5393 name: stmt.name
5394 typ: stmt.typ
5395 stmts: fn_stmts
5396 pos: stmt.pos
5397 }
5398 changed = true
5399 continue
5400 }
5401 new_stmts << stmt
5402 }
5403 if changed {
5404 files[i] = ast.File{
5405 attributes: file.attributes
5406 mod: file.mod
5407 name: file.name
5408 stmts: new_stmts
5409 imports: file.imports
5410 }
5411 break
5412 }
5413 }
5414}
5415
5416// MainRuntimeConstInitParts carries the locator (file_idx + stmt_idx of the
5417// top-level `fn main()` FnDecl, plus its current FlatNodeId) and the
5418// pre-built init-call ExprStmt list that will be prepended to main's body.
5419// Returned by `inject_main_runtime_const_init_parts_from_flat`; consumed by
5420// `inject_main_runtime_const_init_to_flat`.
5421pub struct MainRuntimeConstInitParts {
5422pub:
5423 main_file_idx int
5424 main_stmt_idx int
5425 main_fn_id ast.FlatNodeId
5426 init_calls []ast.Stmt
5427}
5428
5429// runtime_const_init_main_calls_parts_from_flat is the flat-cursor sibling of
5430// `runtime_const_init_main_calls_parts`. Builds the same ordered list of
5431// `__v_init_consts_<mod>()` + `<mod>__init()` ExprStmt calls, but reads the
5432// per-file `init` fn presence from `flat.files` via FileCursor (kind ==
5433// `.stmt_fn_decl`, `!flag(ast.flag_is_method)`, `name() == 'init'`) instead
5434// of walking `[]ast.File`. The runtime-const portion is unchanged — it
5435// iterates `t.runtime_const_modules` + `t.runtime_const_init_fn_name`
5436// (Transformer state, not file content).
5437//
5438// Pure: does not mutate the flat AST or transformer state.
5439pub fn (mut t Transformer) runtime_const_init_main_calls_parts_from_flat(flat &ast.FlatAst) []ast.Stmt {
5440 mut init_calls := []ast.Stmt{}
5441 mut main_const_calls := []ast.Stmt{}
5442 mut seen_init_mods := map[string]bool{}
5443 minimal_x64_runtime := t.uses_minimal_x64_runtime()
5444 init_modules := if minimal_x64_runtime {
5445 t.imported_runtime_init_modules_from_flat(flat)
5446 } else {
5447 map[string]bool{}
5448 }
5449 main_mod := main_module_name_from_flat(flat)
5450 for mod in t.runtime_const_modules {
5451 if minimal_x64_runtime && mod !in init_modules {
5452 continue
5453 }
5454 fn_name := t.runtime_const_init_fn_name[mod] or { continue }
5455 call_name := runtime_const_init_call_name(mod, fn_name)
5456 call_stmt := ast.ExprStmt{
5457 expr: ast.CallExpr{
5458 lhs: ast.Ident{
5459 name: call_name
5460 }
5461 }
5462 }
5463 if normalized_module_name(mod) == main_mod {
5464 main_const_calls << call_stmt
5465 } else {
5466 init_calls << call_stmt
5467 }
5468 }
5469 for i in 0 .. flat.files.len {
5470 fc := flat.file_cursor(i)
5471 mod := fc.mod()
5472 if mod in seen_init_mods {
5473 continue
5474 }
5475 if minimal_x64_runtime && mod !in init_modules {
5476 continue
5477 }
5478 stmts := fc.stmts()
5479 for j in 0 .. stmts.len() {
5480 c := stmts.at(j)
5481 if c.kind() == .stmt_fn_decl && !c.flag(ast.flag_is_method) && c.name() == 'init' {
5482 seen_init_mods[mod] = true
5483 init_calls << ast.ExprStmt{
5484 expr: ast.CallExpr{
5485 lhs: ast.Ident{
5486 name: module_init_call_name(mod)
5487 }
5488 }
5489 }
5490 break
5491 }
5492 }
5493 }
5494 init_calls << main_const_calls
5495 return init_calls
5496}
5497
5498fn main_module_name_from_flat(flat &ast.FlatAst) string {
5499 for i in 0 .. flat.files.len {
5500 fc := flat.file_cursor(i)
5501 stmts := fc.stmts()
5502 for j in 0 .. stmts.len() {
5503 c := stmts.at(j)
5504 if c.kind() == .stmt_fn_decl && !c.flag(ast.flag_is_method) && c.name() == 'main' {
5505 return normalized_module_name(fc.mod())
5506 }
5507 }
5508 }
5509 return 'main'
5510}
5511
5512// inject_main_runtime_const_init_parts_from_flat is the flat-cursor sibling of
5513// the implicit locator in `inject_main_runtime_const_init_calls`. Returns the
5514// (file_idx, stmt_idx, main_fn_id, init_calls) tuple needed to drive the
5515// flat-aware splice. Returns `none` when no init calls are needed OR no
5516// top-level `fn main()` is present in any file.
5517//
5518// The "first match wins" semantics of the legacy loop are preserved: we
5519// scan files in declaration order and stop at the first non-method
5520// `fn main()` we encounter.
5521//
5522// Fourth flat-aware port (s155, follows s151/s152/s153). Pinned by the
5523// 4-case test in `inject_main_runtime_const_init_to_flat_test.v`.
5524pub fn (mut t Transformer) inject_main_runtime_const_init_parts_from_flat(flat &ast.FlatAst) ?MainRuntimeConstInitParts {
5525 init_calls := t.runtime_const_init_main_calls_parts_from_flat(flat)
5526 if init_calls.len == 0 {
5527 return none
5528 }
5529 for i in 0 .. flat.files.len {
5530 fc := flat.file_cursor(i)
5531 stmts := fc.stmts()
5532 for j in 0 .. stmts.len() {
5533 c := stmts.at(j)
5534 if c.kind() == .stmt_fn_decl && !c.flag(ast.flag_is_method) && c.name() == 'main' {
5535 return MainRuntimeConstInitParts{
5536 main_file_idx: i
5537 main_stmt_idx: j
5538 main_fn_id: c.id
5539 init_calls: init_calls
5540 }
5541 }
5542 }
5543 }
5544 return none
5545}
5546
5547// inject_main_runtime_const_init_to_flat is the FlatBuilder-side splice
5548// counterpart to the legacy `inject_main_runtime_const_init_calls(mut []ast.File)`.
5549// Locates the `fn main()` via `inject_main_runtime_const_init_parts_from_flat`,
5550// emits each init-call ExprStmt via `out.emit_stmt`, prepends them to main's
5551// body via `out.prepend_to_fn_body(main_fn_id, init_call_ids)` (s154's seed
5552// primitive, returns a NEW fn_decl_id), then rewires the enclosing file's
5553// stmts list via `out.replace_file_stmt(file_idx, stmt_idx, new_fn_id)`.
5554//
5555// Bit-equal w.r.t. `signature()` to running legacy
5556// `inject_main_runtime_const_init_calls(mut files)` followed by
5557// `ast.flatten_files(files)`: same first-main routing, same init-call
5558// payload + ordering (init_calls first, original main body after), same
5559// final file root edges (attrs / imports unchanged, stmts list one entry
5560// swapped from old fn_decl_id to new fn_decl_id).
5561pub fn (mut t Transformer) inject_main_runtime_const_init_to_flat(mut out ast.FlatBuilder) {
5562 parts := t.inject_main_runtime_const_init_parts_from_flat(&out.flat) or { return }
5563 mut init_call_ids := []ast.FlatNodeId{cap: parts.init_calls.len}
5564 for call_stmt in parts.init_calls {
5565 init_call_ids << out.emit_stmt(call_stmt)
5566 }
5567 new_fn_id := out.prepend_to_fn_body(parts.main_fn_id, init_call_ids)
5568 out.replace_file_stmt(parts.main_file_idx, parts.main_stmt_idx, new_fn_id)
5569}
5570
5571fn (mut t Transformer) transform_file(file ast.File) ast.File {
5572 if t.skip_native_backend_transform_file(file) {
5573 return file
5574 }
5575 t.enter_file_context(file)
5576 stmts := t.transform_stmts(file.stmts)
5577 return ast.File{
5578 attributes: file.attributes
5579 mod: file.mod
5580 name: file.name
5581 stmts: stmts
5582 imports: file.imports
5583 }
5584}
5585
5586fn (mut t Transformer) transform_stmt(stmt ast.Stmt) ast.Stmt {
5587 if !stmt_has_valid_data(stmt) {
5588 return stmt
5589 }
5590 // Check for OrExpr assignment that needs expansion
5591 if stmt is ast.AssignStmt {
5592 if expanded := t.try_expand_or_expr_assign(stmt) {
5593 return expanded
5594 }
5595 // Check for map index assignment: m[key] = val -> map__set(&m, &key, &val)
5596 if transformed := t.try_transform_map_index_assign(stmt) {
5597 return transformed
5598 }
5599 }
5600 return match stmt {
5601 ast.AssignStmt {
5602 t.transform_assign_stmt(stmt)
5603 }
5604 ast.BlockStmt {
5605 ast.BlockStmt{
5606 stmts: t.transform_stmts(stmt.stmts)
5607 }
5608 }
5609 ast.ComptimeStmt {
5610 // Keep ComptimeStmt wrapper for $for — cleanc handles it at codegen time
5611 if stmt.stmt is ast.ForStmt {
5612 for_stmt := stmt.stmt as ast.ForStmt
5613 ast.Stmt(ast.ComptimeStmt{
5614 stmt: ast.Stmt(ast.ForStmt{
5615 init: for_stmt.init
5616 cond: for_stmt.cond
5617 post: for_stmt.post
5618 stmts: t.transform_stmts(for_stmt.stmts)
5619 })
5620 })
5621 } else {
5622 t.transform_stmt(stmt.stmt)
5623 }
5624 }
5625 ast.DeferStmt {
5626 ast.DeferStmt{
5627 mode: stmt.mode
5628 stmts: t.transform_stmts(stmt.stmts)
5629 }
5630 }
5631 ast.ExprStmt {
5632 // When IfExpr is directly in a statement position (ExprStmt), don't lower
5633 // to temp variable — it's not used as a value expression.
5634 is_direct_if := stmt.expr is ast.IfExpr
5635 saved_skip := t.skip_if_value_lowering
5636 if is_direct_if {
5637 t.skip_if_value_lowering = true
5638 }
5639 result := ast.Stmt(ast.ExprStmt{
5640 expr: t.transform_expr(stmt.expr)
5641 })
5642 t.skip_if_value_lowering = saved_skip
5643 result
5644 }
5645 ast.FnDecl {
5646 t.transform_fn_decl(stmt)
5647 }
5648 ast.ForStmt {
5649 t.transform_for_stmt(stmt)
5650 }
5651 ast.ForInStmt {
5652 t.transform_for_in_stmt(stmt)
5653 }
5654 ast.LabelStmt {
5655 ast.LabelStmt{
5656 name: stmt.name
5657 stmt: t.transform_stmt(stmt.stmt)
5658 }
5659 }
5660 ast.ReturnStmt {
5661 t.transform_return_stmt(stmt)
5662 }
5663 ast.ConstDecl {
5664 t.transform_const_decl(stmt)
5665 }
5666 ast.GlobalDecl {
5667 t.transform_global_decl(stmt)
5668 }
5669 ast.StructDecl {
5670 ast.Stmt(t.transform_struct_decl(stmt))
5671 }
5672 ast.AssertStmt {
5673 // Should have been expanded in transform_stmts; fallback pass-through
5674 ast.AssertStmt{
5675 expr: t.transform_expr(stmt.expr)
5676 }
5677 }
5678 else {
5679 stmt
5680 }
5681 }
5682}
5683
5684fn (mut t Transformer) append_transformed_stmt(mut result []ast.Stmt, stmt ast.Stmt) {
5685 transformed := t.transform_stmt(stmt)
5686 if t.pending_stmts.len > 0 {
5687 for ps in t.pending_stmts {
5688 result << ps
5689 }
5690 t.pending_stmts.clear()
5691 }
5692 result << transformed
5693}
5694
5695fn (mut t Transformer) transform_stmts(stmts []ast.Stmt) []ast.Stmt {
5696 mut result := []ast.Stmt{cap: stmts.len}
5697 is_native_be := t.is_native_be
5698 block_smartcast_depth := t.smartcast_stack.len
5699 // Lazy snapshot: most blocks enter with an empty smartcast stack. Cloning an
5700 // empty stack/map is cheap but still allocates; multiply by tens of thousands
5701 // of blocks and it adds up. Only snapshot when there is state to restore.
5702 has_smartcast_state := block_smartcast_depth > 0
5703 block_smartcast_stack := if has_smartcast_state {
5704 t.smartcast_stack.clone()
5705 } else {
5706 []SmartcastContext{}
5707 }
5708 block_smartcast_counts := if has_smartcast_state {
5709 t.smartcast_expr_counts.clone()
5710 } else {
5711 map[string]int{}
5712 }
5713 for stmt in stmts {
5714 if t.smartcast_stack.len < block_smartcast_depth {
5715 t.smartcast_stack = block_smartcast_stack.clone()
5716 t.smartcast_expr_counts = block_smartcast_counts.clone()
5717 } else if t.smartcast_stack.len > block_smartcast_depth {
5718 t.truncate_smartcasts(block_smartcast_depth)
5719 }
5720 if stmt is ast.FnDecl && t.omit_backend_generic_decl(stmt) {
5721 continue
5722 }
5723 // Check for OrExpr assignment that expands to multiple statements
5724 if stmt is ast.AssignStmt {
5725 assign_stmt := stmt as ast.AssignStmt
5726 // Expand comptime $if assignment: `mut res := $if flag ? { expr1 } $else { stmts... ; expr_n }`
5727 // When the selected branch has multiple statements, inline the statements and use
5728 // the last expression as the assignment RHS.
5729 if assign_stmt.rhs.len == 1 && assign_stmt.rhs[0] is ast.ComptimeExpr {
5730 comptime_expr := assign_stmt.rhs[0] as ast.ComptimeExpr
5731 if comptime_expr.expr is ast.IfExpr {
5732 comptime_if := comptime_expr.expr as ast.IfExpr
5733 if !t.can_eval_comptime_cond(comptime_if.cond) {
5734 t.append_transformed_stmt(mut result, ast.Stmt(assign_stmt))
5735 continue
5736 }
5737 selected := t.resolve_comptime_if_stmts(comptime_if)
5738 if selected.len > 0 {
5739 // Inline all but the last statement
5740 for i := 0; i < selected.len - 1; i++ {
5741 t.append_transformed_stmt(mut result, selected[i])
5742 }
5743 // Use the last statement's expression as the assignment RHS
5744 last_stmt := selected[selected.len - 1]
5745 if last_stmt is ast.ExprStmt {
5746 last_expr_stmt := last_stmt as ast.ExprStmt
5747 t.append_transformed_stmt(mut result, ast.Stmt(ast.AssignStmt{
5748 op: assign_stmt.op
5749 lhs: assign_stmt.lhs
5750 rhs: [t.transform_expr(last_expr_stmt.expr)]
5751 pos: assign_stmt.pos
5752 }))
5753 continue
5754 } else if last_stmt is ast.AssignStmt {
5755 last_assign_stmt := last_stmt as ast.AssignStmt
5756 t.append_transformed_stmt(mut result, ast.Stmt(ast.AssignStmt{
5757 op: assign_stmt.op
5758 lhs: assign_stmt.lhs
5759 rhs: last_assign_stmt.rhs
5760 pos: assign_stmt.pos
5761 }))
5762 continue
5763 }
5764 }
5765 }
5766 }
5767 if is_native_be && assign_stmt.rhs.len == 1 && assign_stmt.lhs.len == 1 {
5768 lhs_name := t.get_var_name(assign_stmt.lhs[0])
5769 if lhs_name != '' {
5770 if concrete := t.get_interface_assignment_concrete_type(assign_stmt.rhs[0]) {
5771 t.interface_concrete_types[lhs_name] = concrete
5772 } else if assign_stmt.op in [.assign, .decl_assign] {
5773 t.interface_concrete_types.delete(lhs_name)
5774 }
5775 }
5776 }
5777 // Native backends (arm64/x64): lower interface casts.
5778 // `shape1 := Shape(rect)` → `shape1 := rect` and record concrete type mapping.
5779 if is_native_be && assign_stmt.rhs.len == 1 && assign_stmt.lhs.len == 1
5780 && assign_stmt.lhs[0] is ast.Ident && t.is_interface_cast(assign_stmt.rhs[0]) {
5781 inner := t.get_interface_cast_inner_expr(assign_stmt.rhs[0]) or {
5782 assign_stmt.rhs[0]
5783 }
5784 lhs_name := (assign_stmt.lhs[0] as ast.Ident).name
5785 // Get the concrete type name from the value being cast
5786 if concrete := t.get_expr_type_name(inner) {
5787 t.interface_concrete_types[lhs_name] = concrete
5788 }
5789 // Replace the interface cast with just the inner value
5790 t.append_transformed_stmt(mut result, ast.Stmt(ast.AssignStmt{
5791 op: assign_stmt.op
5792 lhs: assign_stmt.lhs
5793 rhs: [inner]
5794 pos: assign_stmt.pos
5795 }))
5796 continue
5797 }
5798 // Native backends: expand sincos(x) → sin(x), cos(x) since sincos uses
5799 // ChebSeries struct constants that aren't initialized by the native backend.
5800 // The separate sin/cos functions use C library calls via .c.v overrides.
5801 if is_native_be && assign_stmt.lhs.len >= 2 && assign_stmt.rhs.len == 1 {
5802 if sincos_arg := t.try_extract_sincos_arg(assign_stmt.rhs[0]) {
5803 lhs0 := assign_stmt.lhs[0]
5804 lhs1 := assign_stmt.lhs[1]
5805 is_blank0 := lhs0 is ast.Ident && (lhs0 as ast.Ident).name == '_'
5806 is_blank1 := lhs1 is ast.Ident && (lhs1 as ast.Ident).name == '_'
5807 if !is_blank0 {
5808 t.append_transformed_stmt(mut result, ast.Stmt(ast.AssignStmt{
5809 op: assign_stmt.op
5810 lhs: [lhs0]
5811 rhs: [
5812 ast.Expr(ast.CallExpr{
5813 lhs: ast.Expr(ast.Ident{
5814 name: 'sin'
5815 })
5816 args: [sincos_arg]
5817 pos: assign_stmt.pos
5818 }),
5819 ]
5820 pos: assign_stmt.pos
5821 }))
5822 }
5823 if !is_blank1 {
5824 t.append_transformed_stmt(mut result, ast.Stmt(ast.AssignStmt{
5825 op: assign_stmt.op
5826 lhs: [lhs1]
5827 rhs: [
5828 ast.Expr(ast.CallExpr{
5829 lhs: ast.Expr(ast.Ident{
5830 name: 'cos'
5831 })
5832 args: [sincos_arg]
5833 pos: assign_stmt.pos
5834 }),
5835 ]
5836 pos: assign_stmt.pos
5837 }))
5838 }
5839 continue
5840 }
5841 }
5842 if expanded_or_assign := t.try_expand_or_expr_assign_stmts(&assign_stmt) {
5843 // Note: expand_direct_or_expr_assign already transforms expressions internally,
5844 // so we don't call transform_stmt again to avoid double transformation
5845 // (which would cause smartcasts to be applied twice)
5846 if t.pending_stmts.len > 0 {
5847 for ps in t.pending_stmts {
5848 result << ps
5849 }
5850 t.pending_stmts.clear()
5851 }
5852 result << expanded_or_assign
5853 continue
5854 }
5855 // Check for tuple if-expression: x, y, w, h := if cond { a, b, c, d } else { e, f, g, h }
5856 if expanded_tuple_if := t.try_expand_tuple_if_assign_stmts(assign_stmt) {
5857 for exp_stmt in expanded_tuple_if {
5858 t.append_transformed_stmt(mut result, exp_stmt)
5859 }
5860 continue
5861 }
5862 // Lower multi-value call assignment: a, b = call() → _tuple_t = call(); a = _tuple_t.arg0; b = _tuple_t.arg1
5863 if expanded_tuple_call := t.try_expand_tuple_call_assign(assign_stmt) {
5864 for exp_stmt in expanded_tuple_call {
5865 t.append_transformed_stmt(mut result, exp_stmt)
5866 }
5867 continue
5868 }
5869 // Check for if-guard expression: x := if r := map[key] { r } else { default }
5870 if expanded_if_guard_assign := t.try_expand_if_guard_assign_stmts(assign_stmt) {
5871 for exp_stmt in expanded_if_guard_assign {
5872 t.append_transformed_stmt(mut result, exp_stmt)
5873 }
5874 continue
5875 }
5876 // Check for if-expression assignment: x = if cond { a } else { b }
5877 // Transform to a statement-form if that assigns in each branch.
5878 if expanded_if_expr_assign := t.try_expand_if_expr_assign_stmts(assign_stmt) {
5879 for exp_stmt in expanded_if_expr_assign {
5880 t.append_transformed_stmt(mut result, exp_stmt)
5881 }
5882 continue
5883 }
5884 }
5885 // Expand compile-time $if at the statement level
5886 if stmt is ast.ExprStmt {
5887 if stmt.expr is ast.ComptimeExpr {
5888 if stmt.expr.expr is ast.IfExpr {
5889 if t.can_eval_comptime_cond(stmt.expr.expr.cond) {
5890 selected := t.resolve_comptime_if_stmts(stmt.expr.expr)
5891 // Process through transform_stmts to handle nested $if blocks
5892 transformed := t.transform_stmts(selected)
5893 for s in transformed {
5894 result << s
5895 }
5896 continue
5897 }
5898 // Can't evaluate — transform body stmts and keep the comptime wrapper
5899 transformed_comptime := t.transform_comptime_if_bodies(stmt.expr.expr)
5900 result << ast.Stmt(ast.ExprStmt{
5901 expr: ast.Expr(ast.ComptimeExpr{
5902 expr: ast.Expr(transformed_comptime)
5903 })
5904 })
5905 continue
5906 }
5907 }
5908 }
5909 // Check for OrExpr in expression statements (e.g., println(may_fail() or { 0 }))
5910 if stmt is ast.ExprStmt {
5911 if expanded_or_stmt := t.try_expand_or_expr_stmt(stmt) {
5912 // Note: expand_single_or_expr already transforms expressions internally,
5913 // so we don't call transform_stmt again to avoid double transformation
5914 // (which would cause interface method _object to be added twice)
5915 if t.pending_stmts.len > 0 {
5916 for ps in t.pending_stmts {
5917 result << ps
5918 }
5919 t.pending_stmts.clear()
5920 }
5921 result << expanded_or_stmt
5922 continue
5923 }
5924 // Check for if-guard in expression statements (e.g., if attr := table[name] { ... })
5925 if expanded_if_guard_stmt := t.try_expand_if_guard_stmt(stmt) {
5926 // Note: try_expand_if_guard_stmt already transforms expressions internally,
5927 // so we don't call transform_stmt again to avoid double transformation
5928 for exp_stmt in expanded_if_guard_stmt {
5929 result << exp_stmt
5930 }
5931 continue
5932 }
5933 }
5934 // Check for flag enum .set() / .clear() calls that need statement-level lowering
5935 if stmt is ast.ExprStmt {
5936 if flag_stmt := t.try_transform_flag_enum_set_clear(stmt) {
5937 result << flag_stmt
5938 continue
5939 }
5940 }
5941 // Check for OrExpr in return statements
5942 if stmt is ast.ReturnStmt {
5943 if expanded_return_match := t.try_expand_return_match_expr(stmt) {
5944 if t.pending_stmts.len > 0 {
5945 for ps in t.pending_stmts {
5946 result << ps
5947 }
5948 t.pending_stmts.clear()
5949 }
5950 for exp_stmt in expanded_return_match {
5951 result << exp_stmt
5952 }
5953 continue
5954 }
5955 if expanded_or_return := t.try_expand_or_expr_return(stmt) {
5956 // Note: expand_single_or_expr already transforms expressions internally,
5957 // so we don't call transform_stmt again to avoid double transformation.
5958 // However, transform_expr may have populated pending_stmts (e.g., from
5959 // lower_if_expr_value), which must be drained before the return stmt.
5960 if t.pending_stmts.len > 0 {
5961 // Insert pending_stmts before the last statement (the return).
5962 for i, es in expanded_or_return {
5963 if i == expanded_or_return.len - 1 {
5964 for ps in t.pending_stmts {
5965 result << ps
5966 }
5967 t.pending_stmts.clear()
5968 }
5969 result << es
5970 }
5971 } else {
5972 result << expanded_or_return
5973 }
5974 continue
5975 }
5976 // Check for if-expression in return statements
5977 // Transform: return if cond { a } else { b }
5978 // Into: if cond { return a } else { return b }
5979 if expanded_return_if := t.try_expand_return_if_expr(stmt) {
5980 for exp_stmt in expanded_return_if {
5981 t.append_transformed_stmt(mut result, exp_stmt)
5982 }
5983 continue
5984 }
5985 }
5986 // Expand lock/rlock expressions into mutex lock/unlock calls around the body
5987 if stmt is ast.ExprStmt {
5988 if stmt.expr is ast.LockExpr {
5989 // Store in a temp variable to avoid double evaluation of expand_lock_expr.
5990 // V1's C backend compiles `result << fn_call()` as
5991 // `array__push_many(&result, fn_call().data, fn_call().len)` which calls
5992 // the function twice, causing temp_counter to advance twice and generating
5993 // mismatched variable IDs.
5994 expanded_lock := t.expand_lock_expr(stmt.expr)
5995 result << expanded_lock
5996 continue
5997 }
5998 // Expand map[key] << value to get_and_set + array_push
5999 if expanded_map_push := t.try_transform_map_index_push(stmt) {
6000 result << ast.Stmt(expanded_map_push)
6001 continue
6002 }
6003 // Expand map[key]++ / map[key]-- to compound assignment (handled by try_transform_map_index_assign)
6004 if expanded_map_postfix := t.try_transform_map_index_postfix(stmt) {
6005 result << expanded_map_postfix
6006 continue
6007 }
6008 // For native backends, transform obj.field++ / obj.field-- to compound assignment
6009 // to avoid the build_postfix code path which has issues in self-hosted binaries.
6010 if t.is_native_be {
6011 if postfix_assign := t.try_transform_selector_postfix(stmt) {
6012 result << postfix_assign
6013 continue
6014 }
6015 }
6016 }
6017 // Check for map iteration expansion
6018 if stmt is ast.ForStmt {
6019 if expanded_for_in_map := t.try_expand_for_in_map(stmt) {
6020 for exp_stmt in expanded_for_in_map {
6021 t.append_transformed_stmt(mut result, exp_stmt)
6022 }
6023 continue
6024 }
6025 }
6026 // Expand assert statements into if + eprintln + exit
6027 if stmt is ast.AssertStmt {
6028 // Store in temp variable to avoid V1 C backend double-evaluation of
6029 // `for x in fn()` which calls fn() twice per iteration.
6030 expanded_asserts := t.expand_assert_stmt(stmt)
6031 for exp_stmt in expanded_asserts {
6032 t.append_transformed_stmt(mut result, exp_stmt)
6033 // Drain pending_stmts (e.g. from filter/map expansion in assert condition)
6034 if t.pending_stmts.len > 0 {
6035 last := result.pop()
6036 for ps in t.pending_stmts {
6037 result << ps
6038 }
6039 t.pending_stmts.clear()
6040 result << last
6041 }
6042 }
6043 continue
6044 }
6045 // Transform the statement. Filter/map expression expansions may populate
6046 // pending_stmts during this call, which must be hoisted before the result.
6047 t.append_transformed_stmt(mut result, stmt)
6048 if t.pending_stmts.len > 0 {
6049 // Move the just-appended transformed statement to after pending_stmts.
6050 last := result.pop()
6051 for ps in t.pending_stmts {
6052 result << ps
6053 }
6054 t.pending_stmts.clear()
6055 result << last
6056 }
6057 }
6058 if t.smartcast_stack.len < block_smartcast_depth {
6059 t.smartcast_stack = block_smartcast_stack.clone()
6060 t.smartcast_expr_counts = block_smartcast_counts.clone()
6061 } else if t.smartcast_stack.len > block_smartcast_depth {
6062 t.truncate_smartcasts(block_smartcast_depth)
6063 }
6064 return result
6065}
6066
6067fn (t &Transformer) omit_backend_generic_decl(decl ast.FnDecl) bool {
6068 return decl_generic_param_names(decl).len > 0
6069}
6070
6071fn (mut t Transformer) transform_const_decl(decl ast.ConstDecl) ast.ConstDecl {
6072 mut fields := []ast.FieldInit{cap: decl.fields.len}
6073 for field in decl.fields {
6074 fields << ast.FieldInit{
6075 name: field.name
6076 value: t.transform_expr(field.value)
6077 }
6078 }
6079 return ast.ConstDecl{
6080 is_public: decl.is_public
6081 fields: fields
6082 }
6083}
6084
6085fn (mut t Transformer) transform_global_decl(decl ast.GlobalDecl) ast.GlobalDecl {
6086 mut fields := []ast.FieldDecl{cap: decl.fields.len}
6087 for field in decl.fields {
6088 fields << ast.FieldDecl{
6089 name: field.name
6090 typ: t.transform_expr(field.typ)
6091 value: t.transform_expr(field.value)
6092 attributes: field.attributes
6093 is_public: field.is_public
6094 is_mut: field.is_mut
6095 is_module_mut: field.is_module_mut
6096 is_interface_method: field.is_interface_method
6097 }
6098 }
6099 return ast.GlobalDecl{
6100 attributes: decl.attributes
6101 fields: fields
6102 is_public: decl.is_public
6103 }
6104}
6105
6106fn (mut t Transformer) transform_struct_decl(decl ast.StructDecl) ast.StructDecl {
6107 mut fields := []ast.FieldDecl{cap: decl.fields.len}
6108 for field in decl.fields {
6109 fields << ast.FieldDecl{
6110 name: field.name
6111 typ: t.rewrite_concrete_generic_struct_type_expr(field.typ)
6112 value: field.value
6113 attributes: field.attributes
6114 is_public: field.is_public
6115 is_mut: field.is_mut
6116 is_module_mut: field.is_module_mut
6117 is_interface_method: field.is_interface_method
6118 }
6119 }
6120 mut embedded := []ast.Expr{cap: decl.embedded.len}
6121 for item in decl.embedded {
6122 embedded << t.rewrite_concrete_generic_struct_type_expr(item)
6123 }
6124 t.register_concrete_embedded_owner_names(decl, embedded)
6125 mut implemented_types := []ast.Expr{cap: decl.implements.len}
6126 for item in decl.implements {
6127 implemented_types << t.rewrite_concrete_generic_struct_type_expr(item)
6128 }
6129 return ast.StructDecl{
6130 attributes: decl.attributes
6131 is_public: decl.is_public
6132 is_union: decl.is_union
6133 implements: implemented_types
6134 embedded: embedded
6135 language: decl.language
6136 name: decl.name
6137 generic_params: decl.generic_params
6138 fields: fields
6139 pos: decl.pos
6140 }
6141}
6142
6143fn (mut t Transformer) register_concrete_embedded_owner_names(decl ast.StructDecl, embedded []ast.Expr) {
6144 mut names := map[string]string{}
6145 for i, original in decl.embedded {
6146 if i >= embedded.len {
6147 break
6148 }
6149 base_owner := embedded_owner_name_from_type_expr(original)
6150 concrete_owner := embedded_owner_name_from_type_expr(embedded[i])
6151 if base_owner == '' || concrete_owner == '' || base_owner == concrete_owner {
6152 continue
6153 }
6154 names[base_owner] = concrete_owner
6155 }
6156 if names.len == 0 {
6157 return
6158 }
6159 struct_name := t.struct_decl_c_name_for_current_module(decl)
6160 if struct_name == '' {
6161 return
6162 }
6163 t.concrete_embedded_owner_names[struct_name] = names.clone()
6164 if struct_name.contains('__') {
6165 t.concrete_embedded_owner_names[struct_name.all_after_last('__')] = names.clone()
6166 }
6167}
6168
6169fn (t &Transformer) struct_decl_c_name_for_current_module(decl ast.StructDecl) string {
6170 if decl.name.contains('__') {
6171 return decl.name
6172 }
6173 if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin' {
6174 return '${t.cur_module}__${decl.name}'
6175 }
6176 return decl.name
6177}
6178
6179fn embedded_owner_name_from_type_expr(expr ast.Expr) string {
6180 match expr {
6181 ast.GenericArgs {
6182 return embedded_owner_name_from_type_expr(expr.lhs)
6183 }
6184 ast.GenericArgOrIndexExpr {
6185 return embedded_owner_name_from_type_expr(expr.lhs)
6186 }
6187 ast.Ident {
6188 return embedded_concrete_owner_name_from_type_name(expr.name)
6189 }
6190 ast.IndexExpr {
6191 return embedded_owner_name_from_type_expr(expr.lhs)
6192 }
6193 ast.ModifierExpr {
6194 return embedded_owner_name_from_type_expr(expr.expr)
6195 }
6196 ast.PrefixExpr {
6197 return embedded_owner_name_from_type_expr(expr.expr)
6198 }
6199 ast.SelectorExpr {
6200 return embedded_concrete_owner_name_from_type_name(expr.rhs.name)
6201 }
6202 ast.Type {
6203 match expr {
6204 ast.GenericType {
6205 return embedded_owner_name_from_type_expr(expr.name)
6206 }
6207 ast.PointerType {
6208 return embedded_owner_name_from_type_expr(expr.base_type)
6209 }
6210 else {}
6211 }
6212 }
6213 else {}
6214 }
6215
6216 return ''
6217}
6218
6219fn embedded_concrete_owner_name_from_type_name(name string) string {
6220 if name == '' {
6221 return ''
6222 }
6223 mut field_name := name
6224 if field_name.contains('.') {
6225 field_name = field_name.all_after_last('.')
6226 }
6227 if suffix_idx := field_name.index('_T_') {
6228 mut base_name := field_name[..suffix_idx]
6229 if base_name.contains('__') {
6230 base_name = base_name.all_after_last('__')
6231 }
6232 return base_name + field_name[suffix_idx..]
6233 }
6234 if field_name.contains('__') {
6235 return field_name.all_after_last('__')
6236 }
6237 return field_name
6238}
6239
6240fn (t &Transformer) concrete_embedded_owner_name(struct_name string, owner string) ?string {
6241 if owner == '' {
6242 return none
6243 }
6244 for key in [struct_name, struct_name.all_after_last('__')] {
6245 if names := t.concrete_embedded_owner_names[key] {
6246 if concrete_owner := names[owner] {
6247 return concrete_owner
6248 }
6249 }
6250 }
6251 return none
6252}
6253
6254fn (t &Transformer) rewrite_embedded_default_field_name(struct_name string, field_name string) string {
6255 if !field_name.contains('.') {
6256 return field_name
6257 }
6258 owner := field_name.all_before('.')
6259 rest := field_name.all_after('.')
6260 if concrete_owner := t.concrete_embedded_owner_name(struct_name, owner) {
6261 return '${concrete_owner}.${rest}'
6262 }
6263 return field_name
6264}
6265
6266fn (mut t Transformer) rewrite_concrete_generic_struct_type_expr(expr ast.Expr) ast.Expr {
6267 if concrete := t.concrete_generic_struct_type_expr(expr) {
6268 return concrete
6269 }
6270 match expr {
6271 ast.GenericArgOrIndexExpr {
6272 rewritten := ast.Expr(ast.GenericArgOrIndexExpr{
6273 lhs: t.rewrite_concrete_generic_struct_type_expr(expr.lhs)
6274 expr: t.rewrite_concrete_generic_struct_type_expr(expr.expr)
6275 pos: expr.pos
6276 })
6277 if concrete := t.concrete_generic_struct_type_expr(rewritten) {
6278 return concrete
6279 }
6280 return rewritten
6281 }
6282 ast.GenericArgs {
6283 mut args := []ast.Expr{cap: expr.args.len}
6284 for arg in expr.args {
6285 args << t.rewrite_concrete_generic_struct_type_expr(arg)
6286 }
6287 rewritten := ast.Expr(ast.GenericArgs{
6288 lhs: t.rewrite_concrete_generic_struct_type_expr(expr.lhs)
6289 args: args
6290 pos: expr.pos
6291 })
6292 if concrete := t.concrete_generic_struct_type_expr(rewritten) {
6293 return concrete
6294 }
6295 return rewritten
6296 }
6297 ast.ModifierExpr {
6298 return ast.Expr(ast.ModifierExpr{
6299 kind: expr.kind
6300 expr: t.rewrite_concrete_generic_struct_type_expr(expr.expr)
6301 pos: expr.pos
6302 })
6303 }
6304 ast.PrefixExpr {
6305 return ast.Expr(ast.PrefixExpr{
6306 op: expr.op
6307 expr: t.rewrite_concrete_generic_struct_type_expr(expr.expr)
6308 pos: expr.pos
6309 })
6310 }
6311 ast.Type {
6312 rewritten := ast.Expr(t.rewrite_concrete_generic_struct_type_node(expr))
6313 if concrete := t.concrete_generic_struct_type_expr(rewritten) {
6314 return concrete
6315 }
6316 return rewritten
6317 }
6318 else {
6319 return expr
6320 }
6321 }
6322}
6323
6324fn (mut t Transformer) rewrite_concrete_generic_struct_type_node(typ ast.Type) ast.Type {
6325 match typ {
6326 ast.ArrayType {
6327 return ast.Type(ast.ArrayType{
6328 elem_type: t.rewrite_concrete_generic_struct_type_expr(typ.elem_type)
6329 })
6330 }
6331 ast.ArrayFixedType {
6332 return ast.Type(ast.ArrayFixedType{
6333 len: typ.len
6334 elem_type: t.rewrite_concrete_generic_struct_type_expr(typ.elem_type)
6335 })
6336 }
6337 ast.ChannelType {
6338 return ast.Type(ast.ChannelType{
6339 cap: typ.cap
6340 elem_type: t.rewrite_concrete_generic_struct_type_expr(typ.elem_type)
6341 })
6342 }
6343 ast.FnType {
6344 mut params := []ast.Parameter{cap: typ.params.len}
6345 for param in typ.params {
6346 params << ast.Parameter{
6347 name: param.name
6348 typ: t.rewrite_concrete_generic_struct_type_expr(param.typ)
6349 is_mut: param.is_mut
6350 pos: param.pos
6351 }
6352 }
6353 return ast.Type(ast.FnType{
6354 generic_params: typ.generic_params
6355 params: params
6356 return_type: t.rewrite_concrete_generic_struct_type_expr(typ.return_type)
6357 })
6358 }
6359 ast.GenericType {
6360 mut params := []ast.Expr{cap: typ.params.len}
6361 for param in typ.params {
6362 params << t.rewrite_concrete_generic_struct_type_expr(param)
6363 }
6364 return ast.Type(ast.GenericType{
6365 name: typ.name
6366 params: params
6367 })
6368 }
6369 ast.MapType {
6370 return ast.Type(ast.MapType{
6371 key_type: t.rewrite_concrete_generic_struct_type_expr(typ.key_type)
6372 value_type: t.rewrite_concrete_generic_struct_type_expr(typ.value_type)
6373 })
6374 }
6375 ast.OptionType {
6376 return ast.Type(ast.OptionType{
6377 base_type: t.rewrite_concrete_generic_struct_type_expr(typ.base_type)
6378 })
6379 }
6380 ast.PointerType {
6381 return ast.Type(ast.PointerType{
6382 base_type: t.rewrite_concrete_generic_struct_type_expr(typ.base_type)
6383 lifetime: typ.lifetime
6384 })
6385 }
6386 ast.ResultType {
6387 return ast.Type(ast.ResultType{
6388 base_type: t.rewrite_concrete_generic_struct_type_expr(typ.base_type)
6389 })
6390 }
6391 ast.ThreadType {
6392 return ast.Type(ast.ThreadType{
6393 elem_type: t.rewrite_concrete_generic_struct_type_expr(typ.elem_type)
6394 })
6395 }
6396 else {
6397 return typ
6398 }
6399 }
6400}
6401
6402fn (_ &Transformer) get_tuple_lhs(stmt ast.AssignStmt) ?[]ast.Expr {
6403 if stmt.lhs.len > 1 {
6404 return stmt.lhs
6405 }
6406 if stmt.lhs.len == 1 && stmt.lhs[0] is ast.Tuple {
6407 return (stmt.lhs[0] as ast.Tuple).exprs
6408 }
6409 return none
6410}
6411
6412// try_expand_tuple_call_assign expands `a, b = call()` to:
6413// _tuple_tN := call()
6414// a = _tuple_tN.arg0
6415// b = _tuple_tN.arg1
6416// This handles tuple-returning calls when cleanc can't resolve the tuple type.
6417fn (mut t Transformer) try_expand_tuple_call_assign(stmt ast.AssignStmt) ?[]ast.Stmt {
6418 tuple_lhs := t.get_tuple_lhs(stmt) or { return none }
6419 n := tuple_lhs.len
6420 if n < 2 {
6421 return none
6422 }
6423 // Must have single RHS (a call expression or similar)
6424 if stmt.rhs.len != 1 {
6425 return none
6426 }
6427 // Skip if RHS is an IfExpr (handled by try_expand_tuple_if_assign_stmts)
6428 if stmt.rhs[0] is ast.IfExpr {
6429 return none
6430 }
6431 rhs_expr := stmt.rhs[0]
6432 // Only handle when RHS is a call expression or postfix unwrap of a call
6433 mut is_call := rhs_expr is ast.CallExpr
6434 if rhs_expr is ast.PostfixExpr {
6435 if rhs_expr.op in [.not, .question] {
6436 is_call = rhs_expr.expr is ast.CallExpr
6437 }
6438 }
6439 if !is_call {
6440 return none
6441 }
6442 t.temp_counter++
6443 tmp_name := '_tuple_t${t.temp_counter}'
6444 tmp_ident := ast.Ident{
6445 name: tmp_name
6446 }
6447 mut result := []ast.Stmt{cap: n + 1}
6448 // Assign call result to temp variable
6449 result << ast.Stmt(ast.AssignStmt{
6450 op: .decl_assign
6451 lhs: [ast.Expr(tmp_ident)]
6452 rhs: [rhs_expr]
6453 pos: stmt.pos
6454 })
6455 // Extract .arg0, .arg1, etc. Use decl_assign for new declarations, assign for re-assignments.
6456 for i in 0 .. n {
6457 result << ast.Stmt(ast.AssignStmt{
6458 op: stmt.op
6459 lhs: [tuple_lhs[i]]
6460 rhs: [
6461 ast.Expr(ast.SelectorExpr{
6462 lhs: tmp_ident
6463 rhs: ast.Ident{
6464 name: 'arg${i}'
6465 }
6466 }),
6467 ]
6468 pos: stmt.pos
6469 })
6470 }
6471 return result
6472}
6473
6474fn (mut t Transformer) transform_assign_stmt(stmt ast.AssignStmt) ast.AssignStmt {
6475 op, lhs, rhs, pos := t.transform_assign_stmt_parts(stmt)
6476 return ast.AssignStmt{
6477 op: op
6478 lhs: lhs
6479 rhs: rhs
6480 pos: pos
6481 }
6482}
6483
6484// transform_assign_stmt_parts returns the four variable parts of a lowered
6485// AssignStmt — `(op, lhs, rhs, pos)`. The flat-write arm calls this directly
6486// to skip the `ast.AssignStmt` wrapper allocation; the legacy
6487// `transform_assign_stmt` above wraps it for non-flat callers. Mirrors the
6488// `_parts` extraction template established by `transform_fn_decl_parts`
6489// (session 4) and `transform_return_stmt_parts` (session 59).
6490fn (mut t Transformer) transform_assign_stmt_parts(stmt ast.AssignStmt) (token.Token, []ast.Expr, []ast.Expr, token.Pos) {
6491 if !expr_array_has_valid_data(stmt.lhs) || !expr_array_has_valid_data(stmt.rhs) {
6492 return stmt.op, stmt.lhs, stmt.rhs, stmt.pos
6493 }
6494 // Check for string compound assignment: p += x -> p = string__plus(p, x)
6495 if stmt.op == .plus_assign && stmt.lhs.len == 1 && stmt.rhs.len == 1 {
6496 lhs_expr := stmt.lhs[0]
6497 if t.is_string_expr(lhs_expr) {
6498 // Transform p += x to p = string__plus(p, x)
6499 return token.Token.assign, stmt.lhs, [
6500 ast.Expr(ast.CallExpr{
6501 lhs: ast.Ident{
6502 name: 'string__plus'
6503 }
6504 args: [t.transform_expr(lhs_expr), t.transform_expr(stmt.rhs[0])]
6505 pos: stmt.pos
6506 }),
6507 ], stmt.pos
6508 }
6509 }
6510 // Lower writes into result/option payload: res.data = v
6511 if stmt.op == .assign && stmt.lhs.len == 1 && stmt.rhs.len == 1
6512 && stmt.lhs[0] is ast.SelectorExpr {
6513 lhs_sel := stmt.lhs[0] as ast.SelectorExpr
6514 if lhs_sel.rhs.name == 'data' {
6515 if lhs_type := t.get_expr_type(lhs_sel.lhs) {
6516 match lhs_type {
6517 types.ResultType {
6518 base_c := t.type_to_c_name(lhs_type.base_type)
6519 if base_c != '' && base_c != 'void' {
6520 return token.Token.assign, [
6521 t.lower_wrapper_payload_access(t.transform_expr(lhs_sel.lhs),
6522 base_c),
6523 ], [t.transform_expr(stmt.rhs[0])], stmt.pos
6524 }
6525 }
6526 types.OptionType {
6527 base_c := t.type_to_c_name(lhs_type.base_type)
6528 if base_c != '' && base_c != 'void' {
6529 return token.Token.assign, [
6530 t.lower_wrapper_payload_access(t.transform_expr(lhs_sel.lhs),
6531 base_c),
6532 ], [t.transform_expr(stmt.rhs[0])], stmt.pos
6533 }
6534 }
6535 else {}
6536 }
6537 }
6538 }
6539 }
6540
6541 mut lhs := []ast.Expr{cap: stmt.lhs.len}
6542 for _, expr in stmt.lhs {
6543 if expr is ast.Ident {
6544 // Assignment writes to the original variable, not a smartcasted view.
6545 lhs << ast.Expr(expr)
6546 } else {
6547 lhs << t.transform_expr(expr)
6548 }
6549 }
6550 is_tuple_lhs := stmt.lhs.len > 1 || (stmt.lhs.len == 1 && stmt.lhs[0] is ast.Tuple)
6551 // Keep a shallow view of RHS expressions; deep clone can crash on malformed AST payloads.
6552 mut rhs_src := unsafe { stmt.rhs }
6553 if is_tuple_lhs && stmt.rhs.len == 1 && stmt.rhs[0] is ast.PostfixExpr {
6554 postfix := stmt.rhs[0] as ast.PostfixExpr
6555 if postfix.op in [.not, .question] {
6556 // For tuple destructuring with `call()!`, keep the raw call expression.
6557 // Tuple/result unwrapping is handled later by codegen.
6558 rhs_src = [postfix.expr]
6559 }
6560 }
6561 mut rhs := []ast.Expr{cap: stmt.rhs.len}
6562 // For decl_assign with IfExpr RHS, skip value-lowering since cleanc
6563 // already handles this case efficiently (Type name; if (...) { name = a; } else { ... }).
6564 is_decl_with_if_rhs := stmt.op == .decl_assign && rhs_src.len == 1 && rhs_src[0] is ast.IfExpr
6565 saved_skip := t.skip_if_value_lowering
6566 if is_decl_with_if_rhs {
6567 t.skip_if_value_lowering = true
6568 }
6569 mut rhs_smartcast_variants := []string{len: rhs_src.len}
6570 for i, expr in rhs_src {
6571 mut rhs_expr := expr
6572 // Preserve expected type context for untyped literals like `[]` / `{}` on assignment.
6573 if i < stmt.lhs.len {
6574 lhs_expr := stmt.lhs[i]
6575 lhs_is_new_decl_ident := stmt.op == .decl_assign && lhs_expr is ast.Ident
6576 if !lhs_is_new_decl_ident && !(stmt.op == .decl_assign && rhs_expr is ast.AsCastExpr) {
6577 mut lhs_type_name_for_wrap := t.assignment_lhs_type_name(lhs_expr)
6578 if lhs_expr is ast.Ident {
6579 if lhs_type_name_for_wrap != '' {
6580 if lhs_expected2 := t.lookup_type(lhs_type_name_for_wrap) {
6581 rhs_expr = t.resolve_expr_with_expected_type(rhs_expr, lhs_expected2)
6582 }
6583 }
6584 } else if lhs_expected := t.get_expr_type(lhs_expr) {
6585 rhs_expr = t.resolve_expr_with_expected_type(rhs_expr, lhs_expected)
6586 }
6587 if lhs_type_name_for_wrap != '' && t.is_sum_type(lhs_type_name_for_wrap)
6588 && expr is ast.Ident {
6589 if ctx := t.find_smartcast_for_expr(expr.name) {
6590 rhs_smartcast_variants[i] = ctx.variant
6591 }
6592 }
6593 }
6594 }
6595 if t.is_native_be && rhs_expr is ast.ArrayInitExpr {
6596 arr := rhs_expr as ast.ArrayInitExpr
6597 if arr.len is ast.EmptyExpr && arr.cap is ast.EmptyExpr && arr.init is ast.EmptyExpr {
6598 rhs << rhs_expr
6599 continue
6600 }
6601 }
6602 rhs << t.transform_expr(rhs_expr)
6603 }
6604 t.skip_if_value_lowering = saved_skip
6605 // For simple assignments, check if LHS is a sumtype and wrap the RHS if needed
6606 if stmt.op in [.assign, .decl_assign] && lhs.len == 1 && rhs.len == 1 {
6607 lhs_name := t.get_var_name(stmt.lhs[0])
6608 if lhs_name != '' && !(stmt.op == .decl_assign && rhs_src[0] is ast.AsCastExpr) {
6609 lhs_type_name := if stmt.op == .decl_assign && stmt.lhs[0] is ast.Ident {
6610 ''
6611 } else {
6612 t.assignment_lhs_type_name(stmt.lhs[0])
6613 }
6614 if lhs_type_name != '' && t.is_sum_type(lhs_type_name) {
6615 if wrapped := t.wrap_sumtype_value_transformed(rhs[0], lhs_type_name) {
6616 rhs[0] = wrapped
6617 } else if rhs_smartcast_variants.len > 0 && rhs_smartcast_variants[0] != '' {
6618 variants := t.get_sum_type_variants(lhs_type_name)
6619 variant_name := t.match_variant(rhs_smartcast_variants[0], variants) or {
6620 rhs_smartcast_variants[0]
6621 }
6622 if wrapped := t.build_sumtype_init(rhs[0], variant_name, lhs_type_name) {
6623 rhs[0] = wrapped
6624 }
6625 }
6626 }
6627 }
6628 }
6629 // Propagate array element type overrides from RHS temp variables to LHS variables.
6630 // When .map(fn_name) expansion creates _filter_tN with a type override, propagate
6631 // it to the declared variable (e.g. r2 := _filter_tN → r2 gets the same override).
6632 if stmt.op == .decl_assign && rhs.len == 1 {
6633 rhs0_valid := unsafe { expr_has_valid_data_ref(&rhs[0]) }
6634 rhs0 := rhs[0]
6635 if rhs0_valid && rhs0 is ast.Ident {
6636 rhs_name := (rhs0 as ast.Ident).name
6637 if override := t.array_elem_type_overrides[rhs_name] {
6638 lhs_name := t.get_var_name(stmt.lhs[0])
6639 if lhs_name != '' {
6640 t.array_elem_type_overrides[lhs_name] = override
6641 }
6642 }
6643 }
6644 }
6645 if stmt.op == .decl_assign && stmt.lhs.len == 1 && rhs_src.len == 1 {
6646 lhs_name := t.get_var_name(stmt.lhs[0])
6647 if lhs_name != '' {
6648 decl_rhs := if rhs.len == 1 && rhs[0] is ast.IfExpr {
6649 rhs[0]
6650 } else {
6651 rhs_src[0]
6652 }
6653 if decl_type := t.decl_assign_storage_type(stmt.lhs[0], decl_rhs) {
6654 t.remember_decl_assign_lhs_type(stmt.lhs, decl_type)
6655 }
6656 if rhs_type := t.fn_pointer_call_return_type(rhs_src[0]) {
6657 t.register_temp_var(lhs_name, rhs_type)
6658 } else if rhs_type := t.smartcast_type_for_expr(rhs_src[0]) {
6659 t.register_local_var_type(lhs_name, rhs_type)
6660 } else if rhs_type := t.rune_arithmetic_expr_type(rhs_src[0]) {
6661 t.register_local_var_type(lhs_name, rhs_type)
6662 } else if rhs_src[0] is ast.ArrayInitExpr {
6663 if rhs_type := t.get_array_init_expr_type(rhs_src[0] as ast.ArrayInitExpr) {
6664 t.register_local_var_type(lhs_name, rhs_type)
6665 }
6666 } else if rhs_src[0] is ast.MapInitExpr {
6667 map_init := rhs_src[0] as ast.MapInitExpr
6668 if map_init.typ !is ast.EmptyExpr {
6669 if rhs_type := t.type_from_param_type_expr(map_init.typ, []) {
6670 t.register_local_var_type(lhs_name, rhs_type)
6671 }
6672 }
6673 } else if rhs_src[0] is ast.CallExpr || rhs_src[0] is ast.CallOrCastExpr
6674 || rhs_src[0] is ast.InitExpr || rhs_src[0] is ast.Ident
6675 || rhs_src[0] is ast.SelectorExpr {
6676 if rhs_type := t.get_expr_type(rhs_src[0]) {
6677 t.register_local_var_type(lhs_name, rhs_type)
6678 }
6679 }
6680 if bindings := t.generic_bindings_from_generic_call_expr(rhs_src[0]) {
6681 t.local_receiver_generic_bindings[lhs_name] = bindings.clone()
6682 } else if bindings := t.generic_bindings_from_generic_init_expr(rhs_src[0]) {
6683 t.local_receiver_generic_bindings[lhs_name] = bindings.clone()
6684 }
6685 }
6686 }
6687 return stmt.op, lhs, rhs, stmt.pos
6688}
6689
6690fn (t &Transformer) assignment_lhs_type_name(expr ast.Expr) string {
6691 if expr is ast.Ident {
6692 type_name := t.get_var_type_name(expr.name)
6693 if type_name != '' {
6694 return type_name
6695 }
6696 }
6697 if typ := t.get_expr_type(expr) {
6698 return t.type_to_c_name(typ)
6699 }
6700 return ''
6701}
6702
6703fn (t &Transformer) smartcast_type_for_expr(expr ast.Expr) ?types.Type {
6704 expr_str := t.expr_to_string(expr)
6705 if expr_str == '' {
6706 return none
6707 }
6708 ctx := t.find_smartcast_for_expr(expr_str) or { return none }
6709 if ctx.variant_full != '' {
6710 if typ := t.c_name_to_type(ctx.variant_full) {
6711 return t.normalize_type(typ)
6712 }
6713 if typ := t.lookup_type(ctx.variant_full) {
6714 return t.normalize_type(typ)
6715 }
6716 }
6717 if ctx.variant != '' {
6718 if typ := t.c_name_to_type(ctx.variant) {
6719 return t.normalize_type(typ)
6720 }
6721 if typ := t.lookup_type(ctx.variant) {
6722 return t.normalize_type(typ)
6723 }
6724 }
6725 return none
6726}
6727
6728// get_var_name extracts the variable name from an expression, handling ModifierExpr
6729fn (t &Transformer) get_var_name(expr ast.Expr) string {
6730 if expr is ast.Ident {
6731 return expr.name
6732 }
6733 if expr is ast.ModifierExpr {
6734 return t.get_var_name(expr.expr)
6735 }
6736 return ''
6737}
6738
6739// try_extract_sincos_arg checks if an expression is a call to `sincos(x)` and returns the argument.
6740fn (t &Transformer) try_extract_sincos_arg(expr ast.Expr) ?ast.Expr {
6741 if expr is ast.CallExpr {
6742 if expr.lhs is ast.Ident {
6743 name := (expr.lhs as ast.Ident).name
6744 if name == 'sincos' && expr.args.len == 1 {
6745 return expr.args[0]
6746 }
6747 } else if expr.lhs is ast.SelectorExpr {
6748 sel := expr.lhs as ast.SelectorExpr
6749 if sel.rhs.name == 'sincos' && expr.args.len == 1 {
6750 return expr.args[0]
6751 }
6752 }
6753 }
6754 // sincos(x) with single arg may be parsed as CallOrCastExpr
6755 if expr is ast.CallOrCastExpr {
6756 if expr.lhs is ast.Ident {
6757 name := (expr.lhs as ast.Ident).name
6758 if name == 'sincos' {
6759 return expr.expr
6760 }
6761 } else if expr.lhs is ast.SelectorExpr {
6762 sel := expr.lhs as ast.SelectorExpr
6763 if sel.rhs.name == 'sincos' {
6764 return expr.expr
6765 }
6766 }
6767 }
6768 return none
6769}
6770
6771// try_expand_or_expr_assign checks if an assignment has an OrExpr RHS (used by transform_stmt)
6772// Returns none since expansion is handled by try_expand_or_expr_assign_stmts at the list level
6773fn (mut t Transformer) try_expand_or_expr_assign(_stmt ast.AssignStmt) ?ast.Stmt {
6774 return none
6775}
6776
6777// try_transform_map_index_assign transforms map index assignment to a function call.
6778// Transforms: m[key] = val -> map__set(&m, &key, &val)
6779// Also handles compound assignments: m[key] += val -> { tmp = map_get(m,key); tmp += val; map_set(m,key,tmp) }
6780fn (mut t Transformer) map_value_temp_expr(expr ast.Expr, value_type types.Type) ast.Expr {
6781 mut resolved := t.resolve_expr_with_expected_type(expr, value_type)
6782 target_name := t.type_to_c_name(value_type)
6783 if target_name != '' {
6784 if t.expr_is_casted_to_type(resolved, target_name) {
6785 return resolved
6786 }
6787 if resolved_type := t.get_expr_type(resolved) {
6788 if t.type_names_match(t.type_to_c_name(resolved_type), target_name) {
6789 return resolved
6790 }
6791 resolved_base_name := t.type_to_c_name(t.unwrap_alias_and_pointer_type(resolved_type))
6792 value_base_name := t.type_to_c_name(t.unwrap_alias_and_pointer_type(value_type))
6793 if t.type_names_match(resolved_base_name, value_base_name) {
6794 return resolved
6795 }
6796 }
6797 }
6798 if value_type is types.SumType {
6799 st := value_type as types.SumType
6800 return ast.CastExpr{
6801 typ: ast.Ident{
6802 name: st.name
6803 }
6804 expr: resolved
6805 pos: resolved.pos()
6806 }
6807 }
6808 base := t.unwrap_alias_and_pointer_type(value_type)
6809 match base {
6810 types.Primitive, types.ISize, types.USize, types.Char, types.Rune, types.Enum {
6811 if target_name != '' && t.expr_is_casted_to_type(resolved, target_name) {
6812 return resolved
6813 }
6814 return ast.CastExpr{
6815 typ: t.type_to_ast_type_expr(value_type)
6816 expr: resolved
6817 pos: resolved.pos()
6818 }
6819 }
6820 else {}
6821 }
6822
6823 return resolved
6824}
6825
6826fn (mut t Transformer) try_transform_map_index_assign(stmt ast.AssignStmt) ?ast.Stmt {
6827 if t.is_eval_backend() {
6828 return none
6829 }
6830 // Handle compound assignment by converting to: m[key] = m[key] op val
6831 if stmt.op != .assign && stmt.op != .decl_assign {
6832 infix_op := match stmt.op {
6833 .plus_assign { token.Token.plus }
6834 .minus_assign { token.Token.minus }
6835 .mul_assign { token.Token.mul }
6836 .div_assign { token.Token.div }
6837 .mod_assign { token.Token.mod }
6838 .and_assign { token.Token.amp }
6839 .or_assign { token.Token.pipe }
6840 .xor_assign { token.Token.xor }
6841 .left_shift_assign { token.Token.left_shift }
6842 .right_shift_assign { token.Token.right_shift }
6843 else { return none }
6844 }
6845
6846 if stmt.lhs.len != 1 || stmt.rhs.len != 1 {
6847 return none
6848 }
6849 // Convert m[key] op= val to m[key] = m[key] op val
6850 new_rhs := ast.Expr(ast.InfixExpr{
6851 lhs: stmt.lhs[0]
6852 op: infix_op
6853 rhs: stmt.rhs[0]
6854 pos: stmt.pos
6855 })
6856 return t.try_transform_map_index_assign(ast.AssignStmt{
6857 op: .assign
6858 lhs: stmt.lhs
6859 rhs: [new_rhs]
6860 pos: stmt.pos
6861 })
6862 }
6863 // Check for single LHS that is an IndexExpr
6864 if stmt.lhs.len != 1 || stmt.rhs.len != 1 {
6865 return none
6866 }
6867 lhs_expr := stmt.lhs[0]
6868 mut index_expr := ast.IndexExpr{}
6869 if lhs_expr is ast.IndexExpr {
6870 index_expr = lhs_expr
6871 } else if lhs_expr is ast.GenericArgOrIndexExpr {
6872 // The parser may represent `m[key]` as GenericArgOrIndexExpr due to ambiguity with generic args.
6873 // For assignment LHS, treat it as an index expression unless lhs is callable (generic specialization).
6874 if lhs_type := t.get_expr_type(lhs_expr.lhs) {
6875 if t.is_callable_type(lhs_type) {
6876 return none
6877 }
6878 }
6879 index_expr = ast.IndexExpr{
6880 lhs: lhs_expr.lhs
6881 expr: lhs_expr.expr
6882 is_gated: false
6883