vxx / vlib / v3 / transform / transform.v
7486 lines · 7184 sloc · 223.65 KB · 2b81918e62db9ab38787518603a7f93bee80868c
Raw
1module transform
2
3import v3.flat
4import v3.types
5
6// arr1 supports arr1 handling for transform.
7fn arr1(a flat.NodeId) []flat.NodeId {
8 mut r := []flat.NodeId{}
9 r << a
10 return r
11}
12
13// arr2 supports arr2 handling for transform.
14fn arr2(a flat.NodeId, b flat.NodeId) []flat.NodeId {
15 mut r := []flat.NodeId{}
16 r << a
17 r << b
18 return r
19}
20
21// arr3 supports arr3 handling for transform.
22fn arr3(a flat.NodeId, b flat.NodeId, c flat.NodeId) []flat.NodeId {
23 mut r := []flat.NodeId{}
24 r << a
25 r << b
26 r << c
27 return r
28}
29
30// arr4 supports arr4 handling for transform.
31fn arr4(a flat.NodeId, b flat.NodeId, c flat.NodeId, d flat.NodeId) []flat.NodeId {
32 mut r := []flat.NodeId{}
33 r << a
34 r << b
35 r << c
36 r << d
37 return r
38}
39
40// node_kind_id supports node kind id handling for transform.
41fn node_kind_id(node flat.Node) int {
42 mut kind_id := node.kind_id
43 if kind_id == 0 && int(node.kind) != 0 {
44 kind_id = int(node.kind)
45 }
46 return kind_id
47}
48
49// SmartcastContext stores smartcast context state used by transform.
50pub struct SmartcastContext {
51pub:
52 expr_name string // the expression being smartcast (e.g. "node")
53 variant_name string // the variant type name (e.g. "Ident")
54 sum_type_name string // the parent sum type name (e.g. "Expr")
55}
56
57// Transformer represents transformer data used by transform.
58pub struct Transformer {
59mut:
60 a &flat.FlatAst = unsafe { nil }
61 tc &types.TypeChecker = unsafe { nil }
62 structs map[string]StructInfo
63 unique_fields map[string]string
64 alias_methods map[string]string
65 globals map[string]string
66 sum_types map[string][]string
67 sum_variant_parents map[string][]string
68 sum_variant_fields map[string]string
69 qualified_types map[string]string
70 fn_ret_types map[string]string
71 const_suffixes map[string]string
72 enum_types map[string][]string
73 cur_file string
74 cur_module string
75 cur_fn_name string
76 cur_fn_ret_type string
77 cur_fn_is_generic bool
78 var_types []VarTypeBinding
79 pointer_value_lvalues map[string]bool
80 temp_counter int
81 pending_stmts []flat.NodeId
82 smartcast_stack []SmartcastContext
83 in_call_callee bool
84 in_const_init bool
85 in_return_expr bool
86 alias_cache &AliasCache = unsafe { nil }
87 sum_cache &AliasCache = unsafe { nil }
88 used_fns map[string]bool
89 // used_struct_operator_fns holds the callee names of direct calls seen during
90 // monomorphize. Infix operators on generic instances are lowered to direct calls
91 // (`Vec_int__plus(a, b)`) before this pass, so an operator overload is specialized for
92 // an instantiated generic struct only when its mangled name appears here — an instance
93 // whose type argument never has the operator applied is not emitted with a body that
94 // would fail C compilation.
95 used_struct_operator_fns map[string]bool
96 // active_generic_params holds the generic parameter names of the decl currently
97 // being specialized/rewritten, in the same order as the inferred type `args`.
98 // It lets type-text substitution map placeholders by name (so non-canonical
99 // params like `D`/`F` resolve to the right arg) instead of by the positional
100 // `generic_param_index` heuristic (which collapses anything outside the T/U/C
101 // sequences to index 0). Empty for struct-generic specialization, which keeps
102 // the legacy positional behaviour.
103 active_generic_params []string
104 // escaping_amp_ptrs holds the names of pointer locals `p` declared as `p := &v`
105 // (v a value local) whose pointer escapes the function (is returned). V semantics
106 // auto-heap such a `v`; v3 otherwise takes the address of a stack local that dies
107 // on return. Recomputed per function (structural pre-pass in transform_fn_body),
108 // consumed when the `p := &v` decl is transformed (RHS rewritten to a heap copy).
109 escaping_amp_ptrs map[string]bool
110 // escaping_amp_sources holds the source locals `v` of such `p := &v` escapes — the
111 // values whose address leaves the frame. The local itself is moved to the heap at its
112 // declaration (its type becomes `&T`) so a mutation between `p := &v` and `return p`
113 // is observed by the caller; copying eagerly at the alias would return stale data.
114 escaping_amp_sources map[string]bool
115 // heaped_amp_locals records which of those sources were actually moved to the heap, so
116 // the `p := &v` alias emits `p = v` (the heap pointer) instead of a fresh memdup copy.
117 heaped_amp_locals map[string]bool
118 generic_fn_decls_cache map[string]GenericFnDecl
119 generic_fn_decls_ready bool
120 node_module_map_cache map[int]string
121 node_module_map_nodes int = -1
122}
123
124// AliasCache memoizes normalize_type_alias results. It lives on the heap so the
125// many `&Transformer` (read-only) query methods can populate it through the
126// pointer. normalize_type_alias is a pure function of (cur_module, typ) plus the
127// collected type maps (which never change during transform), so the cache is
128// keyed by typ and cleared whenever cur_module changes.
129struct AliasCache {
130mut:
131 module string
132 entries map[string]string
133}
134
135// StructInfo stores struct info metadata used by transform.
136pub struct StructInfo {
137pub:
138 name string
139 module string
140 is_params bool
141 fields []FieldInfo
142}
143
144// FieldInfo stores field info metadata used by transform.
145pub struct FieldInfo {
146pub:
147 name string
148 typ string
149 raw_typ string
150 default_expr flat.NodeId
151}
152
153// TupleBlockParts represents tuple block parts data used by transform.
154struct TupleBlockParts {
155 prefix []flat.NodeId
156 values []flat.NodeId
157}
158
159// StructFieldLookup represents struct field lookup data used by transform.
160struct StructFieldLookup {
161 info StructInfo
162 owner_type string
163}
164
165// VarTypeBinding represents var type binding data used by transform.
166struct VarTypeBinding {
167 name string
168 typ string
169}
170
171struct GenericFnDecl {
172 id flat.NodeId
173 node flat.Node
174 file string
175 module string
176 key string
177}
178
179// --- entry point ---
180
181// transform supports transform handling for transform.
182pub fn transform(mut a flat.FlatAst, tc &types.TypeChecker) {
183 transform_with_used(mut a, tc, map[string]bool{})
184}
185
186// transform_with_used transforms transform with used data for transform.
187pub fn transform_with_used(mut a flat.FlatAst, tc &types.TypeChecker, used_fns map[string]bool) map[string]bool {
188 augmented, _ := transform_with_used_opt(mut a, tc, used_fns, false)
189 return augmented
190}
191
192// transform_with_used_opt is transform_with_used with an opt-in for parallel
193// function-body transform. It returns the augmented used-fn set and whether the
194// function bodies were actually transformed across threads (false when parallel
195// was not requested, the build lacks thread support, or there was too little work).
196pub fn transform_with_used_opt(mut a flat.FlatAst, tc &types.TypeChecker, used_fns map[string]bool, want_parallel bool) (map[string]bool, bool) {
197 mut augmented_used_fns := used_fns.clone()
198 mut t := new_transformer(mut a, tc, augmented_used_fns)
199 t.prepare()
200 if want_parallel {
201 // Transform roughly grows the node/children arrays by ~75%. Reserve that capacity
202 // up front so they don't double past it (the parsed AST already overshoots to the
203 // next power of two, wasting ~40MB, and each doubling briefly holds both the old and
204 // new arrays — the dominant peak-RSS contributor under -gc none). The serial path is
205 // latency-sensitive and does not clone worker ASTs, so let it grow naturally.
206 reserve_nodes := a.nodes.len * 7 / 4 - a.nodes.cap
207 if reserve_nodes > 0 {
208 unsafe { a.nodes.grow_cap(reserve_nodes) }
209 }
210 reserve_children := a.children.len * 7 / 4 - a.children.cap
211 if reserve_children > 0 {
212 unsafe { a.children.grow_cap(reserve_children) }
213 }
214 }
215 was_parallel := t.transform_all_dispatch(want_parallel)
216 augmented_used_fns = t.used_fns.clone()
217 return augmented_used_fns, was_parallel
218}
219
220pub fn monomorphize_with_used(mut a flat.FlatAst, tc &types.TypeChecker, used_fns map[string]bool) map[string]bool {
221 mut augmented_used_fns := used_fns.clone()
222 mut t := new_transformer(mut a, tc, augmented_used_fns)
223 t.prepare()
224 for name in t.monomorphize_pass() {
225 augmented_used_fns[name] = true
226 augmented_used_fns[c_name(name)] = true
227 t.used_fns[name] = true
228 t.used_fns[c_name(name)] = true
229 }
230 t.materialize_generic_structs()
231 return t.used_fns
232}
233
234fn new_transformer(mut a flat.FlatAst, tc &types.TypeChecker, used_fns map[string]bool) Transformer {
235 return Transformer{
236 a: a
237 tc: unsafe { tc }
238 pointer_value_lvalues: map[string]bool{}
239 escaping_amp_ptrs: map[string]bool{}
240 escaping_amp_sources: map[string]bool{}
241 heaped_amp_locals: map[string]bool{}
242 used_fns: used_fns.clone()
243 }
244}
245
246fn (mut t Transformer) mark_fn_used_name(name string) {
247 if name.len == 0 {
248 return
249 }
250 t.used_fns[name] = true
251 t.used_fns[c_name(name)] = true
252 if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
253 needs_module_prefix := !name.contains('.') || local_method_fn_name_needs_module_prefix(name)
254 if needs_module_prefix && !name.starts_with('${t.cur_module}.') {
255 qname := '${t.cur_module}.${name}'
256 t.used_fns[qname] = true
257 t.used_fns[c_name(qname)] = true
258 }
259 }
260}
261
262fn local_method_fn_name_needs_module_prefix(name string) bool {
263 if !name.contains('.') {
264 return false
265 }
266 receiver_name := name.all_before('.')
267 if receiver_name.len == 0 {
268 return false
269 }
270 first := receiver_name[0]
271 return first >= `A` && first <= `Z`
272}
273
274fn (mut t Transformer) prepare() {
275 t.collect_types()
276 t.collect_const_suffixes()
277 t.collect_alias_methods()
278 // Enable the alias cache only now that the type maps are fully populated.
279 // During collection those maps are incomplete, so caching there would poison
280 // entries with results computed against a partial view.
281 t.alias_cache = &AliasCache{}
282 t.sum_cache = &AliasCache{}
283}
284
285// reset_var_types updates reset var types state for transform.
286fn (mut t Transformer) reset_var_types() {
287 t.var_types.clear()
288}
289
290// set_var_type updates set var type state for transform.
291fn (mut t Transformer) set_var_type(name string, typ string) {
292 if name.len == 0 {
293 return
294 }
295 for i, binding in t.var_types {
296 if binding.name == name {
297 t.var_types[i] = VarTypeBinding{
298 name: name
299 typ: typ
300 }
301 return
302 }
303 }
304 t.var_types << VarTypeBinding{
305 name: name
306 typ: typ
307 }
308}
309
310// unset_var_type supports unset var type handling for Transformer.
311fn (mut t Transformer) unset_var_type(name string) {
312 for i, binding in t.var_types {
313 if binding.name == name {
314 t.var_types.delete(i)
315 return
316 }
317 }
318}
319
320// var_type supports var type handling for Transformer.
321fn (t &Transformer) var_type(name string) string {
322 for binding in t.var_types {
323 if binding.name == name {
324 return binding.typ
325 }
326 }
327 return ''
328}
329
330// --- type collection ---
331
332// collect_types updates collect types state for transform.
333fn (mut t Transformer) collect_types() {
334 mut cur_mod := ''
335 for node in t.a.nodes {
336 match node.kind {
337 .module_decl {
338 cur_mod = node.value
339 }
340 .struct_decl {
341 owner_type := if cur_mod.len > 0 && cur_mod != 'main' && cur_mod != 'builtin' {
342 '${cur_mod}.${node.value}'
343 } else {
344 node.value
345 }
346 mut fields := []FieldInfo{}
347 for i in 0 .. node.children_count {
348 f := t.a.child_node(&node, i)
349 if f.kind != .field_decl {
350 continue
351 }
352 default_expr := if f.children_count > 0 {
353 t.a.child(f, 0)
354 } else {
355 flat.empty_node
356 }
357 fields << FieldInfo{
358 name: f.value
359 typ: t.normalize_field_type(f.typ, owner_type)
360 raw_typ: f.typ
361 default_expr: default_expr
362 }
363 }
364 info := StructInfo{
365 name: node.value
366 module: cur_mod
367 is_params: node.typ.contains('params')
368 fields: fields
369 }
370 t.structs[node.value] = info
371 if cur_mod.len > 0 && cur_mod != 'main' && cur_mod != 'builtin' {
372 qname := '${cur_mod}.${node.value}'
373 t.structs[qname] = info
374 if node.value !in t.qualified_types {
375 t.qualified_types[node.value] = qname
376 }
377 }
378 for f in fields {
379 t.add_unique_field_type(f.name, f.typ)
380 }
381 }
382 .type_decl {
383 if node.children_count > 0 {
384 mut variants := []string{}
385 for i in 0 .. node.children_count {
386 v := t.a.child_node(&node, i)
387 variants << t.normalize_sum_variant_type(v.value, cur_mod)
388 }
389 t.sum_types[node.value] = variants
390 for variant in variants {
391 t.add_sum_variant_parent(variant, node.value)
392 }
393 if cur_mod.len > 0 && cur_mod != 'main' && cur_mod != 'builtin' {
394 qname := '${cur_mod}.${node.value}'
395 t.sum_types[qname] = variants
396 if node.value !in t.qualified_types {
397 t.qualified_types[node.value] = qname
398 }
399 for variant in variants {
400 t.add_sum_variant_parent(variant, qname)
401 }
402 }
403 }
404 }
405 .enum_decl {
406 mut field_names := []string{}
407 for i in 0 .. node.children_count {
408 f := t.a.child_node(&node, i)
409 if f.kind == .enum_field {
410 field_names << f.value
411 }
412 }
413 t.enum_types[node.value] = field_names
414 if cur_mod.len > 0 && cur_mod != 'main' && cur_mod != 'builtin' {
415 t.enum_types['${cur_mod}.${node.value}'] = field_names
416 }
417 }
418 .global_decl {
419 for i in 0 .. node.children_count {
420 f := t.a.child_node(&node, i)
421 mut typ := t.normalize_type_in_module(f.typ, cur_mod)
422 if typ.len == 0 && f.children_count > 0 {
423 typ = t.normalize_type_in_module(t.node_type(t.a.child(f, 0)), cur_mod)
424 }
425 t.globals[f.value] = typ
426 if cur_mod.len > 0 && cur_mod != 'main' && cur_mod != 'builtin' {
427 t.globals['${cur_mod}.${f.value}'] = typ
428 }
429 }
430 }
431 .fn_decl {
432 if node.typ.len > 0 {
433 ret_typ := t.normalize_type_in_module(node.typ, cur_mod)
434 if cur_mod.len > 0 && cur_mod != 'main' && cur_mod != 'builtin' {
435 qname := '${cur_mod}.${node.value}'
436 t.fn_ret_types[qname] = ret_typ
437 qlowered := c_name(qname)
438 if qlowered != qname {
439 t.fn_ret_types[qlowered] = ret_typ
440 }
441 } else {
442 t.fn_ret_types[node.value] = ret_typ
443 lowered := c_name(node.value)
444 if lowered != node.value {
445 t.fn_ret_types[lowered] = ret_typ
446 }
447 }
448 }
449 }
450 .c_fn_decl {
451 if node.typ.len > 0 {
452 ret_typ := t.normalize_type_in_module(node.typ, cur_mod)
453 t.fn_ret_types[node.value] = ret_typ
454 if node.value.starts_with('C.') {
455 t.fn_ret_types[node.value[2..]] = ret_typ
456 } else {
457 t.fn_ret_types['C.${node.value}'] = ret_typ
458 }
459 }
460 }
461 else {}
462 }
463 }
464}
465
466// add_sum_variant_parent updates add sum variant parent state for Transformer.
467fn (mut t Transformer) add_sum_variant_parent(variant string, sum_name string) {
468 if variant.len == 0 || sum_name.len == 0 {
469 return
470 }
471 field_name := t.sum_field_name(variant)
472 if field_name.contains('__') && field_name !in t.sum_variant_fields {
473 t.sum_variant_fields[field_name] = variant
474 }
475 t.add_sum_variant_parent_key(variant, sum_name)
476 if variant.contains('.') {
477 t.add_sum_variant_parent_key(variant.all_after_last('.'), sum_name)
478 }
479}
480
481// add_sum_variant_parent_key updates add sum variant parent key state for Transformer.
482fn (mut t Transformer) add_sum_variant_parent_key(key string, sum_name string) {
483 mut parents := t.sum_variant_parents[key] or { []string{} }
484 if sum_name !in parents {
485 parents << sum_name
486 t.sum_variant_parents[key] = parents
487 }
488}
489
490// add_unique_field_type updates add unique field type state for Transformer.
491fn (mut t Transformer) add_unique_field_type(name string, typ string) {
492 if name.len == 0 || typ.len == 0 {
493 return
494 }
495 if existing := t.unique_fields[name] {
496 if existing != typ {
497 t.unique_fields[name] = ''
498 }
499 } else {
500 t.unique_fields[name] = typ
501 }
502}
503
504// collect_const_suffixes updates collect const suffixes state for transform.
505fn (mut t Transformer) collect_const_suffixes() {
506 if isnil(t.tc) {
507 return
508 }
509 // Register every dot-delimited suffix of each const key so that both
510 // unqualified (`foo`) and partially-qualified (`mod.foo`) lookups resolve
511 // in O(1) via const_type_key, instead of scanning all consts per ident.
512 for key, _ in t.tc.const_types {
513 if !key.contains('.') {
514 t.add_const_suffix(key, key)
515 continue
516 }
517 mut i := 0
518 for i < key.len {
519 if key[i] == `.` {
520 t.add_const_suffix(key[i + 1..], key)
521 }
522 i++
523 }
524 }
525}
526
527// add_const_suffix updates add const suffix state for Transformer.
528fn (mut t Transformer) add_const_suffix(suffix string, key string) {
529 if existing := t.const_suffixes[suffix] {
530 if existing != key {
531 t.const_suffixes[suffix] = ''
532 }
533 } else {
534 t.const_suffixes[suffix] = key
535 }
536}
537
538// collect_alias_methods converts collect alias methods data for transform.
539fn (mut t Transformer) collect_alias_methods() {
540 if isnil(t.tc) {
541 return
542 }
543 for name, params in t.tc.fn_param_types {
544 if params.len == 0 || !name.contains('.') {
545 continue
546 }
547 receiver_name := name.all_before_last('.')
548 if receiver_name.len == 0 || receiver_name !in t.tc.type_aliases {
549 continue
550 }
551 method := name.all_after_last('.')
552 param_name := params[0].name()
553 clean_alias := if param_name.starts_with('&') { param_name[1..] } else { param_name }
554 alias_target := t.normalize_type_alias(clean_alias)
555 if alias_target.len == 0 {
556 continue
557 }
558 key := '${alias_target}.${method}'
559 if key !in t.alias_methods {
560 t.alias_methods[key] = name
561 }
562 }
563}
564
565// normalize_sum_variant_type transforms normalize sum variant type data for transform.
566fn (t &Transformer) normalize_sum_variant_type(typ string, mod string) string {
567 clean := typ.trim_space()
568 if clean.len == 0 {
569 return clean
570 }
571 if clean.starts_with('&') {
572 return '&' + t.normalize_sum_variant_type(clean[1..], mod)
573 }
574 if clean.starts_with('mut ') {
575 return '&' + t.normalize_sum_variant_type(clean[4..], mod)
576 }
577 if clean.starts_with('?') {
578 return '?' + t.normalize_sum_variant_type(clean[1..], mod)
579 }
580 if clean.starts_with('!') {
581 return '!' + t.normalize_sum_variant_type(clean[1..], mod)
582 }
583 if clean.starts_with('[]') {
584 return '[]' + t.normalize_sum_variant_type(clean[2..], mod)
585 }
586 if clean.starts_with('map[') {
587 bracket_end := clean.index(']') or { return clean }
588 key := t.normalize_sum_variant_type(clean[4..bracket_end], mod)
589 value := t.normalize_sum_variant_type(clean[bracket_end + 1..], mod)
590 return 'map[${key}]${value}'
591 }
592 if clean.starts_with('[') {
593 bracket_end := clean.index(']') or { return clean }
594 return clean[..bracket_end + 1] + t.normalize_sum_variant_type(clean[bracket_end +
595 1..], mod)
596 }
597 if clean.contains('.') || mod.len == 0 || mod == 'main' || mod == 'builtin'
598 || types.is_builtin_type_name(clean) {
599 return clean
600 }
601 return '${mod}.${clean}'
602}
603
604// --- main transform pass ---
605
606// transform_all transforms transform all data for transform.
607fn (mut t Transformer) transform_all() {
608 has_entry_main := t.has_entry_main()
609 node_count := t.a.nodes.len
610 for i in 0 .. node_count {
611 node := t.a.nodes[i]
612 kind_id := node_kind_id(node)
613 if kind_id == 77 {
614 t.cur_file = node.value
615 }
616 if kind_id == 73 {
617 t.cur_module = node.value
618 }
619 if kind_id == 61 {
620 if !t.should_transform_fn(node) {
621 continue
622 }
623 t.transform_fn_body(i)
624 } else if kind_id == 65 {
625 t.transform_const_decl(node)
626 } else if kind_id == 64 {
627 t.transform_global_decl(node)
628 }
629 }
630 if !has_entry_main {
631 t.transform_top_level_user_stmts()
632 }
633}
634
635fn (t &Transformer) has_entry_main() bool {
636 mut cur_module := ''
637 for node in t.a.nodes {
638 kind_id := node_kind_id(node)
639 if kind_id == 77 {
640 cur_module = ''
641 continue
642 }
643 if kind_id == 73 {
644 cur_module = node.value
645 continue
646 }
647 if kind_id == 61 && node.value == 'main' && (cur_module.len == 0 || cur_module == 'main') {
648 return true
649 }
650 }
651 return false
652}
653
654fn (mut t Transformer) transform_top_level_user_stmts() {
655 node_count := t.a.nodes.len
656 for file_idx in 0 .. node_count {
657 file_node := t.a.nodes[file_idx]
658 if !t.should_transform_top_level_file(file_idx, file_node) {
659 continue
660 }
661 t.transform_top_level_file(file_idx, file_node)
662 }
663}
664
665fn (t &Transformer) should_transform_top_level_file(file_idx int, file_node flat.Node) bool {
666 if file_idx < t.a.user_code_start || file_node.kind != .file || file_node.children_count == 0 {
667 return false
668 }
669 module_name := t.file_module_name(file_node)
670 return module_name.len == 0 || module_name == 'main'
671}
672
673fn (t &Transformer) file_module_name(file_node flat.Node) string {
674 for i in 0 .. file_node.children_count {
675 child := t.a.child_node(&file_node, i)
676 if child.kind == .module_decl {
677 return child.value
678 }
679 }
680 return ''
681}
682
683fn transform_is_top_level_stmt(node flat.Node) bool {
684 return match node.kind {
685 .expr_stmt, .assign, .decl_assign, .selector_assign, .index_assign, .for_stmt,
686 .for_in_stmt, .if_expr, .match_stmt, .assert_stmt, .defer_stmt, .block {
687 true
688 }
689 else {
690 false
691 }
692 }
693}
694
695fn (mut t Transformer) transform_top_level_file(file_idx int, file_node flat.Node) {
696 old_file := t.cur_file
697 old_module := t.cur_module
698 old_fn_name := t.cur_fn_name
699 old_fn_ret_type := t.cur_fn_ret_type
700 old_var_types := t.var_types.clone()
701 old_smartcast_stack := t.smartcast_stack.clone()
702 old_pending_stmts := t.pending_stmts.clone()
703 module_name := t.file_module_name(file_node)
704 t.cur_file = file_node.value
705 t.cur_module = if module_name.len == 0 { 'main' } else { module_name }
706 t.cur_fn_name = 'main'
707 t.cur_fn_ret_type = 'void'
708 t.reset_var_types()
709 t.smartcast_stack.clear()
710 t.pending_stmts.clear()
711 mut new_children := []flat.NodeId{cap: int(file_node.children_count)}
712 mut pending_stmts := []flat.NodeId{}
713 for i in 0 .. file_node.children_count {
714 child_id := t.a.child(&file_node, i)
715 if int(child_id) >= t.a.user_code_start {
716 child := t.a.nodes[int(child_id)]
717 if transform_is_top_level_stmt(child) {
718 pending_stmts << child_id
719 continue
720 }
721 }
722 t.append_transformed_top_level_stmts(mut new_children, mut pending_stmts)
723 new_children << child_id
724 }
725 t.append_transformed_top_level_stmts(mut new_children, mut pending_stmts)
726 start := t.a.children.len
727 for child_id in new_children {
728 t.a.children << child_id
729 }
730 t.a.nodes[file_idx] = flat.Node{
731 kind: .file
732 kind_id: file_node.kind_id
733 op: file_node.op
734 children_start: start
735 children_count: flat.child_count(new_children.len)
736 pos: file_node.pos
737 value: file_node.value
738 typ: file_node.typ
739 generic_params: file_node.generic_params
740 }
741 t.cur_file = old_file
742 t.cur_module = old_module
743 t.cur_fn_name = old_fn_name
744 t.cur_fn_ret_type = old_fn_ret_type
745 t.var_types = old_var_types
746 t.smartcast_stack = old_smartcast_stack
747 t.pending_stmts = old_pending_stmts
748}
749
750fn (mut t Transformer) append_transformed_top_level_stmts(mut out []flat.NodeId, mut pending []flat.NodeId) {
751 if pending.len == 0 {
752 return
753 }
754 transformed := t.transform_stmts(pending)
755 out << transformed
756 pending.clear()
757}
758
759// FnWorkItem identifies one top-level function body to transform, together with
760// the file/module context active at its declaration and a rough cost estimate
761// (subtree node count) used to balance work across parallel workers.
762struct FnWorkItem {
763 fn_idx int
764 file string
765 module string
766 cost int
767}
768
769// transform_all_dispatch runs the main transform pass either serially (the
770// original single-threaded walk) or, when `want_parallel` is set and there is
771// enough work, with closure-free function bodies transformed across threads.
772// Returns whether function bodies were actually transformed in parallel.
773fn (mut t Transformer) transform_all_dispatch(want_parallel bool) bool {
774 if !want_parallel {
775 t.transform_all()
776 return false
777 }
778 has_entry_main := t.has_entry_main()
779 // Serial phase: transform consts/globals and every function whose body
780 // contains a function literal (the only construct that lifts new top-level
781 // declarations and mutates the shared TypeChecker). Collect the remaining,
782 // closure-free functions as parallelizable work items.
783 has_fn_literals := t.has_fn_literal_nodes()
784 pure_items := t.transform_serial_then_collect_pure(has_fn_literals)
785 base_nodes := t.a.nodes.len
786 base_children := t.a.children.len
787 was_parallel := t.run_parallel_transform(pure_items, base_nodes, base_children)
788 if !has_entry_main {
789 t.transform_top_level_user_stmts()
790 }
791 return was_parallel
792}
793
794fn (t &Transformer) has_fn_literal_nodes() bool {
795 for node in t.a.nodes {
796 if node.kind == .fn_literal || node.kind == .lambda_expr {
797 return true
798 }
799 }
800 return false
801}
802
803// transform_serial_then_collect_pure walks the top level once: it transforms
804// const/global declarations and closure-bearing functions in place (serially),
805// and returns work items for the closure-free functions left to transform.
806fn (mut t Transformer) transform_serial_then_collect_pure(scan_fn_literals bool) []FnWorkItem {
807 mut pure := []FnWorkItem{}
808 original_len := t.a.nodes.len
809 for i in 0 .. original_len {
810 node := t.a.nodes[i]
811 kind_id := node_kind_id(node)
812 if kind_id == 77 {
813 t.cur_file = node.value
814 } else if kind_id == 73 {
815 t.cur_module = node.value
816 } else if kind_id == 61 {
817 if !t.should_transform_fn(node) {
818 continue
819 }
820 mut has_literal := false
821 mut cost := int(node.children_count) + 1
822 if scan_fn_literals {
823 has_literal, cost = t.fn_subtree_scan(i)
824 }
825 if has_literal {
826 t.transform_fn_body(i)
827 } else {
828 pure << FnWorkItem{
829 fn_idx: i
830 file: t.cur_file
831 module: t.cur_module
832 cost: cost
833 }
834 }
835 } else if kind_id == 65 {
836 t.transform_const_decl(node)
837 } else if kind_id == 64 {
838 t.transform_global_decl(node)
839 }
840 }
841 return pure
842}
843
844// fn_subtree_scan walks the subtree rooted at the function declaration `fn_idx`
845// and reports (whether it contains a function literal, total node count). The
846// function-literal flag routes closure-bearing functions to the serial path; the
847// count is a cheap per-function cost used to balance parallel workers.
848fn (t &Transformer) fn_subtree_scan(fn_idx int) (bool, int) {
849 mut has_literal := false
850 mut count := 0
851 mut stack := [flat.NodeId(fn_idx)]
852 for stack.len > 0 {
853 id := stack.pop()
854 idx := int(id)
855 if idx < 0 || idx >= t.a.nodes.len {
856 continue
857 }
858 node := t.a.nodes[idx]
859 count++
860 // 21 = fn_literal, 32 = lambda_expr: both can lower to a lifted closure.
861 kid := node_kind_id(node)
862 if kid == 21 || kid == 32 {
863 has_literal = true
864 }
865 for ci in 0 .. node.children_count {
866 child_id := t.a.children[node.children_start + ci]
867 if int(child_id) >= 0 {
868 stack << child_id
869 }
870 }
871 }
872 return has_literal, count
873}
874
875// transform_pure_items_serial transforms a list of closure-free function bodies
876// on this Transformer, in order. Used both as the serial fallback and as the
877// per-worker body in the parallel path.
878fn (mut t Transformer) transform_pure_items_serial(items []FnWorkItem) {
879 for it in items {
880 t.cur_file = it.file
881 t.cur_module = it.module
882 t.transform_fn_body(it.fn_idx)
883 }
884}
885
886// clone_ast_base produces a private FlatAst holding an independent copy of the
887// first base_nodes nodes / base_children children, so a worker can append its own
888// transformed nodes without racing the master or other workers. Read-only metadata
889// (disabled_fns) is shared.
890fn (t &Transformer) clone_ast_base(base_nodes int, base_children int) &flat.FlatAst {
891 return &flat.FlatAst{
892 nodes: t.a.nodes[0..base_nodes].clone()
893 children: t.a.children[0..base_children].clone()
894 user_code_start: t.a.user_code_start
895 disabled_fns: t.a.disabled_fns
896 }
897}
898
899// fork_worker builds a worker Transformer that shares this transformer's
900// read-only collected maps (structs, sum types, fn return types, …) and
901// operates on its own cloned AST `ast` and forked TypeChecker `wtc`. All
902// per-function mutable state, helper-root tracking, used-fn additions, and
903// memoization caches are reset/private so the worker can run on its own thread.
904fn (t &Transformer) fork_worker(ast &flat.FlatAst, wtc &types.TypeChecker) &Transformer {
905 mut w := *t
906 w.a = ast
907 w.tc = wtc
908 w.used_fns = t.used_fns.clone()
909 w.alias_cache = &AliasCache{}
910 w.sum_cache = &AliasCache{}
911 w.generic_fn_decls_cache = map[string]GenericFnDecl{}
912 w.generic_fn_decls_ready = false
913 w.node_module_map_cache = map[int]string{}
914 w.node_module_map_nodes = -1
915 w.var_types = []VarTypeBinding{}
916 w.smartcast_stack = []SmartcastContext{}
917 w.pending_stmts = []flat.NodeId{}
918 w.pointer_value_lvalues = map[string]bool{}
919 w.escaping_amp_ptrs = map[string]bool{}
920 w.escaping_amp_sources = map[string]bool{}
921 w.heaped_amp_locals = map[string]bool{}
922 w.used_fns = t.used_fns.clone()
923 w.temp_counter = 0
924 w.cur_file = ''
925 w.cur_module = ''
926 w.cur_fn_name = ''
927 w.cur_fn_ret_type = ''
928 w.in_call_callee = false
929 w.in_const_init = false
930 w.in_return_expr = false
931 return &w
932}
933
934fn (mut t Transformer) merge_worker_used_fns(w &Transformer) {
935 for name, used in w.used_fns {
936 if used {
937 t.used_fns[name] = true
938 }
939 }
940}
941
942// merge_worker folds a finished worker's transformed output back into the master
943// AST. The worker created its new nodes/children at indices base_nodes/base_children
944// (matching the master at fork time); here they are appended to the master and every
945// reference to a worker-local new node or new children block is shifted by the
946// distance the block moved. `items` lists the function indices this worker owned, so
947// their rewritten top-level nodes can be copied into place.
948fn (mut t Transformer) merge_worker(w &Transformer, items []FnWorkItem, base_nodes int, base_children int) {
949 node_shift := i32(t.a.nodes.len - base_nodes)
950 child_shift := i32(t.a.children.len - base_children)
951 // New children: relocate references to worker-local new nodes.
952 for k in base_children .. w.a.children.len {
953 cid := w.a.children[k]
954 if int(cid) >= base_nodes {
955 t.a.children << flat.NodeId(int(cid) + int(node_shift))
956 } else {
957 t.a.children << cid
958 }
959 }
960 // New nodes: relocate children_start that points into the new children block.
961 for k in base_nodes .. w.a.nodes.len {
962 n := w.a.nodes[k]
963 if n.children_start >= base_children {
964 t.a.nodes << n.with_shifted_children(child_shift)
965 } else {
966 t.a.nodes << n
967 }
968 }
969 // Rewritten top-level function nodes keep their original index in the master.
970 for it in items {
971 n := w.a.nodes[it.fn_idx]
972 if n.children_start >= base_children {
973 t.a.nodes[it.fn_idx] = n.with_shifted_children(child_shift)
974 } else {
975 t.a.nodes[it.fn_idx] = n
976 }
977 }
978 for name, used in w.used_fns {
979 if used {
980 t.used_fns[name] = true
981 }
982 }
983}
984
985// split_work_items distributes items across `n` buckets using greedy
986// least-loaded-by-cost assignment, so heavy functions are spread evenly. The
987// assignment is deterministic for a given input (required for reproducible builds).
988fn split_work_items(items []FnWorkItem, n int) [][]FnWorkItem {
989 mut buckets := [][]FnWorkItem{len: n, init: []FnWorkItem{}}
990 mut loads := []i64{len: n}
991 for it in items {
992 mut best := 0
993 for b in 1 .. n {
994 if loads[b] < loads[best] {
995 best = b
996 }
997 }
998 buckets[best] << it
999 loads[best] += i64(it.cost) + 1
1000 }
1001 return buckets
1002}
1003
1004// should_transform_fn reports whether should transform fn applies in transform.
1005fn (t &Transformer) should_transform_fn(node flat.Node) bool {
1006 if t.used_fns.len == 0 {
1007 return true
1008 }
1009 if node.value in t.used_fns {
1010 return true
1011 }
1012 qname := transform_qualified_fn_name(t.cur_module, node.value)
1013 if qname in t.used_fns {
1014 return true
1015 }
1016 cname := c_name(qname)
1017 if cname != qname && cname in t.used_fns {
1018 return true
1019 }
1020 return false
1021}
1022
1023// transform_qualified_fn_name transforms transform qualified fn name data for transform.
1024fn transform_qualified_fn_name(mod string, name string) string {
1025 if mod.len == 0 || mod == 'main' || mod == 'builtin' {
1026 return name
1027 }
1028 return '${mod}.${name}'
1029}
1030
1031// transform_const_decl transforms the initializer expression of each const field
1032// so that const-level lowering (e.g. string concatenation in the prelude's
1033// embedded data tables) happens in the transformer rather than the backend.
1034fn (mut t Transformer) transform_const_decl(node flat.Node) {
1035 old_in_const_init := t.in_const_init
1036 t.in_const_init = true
1037 for ci in 0 .. node.children_count {
1038 cf_id := t.a.child(&node, ci)
1039 if int(cf_id) < 0 {
1040 continue
1041 }
1042 cf := t.a.nodes[int(cf_id)]
1043 if cf.kind == .const_field && cf.children_count >= 1 && cf.children_start >= 0 {
1044 val_id := t.a.child(&cf, 0)
1045 if int(val_id) < 0 {
1046 continue
1047 }
1048 val := t.a.nodes[int(val_id)]
1049 if block_val := t.const_block_value(val) {
1050 new_val := t.transform_const_value(block_val)
1051 t.a.children[cf.children_start] = new_val
1052 } else if val.kind == .string_interp {
1053 new_val := t.transform_const_string_interp(val_id, val)
1054 t.a.children[cf.children_start] = new_val
1055 } else if val.kind == .or_expr {
1056 const_typ := t.const_field_type_name(cf)
1057 new_val := t.transform_const_or_expr(val_id, val, const_typ)
1058 t.a.children[cf.children_start] = new_val
1059 } else if val.kind == .struct_init || val.kind == .cast_expr || val.kind == .call {
1060 new_val := t.transform_expr(val_id)
1061 t.a.children[cf.children_start] = new_val
1062 } else if val.kind == .infix && val.children_count >= 2 {
1063 new_val := t.transform_expr(val_id)
1064 // Overwrite the field's value slot in place (each const_field owns
1065 // its own single-element child range, so this is safe).
1066 t.a.children[cf.children_start] = new_val
1067 }
1068 }
1069 }
1070 t.in_const_init = old_in_const_init
1071}
1072
1073// const_field_type_name supports const field type name handling for Transformer.
1074fn (t &Transformer) const_field_type_name(field flat.Node) string {
1075 if field.value.len > 0 {
1076 if t.cur_module.len > 0 {
1077 if typ := t.const_type_name('${t.cur_module}.${field.value}') {
1078 return typ
1079 }
1080 }
1081 if typ := t.const_type_name(field.value) {
1082 return typ
1083 }
1084 }
1085 return field.typ
1086}
1087
1088// transform_const_or_expr transforms transform const or expr data for transform.
1089fn (mut t Transformer) transform_const_or_expr(_id flat.NodeId, node flat.Node, const_typ string) flat.NodeId {
1090 if node.children_count < 2 {
1091 return _id
1092 }
1093 mut children := []flat.NodeId{cap: int(node.children_count)}
1094 for i in 0 .. node.children_count {
1095 child_id := t.a.child(&node, i)
1096 if i == 0 {
1097 children << t.transform_expr(child_id)
1098 } else {
1099 children << child_id
1100 }
1101 }
1102 start := t.a.children.len
1103 for child in children {
1104 t.a.children << child
1105 }
1106 return t.a.add_node(flat.Node{
1107 kind: .or_expr
1108 op: node.op
1109 children_start: start
1110 children_count: node.children_count
1111 pos: node.pos
1112 value: node.value
1113 typ: if const_typ.len > 0 { const_typ } else { node.typ }
1114 })
1115}
1116
1117// transform_global_decl transforms transform global decl data for transform.
1118fn (mut t Transformer) transform_global_decl(node flat.Node) {
1119 for ci in 0 .. node.children_count {
1120 gf_id := t.a.child(&node, ci)
1121 if int(gf_id) < 0 {
1122 continue
1123 }
1124 gf := t.a.nodes[int(gf_id)]
1125 if gf.kind == .field_decl && gf.children_count >= 1 && gf.children_start >= 0 {
1126 val_id := t.a.child(&gf, 0)
1127 if int(val_id) < 0 {
1128 continue
1129 }
1130 val := t.a.nodes[int(val_id)]
1131 if preserved := t.transform_global_amp_initializer(val_id, val) {
1132 t.a.children[gf.children_start] = preserved
1133 continue
1134 }
1135 old_pending := t.pending_stmts.clone()
1136 t.pending_stmts.clear()
1137 new_val := t.transform_expr(val_id)
1138 has_pending := t.pending_stmts.len > 0
1139 t.pending_stmts.clear()
1140 t.pending_stmts = old_pending
1141 if !has_pending {
1142 t.a.children[gf.children_start] = new_val
1143 }
1144 }
1145 }
1146}
1147
1148// transform_global_amp_initializer transforms transform global amp initializer data for transform.
1149fn (mut t Transformer) transform_global_amp_initializer(val_id flat.NodeId, val flat.Node) ?flat.NodeId {
1150 if val.kind != .prefix || val.op != .amp || val.children_count != 1 {
1151 return none
1152 }
1153 child_id := t.a.child(&val, 0)
1154 child := t.a.nodes[int(child_id)]
1155 if child.kind == .assoc {
1156 return val_id
1157 }
1158 old_pending := t.pending_stmts.clone()
1159 t.pending_stmts.clear()
1160 mut result := flat.empty_node
1161 if child.kind == .struct_init {
1162 if preserved := t.transform_amp_struct_init_for_type(val_id, val, val.typ) {
1163 result = preserved
1164 }
1165 } else if child.kind == .cast_expr {
1166 if preserved := t.transform_global_amp_interface_cast(val, val.typ) {
1167 result = preserved
1168 }
1169 }
1170 has_pending := t.pending_stmts.len > 0
1171 t.pending_stmts.clear()
1172 t.pending_stmts = old_pending
1173 if has_pending || int(result) < 0 {
1174 return none
1175 }
1176 return result
1177}
1178
1179// transform_const_value transforms transform const value data for transform.
1180fn (mut t Transformer) transform_const_value(id flat.NodeId) flat.NodeId {
1181 if int(id) < 0 {
1182 return id
1183 }
1184 node := t.a.nodes[int(id)]
1185 if block_val := t.const_block_value(node) {
1186 return t.transform_const_value(block_val)
1187 }
1188 return t.transform_expr(id)
1189}
1190
1191// const_block_value supports const block value handling for Transformer.
1192fn (t &Transformer) const_block_value(node flat.Node) ?flat.NodeId {
1193 if node.kind != .block || node.children_count == 0 {
1194 return none
1195 }
1196 for i := int(node.children_count) - 1; i >= 0; i-- {
1197 stmt_id := t.a.child(&node, i)
1198 stmt := t.a.nodes[int(stmt_id)]
1199 if stmt.kind == .empty {
1200 continue
1201 }
1202 if stmt.kind == .expr_stmt && stmt.children_count == 1 {
1203 return t.a.child(&stmt, 0)
1204 }
1205 break
1206 }
1207 return none
1208}
1209
1210// transform_const_string_interp transforms transform const string interp data for transform.
1211fn (mut t Transformer) transform_const_string_interp(_id flat.NodeId, node flat.Node) flat.NodeId {
1212 if node.children_count == 0 {
1213 return t.make_string_literal('')
1214 }
1215 outer_pending := t.pending_stmts.clone()
1216 t.pending_stmts.clear()
1217 mut parts := []flat.NodeId{cap: int(node.children_count)}
1218 for i in 0 .. node.children_count {
1219 child_id := t.a.child(&node, i)
1220 parts << t.transform_string_interp_part(child_id)
1221 }
1222 mut expr := parts[0]
1223 for i in 1 .. parts.len {
1224 expr = t.make_call_typed('string__plus', arr2(expr, parts[i]), 'string')
1225 }
1226 mut stmts := []flat.NodeId{}
1227 t.drain_pending(mut stmts)
1228 t.pending_stmts = outer_pending
1229 if stmts.len == 0 {
1230 return expr
1231 }
1232 stmts << t.make_expr_stmt(expr)
1233 return t.make_block(stmts)
1234}
1235
1236fn (mut t Transformer) transform_string_interp_part(child_id flat.NodeId) flat.NodeId {
1237 mut expr_id := child_id
1238 mut format := ''
1239 child := t.a.nodes[int(child_id)]
1240 if child.kind == .directive && child.value == 'string_interp_format' && child.children_count > 0 {
1241 expr_id = t.a.child(&child, 0)
1242 format = child.typ
1243 }
1244 transformed := t.transform_expr(expr_id)
1245 mut typ := t.node_type(transformed)
1246 if typ.len == 0 {
1247 typ = t.reliable_stringify_type(transformed)
1248 }
1249 if typ.len == 0 {
1250 typ = t.reliable_stringify_type(expr_id)
1251 }
1252 if typ.len == 0 {
1253 typ = t.node_type(expr_id)
1254 }
1255 if typ.len == 0 {
1256 typ = 'string'
1257 }
1258 return t.wrap_formatted_string_conversion(transformed, typ, format)
1259}
1260
1261// transform_fn_body transforms transform fn body data for transform.
1262// try_heap_escaping_amp reports whether a `p := &v` decl is an escaping address of
1263// a value local that must be heap-copied: `p` is in escaping_amp_ptrs (returned)
1264// and `v` resolves to a non-reference value type.
1265fn (t &Transformer) try_heap_escaping_amp(node flat.Node, rhs_id flat.NodeId) bool {
1266 lhs := t.a.nodes[int(t.a.child(&node, 0))]
1267 if lhs.kind != .ident || lhs.value !in t.escaping_amp_ptrs {
1268 return false
1269 }
1270 rhs := t.a.nodes[int(rhs_id)]
1271 if rhs.kind != .prefix || rhs.op != .amp || rhs.children_count == 0 {
1272 return false
1273 }
1274 amp_child := t.a.child(&rhs, 0)
1275 amp_node := t.a.nodes[int(amp_child)]
1276 if amp_node.kind != .ident {
1277 return false
1278 }
1279 // The source local was moved to the heap at its declaration: the alias is now just that
1280 // `&T` pointer (handled below), regardless of its rewritten pointer type.
1281 if amp_node.value in t.heaped_amp_locals {
1282 return true
1283 }
1284 local_type := t.node_type(amp_child)
1285 return local_type.len > 0 && !local_type.starts_with('&') && !local_type.starts_with('[]')
1286 && !local_type.starts_with('map[') && !local_type.starts_with('?')
1287 && !local_type.starts_with('!')
1288}
1289
1290// heap_escaping_amp_rhs rewrites `&v` into `(&T)memdup(&v, sizeof(T))`, a heap copy
1291// of the value local `v` so the escaping pointer outlives the stack frame. When `v` was
1292// itself moved to the heap at its declaration, the alias is simply that pointer — copying
1293// would resurrect the stale-mutation bug the move avoids.
1294fn (mut t Transformer) heap_escaping_amp_rhs(rhs_id flat.NodeId) flat.NodeId {
1295 rhs := t.a.nodes[int(rhs_id)]
1296 amp_child := t.a.child(&rhs, 0)
1297 amp_node := t.a.nodes[int(amp_child)]
1298 if amp_node.kind == .ident && amp_node.value in t.heaped_amp_locals {
1299 return t.transform_expr(amp_child)
1300 }
1301 local_type := t.node_type(amp_child)
1302 addr := t.make_prefix(.amp, t.transform_expr(amp_child))
1303 size := t.make_sizeof_type(local_type)
1304 dup := t.make_call_typed('memdup', arr2(addr, size), 'voidptr')
1305 return t.make_cast('&${local_type}', dup, '&${local_type}')
1306}
1307
1308// heapable_value_type reports whether a local of this declared type can be moved to the heap
1309// as a `&T` — a plain value type, not an already-reference / container / optional type (those
1310// either carry their own indirection or are not addressable as a single `T`).
1311fn (t &Transformer) heapable_value_type(typ string) bool {
1312 return typ.len > 0 && !typ.starts_with('&') && !typ.starts_with('[]')
1313 && !typ.starts_with('map[') && !typ.starts_with('?') && !typ.starts_with('!')
1314 && !typ.starts_with('[') && typ != 'unknown' && typ != 'void'
1315}
1316
1317// heap_escaping_source_decl rewrites `mut v := <init>` (where `&v` escapes) into a heap
1318// allocation so `v` is a `&T` to a heap object. A struct literal becomes `&T{..}` (the cgen
1319// memdup's it); any other initializer is copied into a stack temp and memdup'd. Subsequent
1320// `v.field = ..` writes then mutate the heap object the returned pointer alias also sees.
1321fn (mut t Transformer) heap_escaping_source_decl(node flat.Node, var_name string, elem_typ string) []flat.NodeId {
1322 rhs_id := t.a.child(&node, 1)
1323 rhs := t.a.nodes[int(rhs_id)]
1324 ptr_typ := '&${elem_typ}'
1325 mut stmts := []flat.NodeId{}
1326 transformed_init := t.transform_expr(rhs_id)
1327 // Statements lifted out while transforming the initializer must precede the heap decl.
1328 t.drain_pending(mut stmts)
1329 mut heap_rhs := flat.NodeId(0)
1330 if rhs.kind == .struct_init {
1331 heap_rhs = t.make_prefix(.amp, transformed_init)
1332 } else {
1333 tmp := t.new_temp('esc')
1334 stmts << t.make_decl_assign_typed(tmp, transformed_init, elem_typ)
1335 addr := t.make_prefix(.amp, t.make_ident(tmp))
1336 size := t.make_sizeof_type(elem_typ)
1337 dup := t.make_call_typed('memdup', arr2(addr, size), 'voidptr')
1338 heap_rhs = t.make_cast(ptr_typ, dup, ptr_typ)
1339 }
1340 t.heaped_amp_locals[var_name] = true
1341 // The local is now a `&T`, so its compound/postfix mutations (`v += 1`, `v++`) must store
1342 // through the pointer (`*v += 1`); mark it as a pointer-value lvalue so that lowering fires.
1343 t.pointer_value_lvalues[var_name] = true
1344 stmts << t.make_decl_assign_typed(var_name, heap_rhs, ptr_typ)
1345 return stmts
1346}
1347
1348// mark_escaping_amp_ptrs runs a structural pre-pass over a function body to find
1349// `p := &v` declarations whose pointer `p` is later returned. Such a `v` is a local
1350// value whose address escapes, so it must be heap-copied (V auto-heaps it); the
1351// names are recorded in `escaping_amp_ptrs` and consumed by the decl-assign
1352// transform. Purely structural (no type info needed here): the type check happens
1353// at rewrite time when `v`'s type is known.
1354fn (mut t Transformer) mark_escaping_amp_ptrs(body_ids []flat.NodeId) {
1355 t.reset_escaping_amp_state()
1356 mut amp_ptrs := map[string]bool{}
1357 mut amp_sources := map[string]string{} // pointer `p` -> source local `v`
1358 mut ptr_aliases := map[string]string{} // copy `q := p` -> aliased pointer `p`
1359 mut returned := map[string]bool{}
1360 for id in body_ids {
1361 t.scan_escape_pass(id, mut amp_ptrs, mut amp_sources, mut ptr_aliases, mut returned)
1362 }
1363 // A pointer may be returned through a copy (`p := &v; q := p; return q`): `q` is collected
1364 // as returned but `p` is not, so propagate "returned" backward along the `q := p` aliases
1365 // until a fixpoint. Then `p` (and its source `v`) is recognised as escaping below.
1366 for _ in 0 .. ptr_aliases.len {
1367 mut changed := false
1368 for q, p in ptr_aliases {
1369 if q in returned && p !in returned {
1370 returned[p] = true
1371 changed = true
1372 }
1373 }
1374 if !changed {
1375 break
1376 }
1377 }
1378 for name, _ in amp_ptrs {
1379 if name in returned {
1380 t.escaping_amp_ptrs[name] = true
1381 if src := amp_sources[name] {
1382 t.escaping_amp_sources[src] = true
1383 }
1384 }
1385 }
1386}
1387
1388fn (mut t Transformer) reset_escaping_amp_state() {
1389 t.escaping_amp_ptrs.clear()
1390 t.escaping_amp_sources.clear()
1391 t.heaped_amp_locals.clear()
1392 // Cleared per function: heaped locals add their names below (in heap_escaping_source_decl);
1393 // for-loop element vars set and restore their own entries within the loop body.
1394 t.pointer_value_lvalues.clear()
1395}
1396
1397// scan_escape_pass recursively collects, in a function-body subtree, (a) the LHS
1398// names of `p := &ident` declarations into `amp_ptrs` (and the source `ident` into
1399// `amp_sources[p]`), (b) plain pointer copies `q := p` into `ptr_aliases[q] = p`, and
1400// (c) every ident name appearing inside a return statement into `returned`.
1401fn (mut t Transformer) scan_escape_pass(id flat.NodeId, mut amp_ptrs map[string]bool, mut amp_sources map[string]string, mut ptr_aliases map[string]string, mut returned map[string]bool) {
1402 if int(id) < 0 || int(id) >= t.a.nodes.len {
1403 return
1404 }
1405 node := t.a.nodes[int(id)]
1406 if node.kind == .decl_assign && node.children_count == 2 {
1407 lhs := t.a.nodes[int(t.a.child(&node, 0))]
1408 rhs := t.a.nodes[int(t.a.child(&node, 1))]
1409 if lhs.kind == .ident && lhs.value.len > 0 && rhs.kind == .prefix && rhs.op == .amp
1410 && rhs.children_count > 0 {
1411 amp_child := t.a.nodes[int(t.a.child(&rhs, 0))]
1412 if amp_child.kind == .ident {
1413 amp_ptrs[lhs.value] = true
1414 amp_sources[lhs.value] = amp_child.value
1415 }
1416 } else if lhs.kind == .ident && lhs.value.len > 0 && rhs.kind == .ident && rhs.value.len > 0 {
1417 // `q := p` aliases an existing pointer; recorded so a returned alias still marks the
1418 // underlying `p := &v` as escaping.
1419 ptr_aliases[lhs.value] = rhs.value
1420 }
1421 }
1422 if node.kind == .return_stmt {
1423 for i in 0 .. node.children_count {
1424 t.collect_return_escape_idents(t.a.child(&node, i), mut returned)
1425 }
1426 }
1427 for i in 0 .. node.children_count {
1428 t.scan_escape_pass(t.a.child(&node, i), mut amp_ptrs, mut amp_sources, mut ptr_aliases, mut
1429 returned)
1430 }
1431}
1432
1433// collect_return_escape_idents gathers the idents in a return-expression subtree that occupy an
1434// actual escape position — the returned value itself, or a member of a returned aggregate
1435// (struct/array/map literal, multi-return). It deliberately stops at operators that consume their
1436// operands into a fresh value: infix (`==`, `&&`, arithmetic, …), postfix, `is`/`in`, and any
1437// non-`&` prefix (deref `*p`, `!x`, `-x`). That way a pointer that is merely compared or
1438// dereferenced in the return expression — e.g. `return p == p && v == 1` — is not mistaken for a
1439// pointer that escapes, so its source local is not needlessly heap-moved (which would also make
1440// later non-pointer uses of that local read through an `int*`).
1441fn (mut t Transformer) collect_return_escape_idents(id flat.NodeId, mut names map[string]bool) {
1442 if int(id) < 0 || int(id) >= t.a.nodes.len {
1443 return
1444 }
1445 node := t.a.nodes[int(id)]
1446 match node.kind {
1447 .ident {
1448 if node.value.len > 0 {
1449 names[node.value] = true
1450 }
1451 return
1452 }
1453 .infix, .postfix, .is_expr, .in_expr {
1454 // These yield a new scalar/bool; their operands do not escape through the return.
1455 return
1456 }
1457 .prefix {
1458 // `&x` propagates an address (which may escape); any other prefix (`*x`, `!x`, `-x`)
1459 // produces a fresh value, so its operand does not escape.
1460 if node.op != .amp {
1461 return
1462 }
1463 }
1464 else {}
1465 }
1466
1467 for i in 0 .. node.children_count {
1468 t.collect_return_escape_idents(t.a.child(&node, i), mut names)
1469 }
1470}
1471
1472fn (mut t Transformer) transform_fn_body(fn_idx int) {
1473 fn_node := t.a.nodes[fn_idx]
1474 t.cur_fn_name = fn_node.value
1475 old_is_generic := t.cur_fn_is_generic
1476 t.cur_fn_is_generic = t.fn_decl_has_unresolved_generics(fn_node, t.cur_module)
1477 param_count := t.fn_body_param_count(fn_node)
1478 param_types := t.fn_body_param_types(fn_node, param_count)
1479 t.cur_fn_ret_type = t.fn_body_return_type(fn_node)
1480 t.reset_var_types()
1481 t.smartcast_stack.clear()
1482 // Collect param types
1483 mut param_idx := 0
1484 for i in 0 .. fn_node.children_count {
1485 child_id := t.a.children[fn_node.children_start + i]
1486 if int(child_id) < 0 {
1487 continue
1488 }
1489 child := t.a.nodes[int(child_id)]
1490 if node_kind_id(child) == 75 && child.value.len > 0 && child.typ.len > 0 {
1491 raw_typ := t.normalize_type_alias(child.typ)
1492 mut typ := if raw_typ.len > 0 {
1493 raw_typ
1494 } else if param_idx < param_types.len {
1495 t.normalize_type_alias(param_types[param_idx].name())
1496 } else {
1497 ''
1498 }
1499 if typ.starts_with('&') && raw_typ.len > 0 && !raw_typ.starts_with('&')
1500 && t.normalize_type_alias(typ[1..]) == raw_typ {
1501 typ = raw_typ
1502 }
1503 t.set_var_type(child.value, typ)
1504 param_idx++
1505 }
1506 }
1507 mut body_ids := []flat.NodeId{cap: int(fn_node.children_count)}
1508 for i in 0 .. fn_node.children_count {
1509 child_id := t.a.children[fn_node.children_start + i]
1510 if int(child_id) < 0 {
1511 continue
1512 }
1513 child := t.a.nodes[int(child_id)]
1514 if node_kind_id(child) != 75 {
1515 body_ids << child_id
1516 }
1517 }
1518 if t.cur_fn_ret_type == 'void' {
1519 t.reset_escaping_amp_state()
1520 } else {
1521 t.mark_escaping_amp_ptrs(body_ids)
1522 }
1523 new_body := t.transform_stmts(body_ids)
1524 // Rebuild function children: params then new body
1525 start := t.a.children.len
1526 for i in 0 .. fn_node.children_count {
1527 child_id := t.a.children[fn_node.children_start + i]
1528 if int(child_id) < 0 {
1529 continue
1530 }
1531 child := t.a.nodes[int(child_id)]
1532 if node_kind_id(child) == 75 {
1533 t.a.children << child_id
1534 }
1535 }
1536 for id in new_body {
1537 t.a.children << id
1538 }
1539 count := t.a.children.len - start
1540 t.a.nodes[fn_idx] = flat.Node{
1541 kind: .fn_decl
1542 kind_id: 61
1543 op: fn_node.op
1544 children_start: start
1545 children_count: flat.child_count(count)
1546 pos: fn_node.pos
1547 value: fn_node.value
1548 typ: fn_node.typ
1549 generic_params: fn_node.generic_params
1550 }
1551 t.smartcast_stack.clear()
1552 t.cur_fn_is_generic = old_is_generic
1553}
1554
1555// fn_body_param_types supports fn body param types handling for Transformer.
1556fn (t &Transformer) fn_body_param_types(fn_node flat.Node, expected int) []types.Type {
1557 if isnil(t.tc) {
1558 return []types.Type{}
1559 }
1560 if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
1561 qname := '${t.cur_module}.${fn_node.value}'
1562 if params := t.fn_param_types_for_name(qname, expected) {
1563 return params
1564 }
1565 cqname := c_name(qname)
1566 if cqname != qname {
1567 if params := t.fn_param_types_for_name(cqname, expected) {
1568 return params
1569 }
1570 }
1571 }
1572 if params := t.fn_param_types_for_name(fn_node.value, expected) {
1573 return params
1574 }
1575 cname := c_name(fn_node.value)
1576 if cname != fn_node.value {
1577 if params := t.fn_param_types_for_name(cname, expected) {
1578 return params
1579 }
1580 }
1581 return []types.Type{}
1582}
1583
1584// fn_param_types_for_name supports fn param types for name handling for Transformer.
1585fn (t &Transformer) fn_param_types_for_name(name string, expected int) ?[]types.Type {
1586 params := t.tc.fn_param_types[name] or { return none }
1587 if expected != 0 && params.len != expected {
1588 return none
1589 }
1590 return params
1591}
1592
1593// fn_body_param_count supports fn body param count handling for Transformer.
1594fn (t &Transformer) fn_body_param_count(fn_node flat.Node) int {
1595 mut n := 0
1596 for i in 0 .. fn_node.children_count {
1597 child := t.a.child_node(&fn_node, i)
1598 if child.kind == .param {
1599 n++
1600 }
1601 }
1602 return n
1603}
1604
1605// fn_body_return_type supports fn body return type handling for Transformer.
1606fn (t &Transformer) fn_body_return_type(fn_node flat.Node) string {
1607 if !isnil(t.tc) {
1608 if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
1609 qname := '${t.cur_module}.${fn_node.value}'
1610 if ret := t.fn_return_type_for_name(qname) {
1611 return ret
1612 }
1613 cqname := c_name(qname)
1614 if cqname != qname {
1615 if ret := t.fn_return_type_for_name(cqname) {
1616 return ret
1617 }
1618 }
1619 }
1620 if ret := t.fn_return_type_for_name(fn_node.value) {
1621 return ret
1622 }
1623 cname := c_name(fn_node.value)
1624 if cname != fn_node.value {
1625 if ret := t.fn_return_type_for_name(cname) {
1626 return ret
1627 }
1628 }
1629 }
1630 return t.normalize_type_alias(fn_node.typ)
1631}
1632
1633// fn_return_type_for_name supports fn return type for name handling for Transformer.
1634fn (t &Transformer) fn_return_type_for_name(name string) ?string {
1635 ret := t.tc.fn_ret_types[name] or { return none }
1636 return t.normalize_type_alias(ret.name())
1637}
1638
1639// --- statement list driver ---
1640
1641// transform_stmts transforms transform stmts data for transform.
1642pub fn (mut t Transformer) transform_stmts(ids []flat.NodeId) []flat.NodeId {
1643 mut result := []flat.NodeId{cap: ids.len}
1644 mut i := 0
1645 for i < ids.len {
1646 id := ids[i]
1647 if int(id) >= 0 && i + 1 < ids.len {
1648 node := t.a.nodes[int(id)]
1649 if node_kind_id(node) == 44 && node.children_count == 0 && t.cur_fn_ret_type.len > 0
1650 && t.cur_fn_ret_type != 'void' {
1651 next_id := ids[i + 1]
1652 next_node := t.a.nodes[int(next_id)]
1653 if node_kind_id(next_node) == 39 && next_node.children_count > 0 {
1654 expr_id := t.a.child(&next_node, 0)
1655 start := t.a.children.len
1656 t.a.children << expr_id
1657 merged_return := t.a.add_node(flat.Node{
1658 kind: .return_stmt
1659 children_start: start
1660 children_count: 1
1661 typ: node.typ
1662 })
1663 expanded := t.transform_stmt(merged_return)
1664 t.drain_pending(mut result)
1665 for eid in expanded {
1666 result << eid
1667 }
1668 i += 2
1669 continue
1670 }
1671 }
1672 if node.kind == .label_stmt {
1673 next_id := ids[i + 1]
1674 next_node := t.a.nodes[int(next_id)]
1675 if next_node.kind in [.for_stmt, .for_in_stmt] {
1676 expanded := t.transform_labeled_loop(node.value, next_id, next_node)
1677 t.drain_pending(mut result)
1678 for eid in expanded {
1679 result << eid
1680 }
1681 i += 2
1682 continue
1683 }
1684 }
1685 }
1686 expanded := t.transform_stmt(id)
1687 t.drain_pending(mut result)
1688 for eid in expanded {
1689 result << eid
1690 }
1691 i++
1692 }
1693 t.drain_pending(mut result)
1694 return result
1695}
1696
1697// transform_labeled_loop transforms transform labeled loop data for transform.
1698fn (mut t Transformer) transform_labeled_loop(label string, loop_id flat.NodeId, loop_node flat.Node) []flat.NodeId {
1699 if label.len == 0 {
1700 return t.transform_stmt(loop_id)
1701 }
1702 continue_label := '${label}_continue'
1703 break_label := '${label}_break'
1704 body_start := if loop_node.kind == .for_in_stmt { loop_node.value.int() } else { 3 }
1705 mut children := []flat.NodeId{cap: int(loop_node.children_count) + 1}
1706 for i in 0 .. loop_node.children_count {
1707 children << t.a.child(&loop_node, i)
1708 }
1709 if body_start <= children.len {
1710 children << t.a.add_val(.label_stmt, continue_label)
1711 }
1712 start := t.a.children.len
1713 for child in children {
1714 t.a.children << child
1715 }
1716 new_loop := t.a.add_node(flat.Node{
1717 kind: loop_node.kind
1718 op: loop_node.op
1719 children_start: start
1720 children_count: flat.child_count(children.len)
1721 pos: loop_node.pos
1722 value: loop_node.value
1723 typ: loop_node.typ
1724 })
1725 mut result := []flat.NodeId{}
1726 result << t.a.add_val(.label_stmt, label)
1727 result << t.transform_stmt(new_loop)
1728 result << t.a.add_val(.label_stmt, break_label)
1729 return result
1730}
1731
1732// transform_stmt transforms transform stmt data for transform.
1733pub fn (mut t Transformer) transform_stmt(id flat.NodeId) []flat.NodeId {
1734 if int(id) < 0 {
1735 return arr1(id)
1736 }
1737 node := t.a.nodes[int(id)]
1738 kind_id := node_kind_id(node)
1739 if kind_id == 44 {
1740 return t.transform_return_stmt(id, node)
1741 }
1742 if kind_id == 40 || kind_id == 42 || kind_id == 43 {
1743 return t.transform_assign_stmt(id, node)
1744 }
1745 if kind_id == 41 {
1746 return t.transform_decl_assign_stmt(id, node)
1747 }
1748 if kind_id == 39 {
1749 return t.transform_expr_stmt(id, node)
1750 }
1751 if kind_id == 46 {
1752 return t.transform_for_stmt(id, node)
1753 }
1754 if kind_id == 47 {
1755 return t.transform_for_in_stmt(id, node)
1756 }
1757 if kind_id == 45 {
1758 return t.transform_block_stmt(id, node)
1759 }
1760 if kind_id == 15 {
1761 return t.transform_if_stmt(id, node)
1762 }
1763 if kind_id == 50 {
1764 return arr1(t.lower_one_match(node))
1765 }
1766 if kind_id == 52 {
1767 return t.transform_defer_stmt(id, node)
1768 }
1769 if kind_id == 53 || kind_id == 56 {
1770 return t.transform_children_stmt(id, node)
1771 }
1772 match node.kind {
1773 .return_stmt {
1774 return t.transform_return_stmt(id, node)
1775 }
1776 .assign, .selector_assign, .index_assign {
1777 return t.transform_assign_stmt(id, node)
1778 }
1779 .decl_assign {
1780 return t.transform_decl_assign_stmt(id, node)
1781 }
1782 .expr_stmt {
1783 return t.transform_expr_stmt(id, node)
1784 }
1785 .for_stmt {
1786 return t.transform_for_stmt(id, node)
1787 }
1788 .for_in_stmt {
1789 return t.transform_for_in_stmt(id, node)
1790 }
1791 .block {
1792 return t.transform_block_stmt(id, node)
1793 }
1794 .comptime_if {
1795 return t.transform_comptime_if_stmt(id, node)
1796 }
1797 .if_expr {
1798 return t.transform_if_stmt(id, node)
1799 }
1800 .match_stmt {
1801 return arr1(t.lower_one_match(node))
1802 }
1803 .defer_stmt {
1804 return t.transform_defer_stmt(id, node)
1805 }
1806 .assert_stmt {
1807 return t.transform_children_stmt(id, node)
1808 }
1809 .select_stmt {
1810 return t.transform_children_stmt(id, node)
1811 }
1812 else {
1813 return arr1(id)
1814 }
1815 }
1816}
1817
1818// transform_expr transforms transform expr data for transform.
1819pub fn (mut t Transformer) transform_expr(id flat.NodeId) flat.NodeId {
1820 if int(id) < 0 {
1821 return id
1822 }
1823 node := t.a.nodes[int(id)]
1824 kind_id := node_kind_id(node)
1825 if kind_id == 8 {
1826 return t.transform_infix_expr(id, node)
1827 }
1828 if kind_id == 12 {
1829 return t.transform_call_expr(id, node)
1830 }
1831 if kind_id == 15 {
1832 return t.transform_if_expr(id, node)
1833 }
1834 if kind_id == 16 {
1835 return t.transform_struct_init(id, node)
1836 }
1837 if kind_id == 17 {
1838 return t.transform_field_init_expr(id, node)
1839 }
1840 if kind_id == 14 {
1841 return t.transform_index_expr(id, node)
1842 }
1843 if kind_id == 6 {
1844 return t.transform_string_interp(id, node)
1845 }
1846 if kind_id == 13 {
1847 return t.transform_selector_expr(id, node)
1848 }
1849 if kind_id == 22 {
1850 return t.transform_or_expr(id, node)
1851 }
1852 if kind_id == 24 {
1853 return t.transform_as_expr(id, node)
1854 }
1855 if kind_id == 9 {
1856 return t.transform_prefix_expr(id, node)
1857 }
1858 if kind_id == 11 {
1859 return t.transform_paren_expr(id, node)
1860 }
1861 if kind_id == 10 {
1862 return t.transform_postfix_expr(id, node)
1863 }
1864 if kind_id == 23 {
1865 return t.transform_cast_expr(id, node)
1866 }
1867 if kind_id == 18 {
1868 return t.transform_array_literal(id, node)
1869 }
1870 if kind_id == 19 {
1871 return t.transform_array_init_expr(id, node)
1872 }
1873 if kind_id == 20 {
1874 return t.transform_map_init(id, node)
1875 }
1876 if kind_id == 38 {
1877 return t.transform_in_expr(id, node)
1878 }
1879 if kind_id == 37 {
1880 return t.transform_is_expr(id, node)
1881 }
1882 if kind_id == 50 {
1883 return t.lower_one_match(node)
1884 }
1885 if kind_id == 45 {
1886 return t.transform_block_expr(id, node)
1887 }
1888 if kind_id == 31 {
1889 return t.transform_lock_expr(id, node)
1890 }
1891 if kind_id == 34 {
1892 return t.transform_typeof_expr(id, node)
1893 }
1894 if kind_id == 7 {
1895 return t.transform_ident_expr(id, node)
1896 }
1897 if kind_id == 26 {
1898 return t.transform_assoc_expr(id, node)
1899 }
1900 if kind_id == 21 {
1901 return t.lift_fn_literal(id, node)
1902 }
1903 if kind_id == 30 || kind_id == 35 || kind_id == 27 || kind_id == 56 || kind_id == 57 {
1904 return t.transform_children_expr(id, node)
1905 }
1906 if kind_id == 1 || kind_id == 2 || kind_id == 3 || kind_id == 4 || kind_id == 5 || kind_id == 28
1907 || kind_id == 29 || kind_id == 25 || kind_id == 33 || kind_id == 36 {
1908 return id
1909 }
1910 match node.kind {
1911 .infix {
1912 return t.transform_infix_expr(id, node)
1913 }
1914 .call {
1915 return t.transform_call_expr(id, node)
1916 }
1917 .if_expr {
1918 return t.transform_if_expr(id, node)
1919 }
1920 .struct_init {
1921 return t.transform_struct_init(id, node)
1922 }
1923 .field_init {
1924 return t.transform_field_init_expr(id, node)
1925 }
1926 .index {
1927 return t.transform_index_expr(id, node)
1928 }
1929 .string_interp {
1930 return t.transform_string_interp(id, node)
1931 }
1932 .selector {
1933 return t.transform_selector_expr(id, node)
1934 }
1935 .or_expr {
1936 return t.transform_or_expr(id, node)
1937 }
1938 .as_expr {
1939 return t.transform_as_expr(id, node)
1940 }
1941 .prefix {
1942 return t.transform_prefix_expr(id, node)
1943 }
1944 .paren {
1945 return t.transform_paren_expr(id, node)
1946 }
1947 .postfix {
1948 return t.transform_postfix_expr(id, node)
1949 }
1950 .cast_expr {
1951 return t.transform_cast_expr(id, node)
1952 }
1953 .array_literal {
1954 return t.transform_array_literal(id, node)
1955 }
1956 .array_init {
1957 return t.transform_array_init_expr(id, node)
1958 }
1959 .map_init {
1960 return t.transform_map_init(id, node)
1961 }
1962 .sql_expr {
1963 return t.transform_sql_expr(id, node)
1964 }
1965 .in_expr {
1966 return t.transform_in_expr(id, node)
1967 }
1968 .is_expr {
1969 return t.transform_is_expr(id, node)
1970 }
1971 .match_stmt {
1972 return t.lower_one_match(node)
1973 }
1974 .block {
1975 return t.transform_block_expr(id, node)
1976 }
1977 .lock_expr {
1978 return t.transform_lock_expr(id, node)
1979 }
1980 .typeof_expr {
1981 return t.transform_typeof_expr(id, node)
1982 }
1983 .ident {
1984 return t.transform_ident_expr(id, node)
1985 }
1986 .assoc {
1987 return t.transform_assoc_expr(id, node)
1988 }
1989 .fn_literal {
1990 return t.lift_fn_literal(id, node)
1991 }
1992 .lambda_expr, .spawn_expr, .dump_expr, .range, .select_stmt, .select_branch {
1993 return t.transform_children_expr(id, node)
1994 }
1995 .int_literal, .float_literal, .bool_literal, .char_literal, .string_literal, .nil_literal,
1996 .none_expr, .enum_val, .sizeof_expr, .offsetof_expr {
1997 // leaf/simple nodes - pass through unchanged
1998 return id
1999 }
2000 else {
2001 return id
2002 }
2003 }
2004}
2005
2006// transform_lvalue transforms transform lvalue data for transform.
2007pub fn (mut t Transformer) transform_lvalue(id flat.NodeId) flat.NodeId {
2008 if int(id) < 0 {
2009 return id
2010 }
2011 node := t.a.nodes[int(id)]
2012 match node.kind {
2013 .ident {
2014 return id
2015 }
2016 .selector {
2017 if node.children_count == 0 {
2018 return id
2019 }
2020 if t.selector_chain_has_sum_shared_field(id) {
2021 value := t.transform_selector_expr(id, node)
2022 mut value_type := t.node_type(id)
2023 if value_type.len == 0 {
2024 value_type = t.node_type(value)
2025 }
2026 return t.stable_transformed_expr_for_reuse(value, value_type, 'lvalue')
2027 }
2028 full_key := t.expr_key(id)
2029 if t.has_smartcast(full_key) {
2030 return t.transform_selector_expr(id, node)
2031 }
2032 base_id := t.a.child(&node, 0)
2033 base_key := t.expr_key(base_id)
2034 if t.has_smartcast(base_key) {
2035 return t.transform_selector_expr(id, node)
2036 }
2037 base := t.transform_lvalue(t.a.child(&node, 0))
2038 mut new_children := []flat.NodeId{cap: int(node.children_count)}
2039 new_children << base
2040 for i in 1 .. node.children_count {
2041 new_children << t.transform_expr(t.a.child(&node, i))
2042 }
2043 start := t.a.children.len
2044 for child in new_children {
2045 t.a.children << child
2046 }
2047 return t.a.add_node(flat.Node{
2048 kind: .selector
2049 op: node.op
2050 children_start: start
2051 children_count: flat.child_count(new_children.len)
2052 pos: node.pos
2053 value: node.value
2054 typ: node.typ
2055 })
2056 }
2057 .index {
2058 if node.children_count == 0 {
2059 return id
2060 }
2061 mut new_children := []flat.NodeId{cap: int(node.children_count)}
2062 new_children << t.transform_expr(t.a.child(&node, 0))
2063 for i in 1 .. node.children_count {
2064 new_children << t.transform_expr(t.a.child(&node, i))
2065 }
2066 start := t.a.children.len
2067 for child in new_children {
2068 t.a.children << child
2069 }
2070 return t.a.add_node(flat.Node{
2071 kind: .index
2072 op: node.op
2073 children_start: start
2074 children_count: flat.child_count(new_children.len)
2075 pos: node.pos
2076 value: node.value
2077 typ: node.typ
2078 })
2079 }
2080 .prefix {
2081 if node.op == .mul && node.children_count > 0 {
2082 child := t.transform_expr(t.a.child(&node, 0))
2083 start := t.a.children.len
2084 t.a.children << child
2085 return t.a.add_node(flat.Node{
2086 kind: .prefix
2087 op: node.op
2088 children_start: start
2089 children_count: 1
2090 pos: node.pos
2091 value: node.value
2092 typ: node.typ
2093 })
2094 }
2095 return t.transform_expr(id)
2096 }
2097 .paren {
2098 if node.children_count == 0 {
2099 return id
2100 }
2101 child := t.transform_lvalue(t.a.child(&node, 0))
2102 start := t.a.children.len
2103 t.a.children << child
2104 return t.a.add_node(flat.Node{
2105 kind: .paren
2106 op: node.op
2107 children_start: start
2108 children_count: 1
2109 pos: node.pos
2110 value: node.value
2111 typ: node.typ
2112 })
2113 }
2114 else {
2115 return t.transform_expr(id)
2116 }
2117 }
2118}
2119
2120// --- stmt handlers (skeleton - identity transforms with child recursion) ---
2121
2122// transform_return_stmt transforms transform return stmt data for transform.
2123fn (mut t Transformer) transform_return_stmt(id flat.NodeId, node flat.Node) []flat.NodeId {
2124 if node.children_count == 0 {
2125 return arr1(id)
2126 }
2127 if expanded := t.try_expand_return_if(id, node) {
2128 return expanded
2129 }
2130 if expanded := t.try_expand_return_match(id, node) {
2131 return expanded
2132 }
2133 if direct := t.try_return_direct_optional_expr(node) {
2134 return direct
2135 }
2136 if expanded := t.try_expand_return_optional_expr(node) {
2137 return expanded
2138 }
2139 if node.children_count == 1 {
2140 child_id := t.a.child(&node, 0)
2141 if t.is_optional_type_name(t.cur_fn_ret_type) && t.return_expr_is_err(child_id) {
2142 return t.with_pending_before(t.make_none_return_stmt())
2143 }
2144 }
2145 mut new_children := []flat.NodeId{cap: int(node.children_count)}
2146 for i in 0 .. node.children_count {
2147 child_id := t.a.child(&node, i)
2148 new_children << t.transform_return_child(child_id, i, int(node.children_count))
2149 }
2150 start := t.a.children.len
2151 for nc in new_children {
2152 t.a.children << nc
2153 }
2154 new_id := t.a.add_node(flat.Node{
2155 kind: .return_stmt
2156 op: node.op
2157 children_start: start
2158 children_count: node.children_count
2159 pos: node.pos
2160 value: node.value
2161 typ: node.typ
2162 })
2163 return t.with_pending_before(new_id)
2164}
2165
2166fn (mut t Transformer) return_values_with_extra(first_id flat.NodeId, extra_ids []flat.NodeId) []flat.NodeId {
2167 total := extra_ids.len + 1
2168 mut vals := []flat.NodeId{cap: total}
2169 vals << t.transform_return_child(first_id, 0, total)
2170 for i, extra_id in extra_ids {
2171 vals << t.transform_return_child(extra_id, i + 1, total)
2172 }
2173 return vals
2174}
2175
2176fn (mut t Transformer) transform_return_child(child_id flat.NodeId, child_index int, total_children int) flat.NodeId {
2177 if converted := t.fixed_array_return_value(child_id) {
2178 return converted
2179 }
2180 if copied := t.heap_copy_local_address_return(child_id) {
2181 return copied
2182 }
2183 target_type := t.return_child_target_type(child_index, total_children)
2184 if target_type.len > 0 && target_type !in t.sum_types && !t.is_optional_type_name(target_type) {
2185 return t.transform_expr_for_type(child_id, target_type)
2186 }
2187 return t.wrap_sum_return_expr(child_id)
2188}
2189
2190fn (t &Transformer) return_child_target_type(child_index int, total_children int) string {
2191 if total_children > 1 && !isnil(t.tc) && t.cur_fn_ret_type.len > 0 {
2192 if items := multi_return_types_from_type(t.tc.parse_type(t.cur_fn_ret_type), total_children) {
2193 if child_index >= 0 && child_index < items.len {
2194 return items[child_index].name()
2195 }
2196 }
2197 }
2198 return t.cur_fn_ret_type
2199}
2200
2201// heap_copy_local_address_return supports heap copy local address return handling for Transformer.
2202fn (mut t Transformer) heap_copy_local_address_return(child_id flat.NodeId) ?flat.NodeId {
2203 if !t.cur_fn_ret_type.starts_with('&') || int(child_id) < 0 {
2204 return none
2205 }
2206 node := t.a.nodes[int(child_id)]
2207 if node.kind != .prefix || node.op != .amp || node.children_count != 1 {
2208 return none
2209 }
2210 inner_id := t.a.child(&node, 0)
2211 inner := t.a.nodes[int(inner_id)]
2212 if inner.kind != .ident || inner.value.len == 0 {
2213 return none
2214 }
2215 local_type := t.var_type(inner.value)
2216 if local_type.len == 0 {
2217 return none
2218 }
2219 ret_base_type := t.cur_fn_ret_type[1..]
2220 if ret_base_type.len == 0 {
2221 return none
2222 }
2223 clean_local_type := t.normalize_type_alias(local_type)
2224 clean_ret_type := t.normalize_type_alias(ret_base_type)
2225 if clean_local_type != clean_ret_type && local_type != ret_base_type {
2226 return none
2227 }
2228 addr := t.make_prefix(.amp, t.make_ident(inner.value))
2229 size := t.make_sizeof_type(ret_base_type)
2230 dup := t.make_call_typed('memdup', arr2(addr, size), 'voidptr')
2231 return t.make_cast(t.cur_fn_ret_type, dup, t.cur_fn_ret_type)
2232}
2233
2234// fixed_array_return_value supports fixed array return value handling for Transformer.
2235fn (mut t Transformer) fixed_array_return_value(child_id flat.NodeId) ?flat.NodeId {
2236 mut ret_type := t.cur_fn_ret_type
2237 if t.is_optional_type_name(ret_type) {
2238 ret_type = t.optional_base_type(ret_type)
2239 }
2240 // A function whose declared return type is itself a fixed array keeps
2241 // fixed-array (by-value) semantics; the C backend returns it via a wrapper
2242 // struct. Only a *dynamic* array return needs a fixed→dynamic conversion of a
2243 // fixed-array return value.
2244 if t.is_fixed_array_type(ret_type) {
2245 return none
2246 }
2247 return t.fixed_array_value_to_dynamic(child_id, ret_type)
2248}
2249
2250// fixed_array_value_to_dynamic converts a fixed-array *value* (e.g. a fixed-array
2251// const or variable, not a literal — those have their own lowering) to a dynamic
2252// array when `target_type` is `[]T` with a matching element type. Returns none
2253// when no conversion is needed/possible.
2254fn (mut t Transformer) fixed_array_value_to_dynamic(value_id flat.NodeId, target_type string) ?flat.NodeId {
2255 array_type := target_type
2256 if !array_type.starts_with('[]') {
2257 return none
2258 }
2259 child_type := t.node_type(value_id)
2260 if !t.is_fixed_array_type(child_type) || fixed_array_elem_type(child_type) != array_type[2..] {
2261 return none
2262 }
2263 return t.fixed_array_value_to_array(value_id, child_type, array_type)
2264}
2265
2266// transform_assign_stmt transforms transform assign stmt data for transform.
2267fn (mut t Transformer) transform_assign_stmt(id flat.NodeId, node flat.Node) []flat.NodeId {
2268 if node.children_count == 0 {
2269 return arr1(id)
2270 }
2271 if expanded := t.try_expand_multi_return_assign(node) {
2272 return expanded
2273 }
2274 if expanded := t.try_expand_plain_multi_assign(node) {
2275 return expanded
2276 }
2277 if lowered := t.try_lower_sum_shared_field_assign(node) {
2278 return lowered
2279 }
2280 if lowered := t.try_lower_optional_selector_lvalue_assign(node) {
2281 return lowered
2282 }
2283 if lowered := t.try_lower_pointer_value_assign(node) {
2284 return lowered
2285 }
2286 if lowered := t.try_lower_map_index_assign(node) {
2287 return lowered
2288 }
2289 // string `s += x` on a plain ident -> `s = string__plus(s, x)` (only when detectable as string)
2290 if expanded := t.try_lower_string_compound_assign(id, node) {
2291 return expanded
2292 }
2293 if expanded := t.try_lower_struct_compound_assign(node) {
2294 return expanded
2295 }
2296 mut new_children := []flat.NodeId{cap: int(node.children_count)}
2297 for i in 0 .. node.children_count {
2298 child_id := t.a.child(&node, i)
2299 if i % 2 == 0 {
2300 new_children << t.transform_lvalue(child_id)
2301 } else {
2302 lhs_id := t.a.child(&node, i - 1)
2303 lhs := t.a.nodes[int(lhs_id)]
2304 mut lhs_type := if lhs.kind in [.selector, .index] {
2305 t.lvalue_type(lhs_id)
2306 } else {
2307 t.original_expr_type(lhs_id)
2308 }
2309 if lhs_type.len == 0 {
2310 lhs_type = t.lvalue_type(lhs_id)
2311 }
2312 // A value local moved to the heap (its type became `&T`) is assigned by storing a
2313 // value through the pointer (cgen emits `*v = ...`), so coerce the RHS to the value
2314 // type `T`, not `&T`. Otherwise a heaped-local RHS (`v = w`, both `&T`) is copied as a
2315 // pointer — aliasing `w`'s object — instead of dereferenced to its value.
2316 if lhs.kind == .ident && lhs.value in t.heaped_amp_locals && lhs_type.starts_with('&') {
2317 lhs_type = lhs_type[1..]
2318 }
2319 sum_target := t.assignment_sum_target(lhs_id, child_id, lhs_type)
2320 if node.op == .assign && sum_target.len > 0 {
2321 new_children << t.wrap_sum_value(child_id, sum_target)
2322 } else {
2323 new_children << t.transform_expr_for_type(child_id, lhs_type)
2324 }
2325 }
2326 }
2327 start := t.a.children.len
2328 for nc in new_children {
2329 t.a.children << nc
2330 }
2331 new_id := t.a.add_node(flat.Node{
2332 kind: node.kind
2333 op: node.op
2334 children_start: start
2335 children_count: node.children_count
2336 pos: node.pos
2337 value: node.value
2338 typ: node.typ
2339 })
2340 if node.kind == .assign && node.op == .left_shift_assign {
2341 t.annotate_left_shift_assign(new_id)
2342 }
2343 return t.with_pending_before(new_id)
2344}
2345
2346fn (mut t Transformer) try_lower_optional_selector_lvalue_assign(node flat.Node) ?[]flat.NodeId {
2347 if node.kind != .selector_assign || node.children_count != 2 {
2348 return none
2349 }
2350 lhs_id := t.a.child(&node, 0)
2351 rhs_id := t.a.child(&node, 1)
2352 if int(lhs_id) < 0 || int(rhs_id) < 0 {
2353 return none
2354 }
2355 lowered_lhs, guard_source, guard_body, guard_mode := t.lower_optional_selector_lvalue(lhs_id) or {
2356 return none
2357 }
2358 mut result := []flat.NodeId{}
2359 t.drain_pending(mut result)
2360 not_ok := t.make_prefix(.not, t.make_selector(guard_source, 'ok', 'bool'))
2361 guard_stmts := t.optional_selector_lvalue_guard_stmts(guard_body, guard_mode, guard_source)
2362 result << t.make_if(not_ok, t.make_block(guard_stmts), t.make_empty())
2363 lhs_type := t.lvalue_type(lhs_id)
2364 sum_target := t.assignment_sum_target(lhs_id, rhs_id, lhs_type)
2365 rhs := if node.op == .assign && sum_target.len > 0 {
2366 t.wrap_sum_value(rhs_id, sum_target)
2367 } else {
2368 t.transform_expr_for_type(rhs_id, lhs_type)
2369 }
2370 t.drain_pending(mut result)
2371 result << t.make_assign_op(lowered_lhs, rhs, node.op)
2372 return result
2373}
2374
2375fn (mut t Transformer) optional_selector_lvalue_guard_stmts(body_id flat.NodeId, mode string, guard_source flat.NodeId) []flat.NodeId {
2376 err_expr := t.make_selector(guard_source, 'err', 'IError')
2377 if mode == '!' || mode == '?' {
2378 if t.is_optional_type_name(t.cur_fn_ret_type) {
2379 return arr1(t.make_return(t.make_optional_none_with_err(t.cur_fn_ret_type, err_expr),
2380 t.cur_fn_ret_type))
2381 }
2382 return arr1(t.make_panic_stmt('option/result propagation failed'))
2383 }
2384 return t.lower_or_body_to_stmts_with_err_expr(body_id, '', '', mode, err_expr)
2385}
2386
2387fn (mut t Transformer) lower_optional_selector_lvalue(id flat.NodeId) ?(flat.NodeId, flat.NodeId, flat.NodeId, string) {
2388 if int(id) < 0 {
2389 return none
2390 }
2391 node := t.a.nodes[int(id)]
2392 if node.kind != .selector || node.children_count == 0 || node.value.len == 0 {
2393 return none
2394 }
2395 base_id := t.a.child(&node, 0)
2396 base := t.a.nodes[int(base_id)]
2397 if base.kind == .or_expr && base.children_count >= 2 {
2398 return t.lower_optional_selector_lvalue_from_or(id, node, base)
2399 }
2400 if base.kind == .paren && base.children_count > 0 {
2401 inner_id := t.a.child(&base, 0)
2402 inner := t.a.nodes[int(inner_id)]
2403 if inner.kind == .or_expr && inner.children_count >= 2 {
2404 return t.lower_optional_selector_lvalue_from_or(id, node, inner)
2405 }
2406 }
2407 lowered_base, guard_source, guard_body, guard_mode := t.lower_optional_selector_lvalue(base_id) or {
2408 return none
2409 }
2410 mut new_children := []flat.NodeId{cap: int(node.children_count)}
2411 new_children << lowered_base
2412 for i in 1 .. node.children_count {
2413 new_children << t.transform_expr(t.a.child(&node, i))
2414 }
2415 start := t.a.children.len
2416 for child in new_children {
2417 t.a.children << child
2418 }
2419 lowered := t.a.add_node(flat.Node{
2420 kind: .selector
2421 op: node.op
2422 children_start: start
2423 children_count: flat.child_count(new_children.len)
2424 pos: node.pos
2425 value: node.value
2426 typ: node.typ
2427 })
2428 return lowered, guard_source, guard_body, guard_mode
2429}
2430
2431fn (mut t Transformer) lower_optional_selector_lvalue_from_or(id flat.NodeId, node flat.Node, base flat.Node) ?(flat.NodeId, flat.NodeId, flat.NodeId, string) {
2432 source_id := t.a.child(&base, 0)
2433 if !t.optional_selector_lvalue_source(source_id) {
2434 return none
2435 }
2436 expr_type, value_type := t.or_expr_types(source_id, base.typ)
2437 if !t.is_optional_type_name(expr_type) || value_type.len == 0 || value_type == 'void' {
2438 return none
2439 }
2440 source := t.transform_lvalue(source_id)
2441 value_base := t.make_selector(source, 'value', value_type)
2442 mut new_children := []flat.NodeId{cap: int(node.children_count)}
2443 new_children << value_base
2444 for i in 1 .. node.children_count {
2445 new_children << t.transform_expr(t.a.child(&node, i))
2446 }
2447 start := t.a.children.len
2448 for child in new_children {
2449 t.a.children << child
2450 }
2451 lhs_type := t.lvalue_type(id)
2452 lowered := t.a.add_node(flat.Node{
2453 kind: .selector
2454 op: if value_type.starts_with('&') { flat.Op.arrow } else { node.op }
2455 children_start: start
2456 children_count: flat.child_count(new_children.len)
2457 pos: node.pos
2458 value: node.value
2459 typ: if lhs_type.len > 0 { lhs_type } else { node.typ }
2460 })
2461 return lowered, source, t.a.child(&base, 1), base.value
2462}
2463
2464fn (t &Transformer) optional_selector_lvalue_source(id flat.NodeId) bool {
2465 if int(id) < 0 {
2466 return false
2467 }
2468 node := t.a.nodes[int(id)]
2469 match node.kind {
2470 .ident {
2471 return node.value.len > 0
2472 }
2473 .paren {
2474 if node.children_count == 0 {
2475 return false
2476 }
2477 return t.optional_selector_lvalue_source(t.a.child(&node, 0))
2478 }
2479 .selector {
2480 if node.children_count == 0 || node.value.len == 0 {
2481 return false
2482 }
2483 return t.optional_selector_lvalue_source(t.a.child(&node, 0))
2484 }
2485 else {
2486 return false
2487 }
2488 }
2489}
2490
2491fn (mut t Transformer) try_lower_struct_compound_assign(node flat.Node) ?[]flat.NodeId {
2492 if node.kind != .assign || node.children_count != 2 {
2493 return none
2494 }
2495 op_name := compound_assign_struct_operator_symbol(node.op) or { return none }
2496 lhs_id := t.a.child(&node, 0)
2497 rhs_id := t.a.child(&node, 1)
2498 lhs := t.a.nodes[int(lhs_id)]
2499 if lhs.kind != .ident || lhs.value.len == 0 {
2500 return none
2501 }
2502 mut lhs_type := t.var_type(lhs.value)
2503 if lhs_type.len == 0 {
2504 lhs_type = t.original_expr_type(lhs_id)
2505 }
2506 if lhs_type.starts_with('&') {
2507 return none
2508 }
2509 mut operator_type := t.struct_lookup_name(lhs_type)
2510 if operator_type.len == 0 {
2511 if _ := t.struct_operator_fn_name(lhs_type, op_name) {
2512 operator_type = lhs_type
2513 }
2514 }
2515 if operator_type.len == 0 {
2516 return none
2517 }
2518 method_name := t.struct_operator_fn_name(operator_type, op_name) or { return none }
2519 rhs := t.transform_expr_for_type(rhs_id, lhs_type)
2520 t.mark_fn_used_name(method_name)
2521 call := t.make_call_typed(method_name, arr2(t.make_ident(lhs.value), rhs), lhs_type)
2522 return arr1(t.make_assign(t.make_ident(lhs.value), call))
2523}
2524
2525fn compound_assign_struct_operator_symbol(op flat.Op) ?string {
2526 match op {
2527 .plus_assign { return '+' }
2528 .minus_assign { return '-' }
2529 .mul_assign { return '*' }
2530 .div_assign { return '/' }
2531 .mod_assign { return '%' }
2532 else {}
2533 }
2534
2535 return none
2536}
2537
2538// try_lower_sum_shared_field_assign
2539// supports helper handling in transform.
2540fn (mut t Transformer) try_lower_sum_shared_field_assign(node flat.Node) ?[]flat.NodeId {
2541 if node.kind !in [.assign, .selector_assign] || node.children_count != 2 {
2542 return none
2543 }
2544 lhs_id := t.a.child(&node, 0)
2545 rhs_id := t.a.child(&node, 1)
2546 if int(lhs_id) < 0 || int(rhs_id) < 0 {
2547 return none
2548 }
2549 lhs := t.a.nodes[int(lhs_id)]
2550 if lhs.kind != .selector || lhs.children_count == 0 || lhs.value.len == 0 {
2551 return none
2552 }
2553 base_id := t.a.child(&lhs, 0)
2554 mut base_type := t.node_type(base_id)
2555 if base_type.len == 0 {
2556 base_type = t.original_expr_type(base_id)
2557 }
2558 field_type := t.sum_shared_field_type_name(base_type, lhs.value) or { return none }
2559 mut base := t.transform_lvalue(base_id)
2560 mut sum_type := base_type
2561 if !t.is_stable_expr_for_reuse(base) {
2562 clean_sum := t.trim_pointer_type(sum_type)
2563 ptr_type := if sum_type.starts_with('&') { sum_type } else { '&${clean_sum}' }
2564 addr := if sum_type.starts_with('&') {
2565 base
2566 } else {
2567 mut addr_expr := t.make_prefix(.amp, base)
2568 t.a.nodes[int(addr_expr)].typ = ptr_type
2569 addr_expr
2570 }
2571 tmp_name := t.new_temp('sum_lhs')
2572 t.pending_stmts << t.make_decl_assign_typed(tmp_name, addr, ptr_type)
2573 base = t.make_ident(tmp_name)
2574 sum_type = ptr_type
2575 }
2576 mut rhs := if node.op == .assign {
2577 t.transform_expr_for_type(rhs_id, field_type)
2578 } else {
2579 t.transform_expr(rhs_id)
2580 }
2581 mut rhs_type := t.node_type(rhs)
2582 if rhs_type.len == 0 {
2583 rhs_type = t.node_type(rhs_id)
2584 }
2585 if rhs_type.len == 0 {
2586 rhs_type = field_type
2587 }
2588 rhs = t.stable_transformed_expr_for_reuse(rhs, rhs_type, 'sum_assign')
2589 resolved_sum := t.resolve_sum_name(t.trim_pointer_type(sum_type))
2590 variants := t.sum_types[resolved_sum] or { return none }
2591 stmt := t.build_sum_shared_field_assign_chain(base, sum_type, resolved_sum, variants,
2592 lhs.value, field_type, rhs, node.op, 0)
2593 return t.with_pending_before(stmt)
2594}
2595
2596// build_sum_shared_field_assign_chain supports build_sum_shared_field_assign_chain handling.
2597fn (mut t Transformer) build_sum_shared_field_assign_chain(base flat.NodeId, sum_type string, resolved_sum string, variants []string, field string, field_type string, rhs flat.NodeId, op flat.Op, idx int) flat.NodeId {
2598 if idx >= variants.len {
2599 return t.make_empty()
2600 }
2601 variant := variants[idx]
2602 tag := t.make_selector_op(base, 'typ', 'int', if sum_type.starts_with('&') {
2603 .arrow
2604 } else {
2605 .dot
2606 })
2607 cond := t.make_infix(.eq, tag, t.make_int_literal(t.sum_type_index(resolved_sum, variant)))
2608 qv := t.resolve_variant(resolved_sum, variant)
2609 sum_field := t.sum_field_name(qv)
2610 use_ptr := t.variant_references_sum(qv, resolved_sum)
2611 variant_base := t.make_selector_op(base, sum_field, if use_ptr { '&${qv}' } else { qv }, if sum_type.starts_with('&') {
2612 .arrow
2613 } else {
2614 .dot
2615 })
2616 mut then_stmt := t.make_empty()
2617 if nested_field_type := t.sum_shared_field_type_name(qv, field) {
2618 nested_sum := t.resolve_sum_name(qv)
2619 if nested_variants := t.sum_types[nested_sum] {
2620 then_stmt = t.build_sum_shared_field_assign_chain(variant_base, qv, nested_sum,
2621 nested_variants, field, nested_field_type, rhs, op, 0)
2622 }
2623 } else {
2624 field_lhs := t.make_selector_op(variant_base, field, field_type, if use_ptr {
2625 .arrow
2626 } else {
2627 .dot
2628 })
2629 then_stmt = t.make_assign_op(field_lhs, rhs, op)
2630 }
2631 then_block := t.make_block(arr1(then_stmt))
2632 else_stmt := t.build_sum_shared_field_assign_chain(base, sum_type, resolved_sum, variants,
2633 field, field_type, rhs, op, idx + 1)
2634 return t.make_if(cond, then_block, else_stmt)
2635}
2636
2637// assignment_sum_target supports assignment sum target handling for Transformer.
2638fn (t &Transformer) assignment_sum_target(lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type string) string {
2639 if lhs_type.starts_with('&') {
2640 return ''
2641 }
2642 if lhs_type.starts_with('[]') || t.is_fixed_array_type(lhs_type) {
2643 return ''
2644 }
2645 if t.is_sum_type_name(lhs_type) {
2646 return lhs_type
2647 }
2648 if int(lhs_id) < 0 || int(rhs_id) < 0 {
2649 return ''
2650 }
2651 lhs := t.a.nodes[int(lhs_id)]
2652 if lhs.kind != .selector || lhs.value.len == 0 {
2653 return ''
2654 }
2655 if lhs.value == 'obj' {
2656 sum_name := t.resolve_sum_name('ScopeObject')
2657 if t.is_sum_type_name(sum_name) {
2658 return sum_name
2659 }
2660 }
2661 rhs := t.a.nodes[int(rhs_id)]
2662 if inferred_sum := t.sum_type_for_field_variant(lhs.value, rhs_id, rhs) {
2663 return inferred_sum
2664 }
2665 if lhs.value == 'info' {
2666 if type_info_sum := t.type_info_sum_name() {
2667 return type_info_sum
2668 }
2669 }
2670 return ''
2671}
2672
2673// type_info_sum_name returns type info sum name data for Transformer.
2674fn (t &Transformer) type_info_sum_name() ?string {
2675 for sum_name, _ in t.sum_types {
2676 if sum_name == 'TypeInfo' || sum_name.ends_with('.TypeInfo') {
2677 return sum_name
2678 }
2679 }
2680 return none
2681}
2682
2683// try_lower_pointer_value_assign supports try lower pointer value assign handling for Transformer.
2684fn (mut t Transformer) try_lower_pointer_value_assign(node flat.Node) ?[]flat.NodeId {
2685 if node.kind != .assign || node.children_count != 2 {
2686 return none
2687 }
2688 lhs_id := t.a.child(&node, 0)
2689 lhs := t.a.nodes[int(lhs_id)]
2690 if lhs.kind != .ident || lhs.value.len == 0 {
2691 return none
2692 }
2693 mut lhs_type := t.var_type(lhs.value)
2694 if lhs_type.len == 0 {
2695 lhs_type = t.node_type(lhs_id)
2696 }
2697 if !lhs_type.starts_with('&') {
2698 return none
2699 }
2700 rhs_id := t.a.child(&node, 1)
2701 rhs_type := t.node_type(rhs_id)
2702 lhs_value_type := t.normalize_type_alias(lhs_type[1..])
2703 if node.op != .assign {
2704 if !t.pointer_value_lvalues[lhs.value] {
2705 return none
2706 }
2707 new_lhs := t.make_prefix(.mul, t.make_ident(lhs.value))
2708 return arr1(t.make_assign_op(new_lhs, t.transform_expr(rhs_id), node.op))
2709 }
2710 if rhs_type.len == 0
2711 || (rhs_type != lhs_value_type && !t.type_alias_targets_type(lhs_type[1..], rhs_type)) {
2712 return none
2713 }
2714 new_lhs := t.make_prefix(.mul, t.make_ident(lhs.value))
2715 return arr1(t.make_assign(new_lhs, t.transform_expr(rhs_id)))
2716}
2717
2718// transform_expr_for_type transforms transform expr for type data for transform.
2719fn (mut t Transformer) transform_expr_for_type(id flat.NodeId, target_type string) flat.NodeId {
2720 if int(id) >= 0 && target_type.len > 0 {
2721 node := t.a.nodes[int(id)]
2722 if node.kind == .none_expr && t.is_ierror_type(target_type) {
2723 return t.make_struct_init('IError')
2724 }
2725 if target_type.starts_with('&') {
2726 if expr := t.transform_amp_struct_init_for_type(id, node, target_type) {
2727 return expr
2728 }
2729 }
2730 if expr := t.transform_interface_value_for_type(id, target_type) {
2731 return expr
2732 }
2733 if node.kind == .block {
2734 if lowered := t.transform_block_expr_for_type(id, node, target_type) {
2735 return lowered
2736 }
2737 }
2738 if node.kind == .if_expr {
2739 if lowered := t.try_expand_if_expr_value_for_type(id, node, target_type) {
2740 return lowered
2741 }
2742 }
2743 if node.kind == .match_stmt {
2744 if lowered := t.transform_match_expr_for_type(id, node, target_type) {
2745 return lowered
2746 }
2747 }
2748 if node.kind == .or_expr && !t.is_optional_type_name(target_type) {
2749 old_typ := node.typ
2750 t.a.nodes[int(id)].typ = target_type
2751 expr := t.transform_expr(id)
2752 t.a.nodes[int(id)].typ = old_typ
2753 return t.coerce_transformed_expr_to_type(expr, id, target_type)
2754 }
2755 if node.kind == .array_literal {
2756 if lowered := t.transform_fixed_array_literal_for_type(id, node, target_type) {
2757 return lowered
2758 }
2759 if lowered := t.transform_array_literal_for_type(id, node, target_type) {
2760 return lowered
2761 }
2762 }
2763 if node.kind == .postfix && node.op == .not && node.children_count == 1 {
2764 child_id := t.a.child(&node, 0)
2765 child := t.a.nodes[int(child_id)]
2766 if child.kind == .array_literal {
2767 if lowered := t.transform_fixed_array_literal_for_type(child_id, child, target_type) {
2768 return lowered
2769 }
2770 }
2771 }
2772 if node.kind == .array_init {
2773 if lowered := t.transform_empty_array_init_for_type(node, target_type) {
2774 return lowered
2775 }
2776 }
2777 if node.kind == .map_init {
2778 clean_target := t.clean_map_type(target_type)
2779 if clean_target.starts_with('map[') {
2780 mut map_node := node
2781 map_node.value = clean_target
2782 map_node.typ = clean_target
2783 return t.lower_map_init_to_runtime(id, map_node)
2784 }
2785 }
2786 if target_type in ['f32', 'f64'] && node.kind == .infix
2787 && node.op in [.plus, .minus, .mul, .div] && node.children_count >= 2 {
2788 lhs := t.transform_expr_for_type(t.a.child(&node, 0), target_type)
2789 rhs := t.transform_expr_for_type(t.a.child(&node, 1), target_type)
2790 start := t.a.children.len
2791 t.a.children << lhs
2792 t.a.children << rhs
2793 return t.a.add_node(flat.Node{
2794 kind: .infix
2795 op: node.op
2796 children_start: start
2797 children_count: 2
2798 pos: node.pos
2799 value: node.value
2800 typ: target_type
2801 })
2802 }
2803 }
2804 expr := t.transform_expr(id)
2805 return t.coerce_transformed_expr_to_type(expr, id, target_type)
2806}
2807
2808// transform_block_expr_for_type transforms transform block expr for type data for transform.
2809fn (mut t Transformer) transform_block_expr_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
2810 if node.kind != .block || node.children_count == 0 || target_type.len == 0 {
2811 return none
2812 }
2813 last_id := t.a.child(&node, node.children_count - 1)
2814 last := t.a.nodes[int(last_id)]
2815 tail_expr_id := if last.kind == .expr_stmt && last.children_count > 0 {
2816 t.a.child(&last, 0)
2817 } else if last.kind == .block && t.stmt_value_type(last_id).len > 0 {
2818 last_id
2819 } else if !t.is_stmt_kind(last.kind) {
2820 last_id
2821 } else {
2822 return none
2823 }
2824 mut prefix := []flat.NodeId{cap: int(node.children_count - 1)}
2825 for i in 0 .. node.children_count - 1 {
2826 prefix << t.a.child(&node, i)
2827 }
2828 mut new_children := t.transform_stmts(prefix)
2829 tail_expr := t.transform_expr_for_type(tail_expr_id, target_type)
2830 tail_stmt := t.make_expr_stmt(tail_expr)
2831 for stmt in t.with_pending_before(tail_stmt) {
2832 new_children << stmt
2833 }
2834 new_block := t.make_block(new_children)
2835 block_typ := t.stmt_value_type(new_block)
2836 t.a.nodes[int(new_block)].typ = if block_typ.len > 0 { block_typ } else { node.typ }
2837 return new_block
2838}
2839
2840// transform_match_expr_for_type transforms transform match expr for type data for transform.
2841fn (mut t Transformer) transform_match_expr_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
2842 if target_type.len == 0 || node.kind != .match_stmt {
2843 return none
2844 }
2845 mut actual_result_type := t.match_expr_type(node)
2846 if actual_result_type.len == 0 || actual_result_type == 'void' {
2847 actual_result_type = target_type
2848 }
2849 tmp_name := t.new_temp('match_val')
2850 outer_pending := t.pending_stmts.clone()
2851 t.pending_stmts.clear()
2852
2853 mut prelude := []flat.NodeId{}
2854 prelude << t.make_decl_assign_typed(tmp_name, t.zero_value_for_type(actual_result_type),
2855 actual_result_type)
2856 for stmt in t.build_match_value_stmts(node, tmp_name, actual_result_type) {
2857 prelude << stmt
2858 }
2859
2860 t.pending_stmts = outer_pending
2861 for stmt in prelude {
2862 t.pending_stmts << stmt
2863 }
2864 tmp := t.make_ident(tmp_name)
2865 t.a.nodes[int(tmp)].typ = actual_result_type
2866 return tmp
2867}
2868
2869// transform_amp_struct_init_for_type supports transform_amp_struct_init_for_type handling.
2870fn (mut t Transformer) transform_amp_struct_init_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
2871 if node.kind != .prefix || node.op != .amp || node.children_count != 1 {
2872 return none
2873 }
2874 child_id := t.a.child(&node, 0)
2875 child := t.a.nodes[int(child_id)]
2876 if child.kind != .struct_init {
2877 return none
2878 }
2879 new_child := t.transform_struct_init(child_id, child)
2880 start := t.a.children.len
2881 t.a.children << new_child
2882 return t.a.add_node(flat.Node{
2883 kind: .prefix
2884 op: node.op
2885 children_start: start
2886 children_count: 1
2887 pos: node.pos
2888 value: node.value
2889 typ: if target_type.len > 0 { target_type } else { node.typ }
2890 })
2891}
2892
2893// coerce_transformed_expr_to_type converts coerce transformed expr to type data for transform.
2894fn (mut t Transformer) coerce_transformed_expr_to_type(expr flat.NodeId, source_id flat.NodeId, target_type string) flat.NodeId {
2895 mut target := t.normalize_type_alias(target_type)
2896 if target.len == 0 || int(expr) < 0 {
2897 return expr
2898 }
2899 mut expr_type := t.node_type(expr)
2900 if expr_type.len == 0 {
2901 expr_type = t.node_type(source_id)
2902 }
2903 if expr_type.len == 0 {
2904 expr_type = t.resolve_expr_type(source_id)
2905 }
2906 expr_type = t.normalize_type_alias(expr_type)
2907 mut optional_target := if t.is_optional_type_name(target_type) {
2908 t.qualify_optional_type(target_type)
2909 } else {
2910 target
2911 }
2912 optional_target = t.infer_typed_optional_target(optional_target, expr_type)
2913 if t.is_optional_type_name(optional_target) && !t.is_optional_type_name(expr_type) {
2914 source := if int(source_id) >= 0 { t.a.nodes[int(source_id)] } else { flat.Node{} }
2915 if source.kind != .none_expr {
2916 return t.make_optional_some(expr, optional_target)
2917 }
2918 }
2919 if expr_type.len == 0 || expr_type == target {
2920 return expr
2921 }
2922 if target in ['f32', 'f64'] && t.is_integer_type_name(expr_type) {
2923 return t.make_cast(target, expr, target)
2924 }
2925 if target.starts_with('&') {
2926 if t.expr_is_nil_like(source_id) {
2927 t.a.nodes[int(expr)].typ = target
2928 return expr
2929 }
2930 target_value_type := t.normalize_type_alias(target[1..])
2931 expr_value_type := if expr_type.starts_with('&') {
2932 t.normalize_type_alias(expr_type[1..])
2933 } else {
2934 expr_type
2935 }
2936 if t.is_sum_type_name(target_value_type)
2937 && t.find_sum_type_for_variant(t.trim_pointer_type(expr_type)).len > 0 {
2938 if t.resolve_sum_name(t.trim_pointer_type(expr_type)) == t.resolve_sum_name(target_value_type) {
2939 if expr_type.starts_with('&') {
2940 return expr
2941 }
2942 if t.expr_can_take_address(expr) {
2943 addr := t.make_prefix(.amp, expr)
2944 t.a.nodes[int(addr)].typ = target
2945 return addr
2946 }
2947 tmp_name := t.new_temp('sum_ref')
2948 t.pending_stmts << t.make_decl_assign_typed(tmp_name, expr, target_value_type)
2949 addr := t.make_prefix(.amp, t.make_ident(tmp_name))
2950 t.a.nodes[int(addr)].typ = target
2951 return addr
2952 }
2953 source := t.a.nodes[int(source_id)]
2954 wrap_source_id := if source.kind == .prefix && source.op == .amp
2955 && source.children_count > 0 {
2956 t.a.child(&source, 0)
2957 } else {
2958 source_id
2959 }
2960 wrapped := t.wrap_sum_value(wrap_source_id, target_value_type)
2961 tmp_name := t.new_temp('sum_ref')
2962 t.pending_stmts << t.make_decl_assign_typed(tmp_name, wrapped, target_value_type)
2963 addr := t.make_prefix(.amp, t.make_ident(tmp_name))
2964 t.a.nodes[int(addr)].typ = target
2965 return addr
2966 }
2967 if expr_value_type == target_value_type
2968 || t.type_alias_targets_type(target[1..], expr_value_type) {
2969 if expr_type.starts_with('&') {
2970 return expr
2971 }
2972 if t.expr_can_take_address(expr) {
2973 addr := t.make_prefix(.amp, expr)
2974 t.a.nodes[int(addr)].typ = target
2975 return addr
2976 }
2977 tmp_name := t.new_temp('addr')
2978 t.pending_stmts << t.make_decl_assign_typed(tmp_name, expr, expr_value_type)
2979 addr := t.make_prefix(.amp, t.make_ident(tmp_name))
2980 t.a.nodes[int(addr)].typ = target
2981 return addr
2982 }
2983 return expr
2984 }
2985 if expr_type.starts_with('&') {
2986 expr_value_type := t.normalize_type_alias(expr_type[1..])
2987 if expr_value_type == target || t.type_alias_targets_type(expr_type[1..], target) {
2988 deref := t.make_prefix(.mul, expr)
2989 t.a.nodes[int(deref)].typ = target
2990 return deref
2991 }
2992 }
2993 return expr
2994}
2995
2996// is_ierror_type reports whether is ierror type applies in transform.
2997fn (t &Transformer) is_ierror_type(name string) bool {
2998 clean := t.trim_pointer_type(t.normalize_type_alias(name))
2999 return clean == 'IError' || clean.ends_with('.IError')
3000}
3001
3002// expr_is_nil_like supports expr is nil like handling for Transformer.
3003fn (t &Transformer) expr_is_nil_like(id flat.NodeId) bool {
3004 if int(id) < 0 {
3005 return false
3006 }
3007 node := t.a.nodes[int(id)]
3008 if node.kind == .nil_literal {
3009 return true
3010 }
3011 if node.kind != .block || node.children_count == 0 {
3012 return false
3013 }
3014 last_id := t.a.child(&node, node.children_count - 1)
3015 last := t.a.nodes[int(last_id)]
3016 if last.kind == .expr_stmt && last.children_count > 0 {
3017 return t.expr_is_nil_like(t.a.child(&last, 0))
3018 }
3019 return t.expr_is_nil_like(last_id)
3020}
3021
3022// infer_typed_optional_target resolves infer typed optional target information for transform.
3023fn (t &Transformer) infer_typed_optional_target(optional_target string, expr_type string) string {
3024 if expr_type.len == 0 {
3025 return optional_target
3026 }
3027 mut value_type := expr_type
3028 if !value_type.contains('.') {
3029 qualified := t.qualify_type(value_type)
3030 if qualified != value_type {
3031 value_type = qualified
3032 }
3033 }
3034 if !isnil(t.tc) {
3035 parsed := t.tc.parse_type(value_type)
3036 parsed_name := parsed.name()
3037 if parsed_name.len > 0 && parsed_name != 'unknown' {
3038 value_type = parsed_name
3039 }
3040 }
3041 if t.is_optional_type_name(optional_target) {
3042 base := t.optional_base_type(optional_target)
3043 if value_type.contains('.') && base == value_type.all_after_last('.') {
3044 return '?${value_type}'
3045 }
3046 return optional_target
3047 }
3048 if optional_target != 'Optional' || isnil(t.tc) {
3049 return optional_target
3050 }
3051 typ := t.tc.parse_type(value_type)
3052 if typ is types.Primitive || typ is types.Enum || typ is types.Void {
3053 return optional_target
3054 }
3055 return '?${value_type}'
3056}
3057
3058// make_optional_some builds make optional some data for transform.
3059fn (mut t Transformer) make_optional_some(value flat.NodeId, optional_type string) flat.NodeId {
3060 ok_field := t.make_sum_literal_field('ok', t.make_bool_literal(true), 'bool')
3061 base_type := t.optional_base_type(optional_type)
3062 mut fields := []flat.NodeId{cap: 2}
3063 fields << ok_field
3064 if base_type.len > 0 && base_type != 'void' {
3065 fields << t.make_sum_literal_field('value', value, base_type)
3066 }
3067 start := t.a.children.len
3068 for field in fields {
3069 t.a.children << field
3070 }
3071 return t.a.add_node(flat.Node{
3072 kind: .struct_init
3073 children_start: start
3074 children_count: flat.child_count(fields.len)
3075 value: optional_type
3076 typ: optional_type
3077 })
3078}
3079
3080// make_optional_none builds make optional none data for transform.
3081fn (mut t Transformer) make_optional_none(optional_type string) flat.NodeId {
3082 ok_field := t.make_sum_literal_field('ok', t.make_bool_literal(false), 'bool')
3083 start := t.a.children.len
3084 t.a.children << ok_field
3085 return t.a.add_node(flat.Node{
3086 kind: .struct_init
3087 children_start: start
3088 children_count: 1
3089 value: optional_type
3090 typ: optional_type
3091 })
3092}
3093
3094// make_optional_none_with_err builds make optional none with err data for transform.
3095fn (mut t Transformer) make_optional_none_with_err(optional_type string, err_expr flat.NodeId) flat.NodeId {
3096 ok_field := t.make_sum_literal_field('ok', t.make_bool_literal(false), 'bool')
3097 err_field := t.make_sum_literal_field('err', err_expr, 'IError')
3098 start := t.a.children.len
3099 t.a.children << ok_field
3100 t.a.children << err_field
3101 return t.a.add_node(flat.Node{
3102 kind: .struct_init
3103 children_start: start
3104 children_count: 2
3105 value: optional_type
3106 typ: optional_type
3107 })
3108}
3109
3110// expr_can_take_address supports expr can take address handling for Transformer.
3111fn (t &Transformer) expr_can_take_address(id flat.NodeId) bool {
3112 if int(id) < 0 {
3113 return false
3114 }
3115 node := t.a.nodes[int(id)]
3116 match node.kind {
3117 .ident {
3118 return true
3119 }
3120 .index {
3121 // `a[lo..hi]` (an index node tagged `range`) yields a fresh array value, not an
3122 // addressable element, so its address can't be taken in place — runtime_addr
3123 // must materialize it to a temp first. Only plain element indexing `a[i]` is an
3124 // addressable lvalue.
3125 if node.value == 'range' {
3126 return false
3127 }
3128 return true
3129 }
3130 .selector {
3131 if node.children_count == 0 {
3132 return false
3133 }
3134 if t.selector_chain_has_sum_variant_field(id) {
3135 return false
3136 }
3137 return t.expr_can_take_address(t.a.child(&node, 0))
3138 }
3139 .prefix {
3140 return node.op == .mul
3141 }
3142 .paren {
3143 if node.children_count == 0 {
3144 return false
3145 }
3146 return t.expr_can_take_address(t.a.child(&node, 0))
3147 }
3148 else {
3149 return false
3150 }
3151 }
3152}
3153
3154// type_alias_targets_type returns type alias targets type data for Transformer.
3155fn (t &Transformer) type_alias_targets_type(alias_name string, target_type string) bool {
3156 if alias_name.len == 0 || target_type.len == 0 || isnil(t.tc) {
3157 return false
3158 }
3159 for name, target in t.tc.type_aliases {
3160 if name == alias_name || name.all_after_last('.') == alias_name {
3161 if t.normalize_type_alias(target) == target_type {
3162 return true
3163 }
3164 }
3165 }
3166 return false
3167}
3168
3169// try_lower_string_compound_assign
3170// supports helper handling in transform.
3171fn (mut t Transformer) try_lower_string_compound_assign(_id flat.NodeId, node flat.Node) ?[]flat.NodeId {
3172 if node.kind != .assign || node.op != .plus_assign || node.children_count != 2 {
3173 return none
3174 }
3175 lhs_id := t.a.child(&node, 0)
3176 lhs := t.a.nodes[int(lhs_id)]
3177 if lhs.kind != .ident {
3178 return none
3179 }
3180 rhs_id := t.a.child(&node, 1)
3181 rhs := t.a.nodes[int(rhs_id)]
3182 is_string := t.resolve_expr_type(lhs_id) == 'string' || rhs.kind == .string_literal
3183 || rhs.kind == .string_interp || t.resolve_expr_type(rhs_id) == 'string'
3184 if !is_string {
3185 return none
3186 }
3187 new_rhs := t.transform_expr(rhs_id)
3188 lhs_copy := t.make_ident(lhs.value)
3189 concat := t.make_call('string__plus', arr2(lhs_copy, new_rhs))
3190 new_lhs := t.make_ident(lhs.value)
3191 return arr1(t.make_assign(new_lhs, concat))
3192}
3193
3194// transform_decl_assign_stmt transforms transform decl assign stmt data for transform.
3195fn (mut t Transformer) transform_decl_assign_stmt(id flat.NodeId, node flat.Node) []flat.NodeId {
3196 if node.children_count == 0 {
3197 return arr1(id)
3198 }
3199 mut has_empty_child := false
3200 for i in 0 .. node.children_count {
3201 if int(t.a.child(&node, i)) < 0 {
3202 has_empty_child = true
3203 }
3204 }
3205 if has_empty_child {
3206 mut parts := []string{}
3207 for i in 0 .. node.children_count {
3208 child_id := t.a.child(&node, i)
3209 if int(child_id) < 0 {
3210 parts << '${i}:empty'
3211 } else {
3212 child := t.a.nodes[int(child_id)]
3213 parts << '${i}:${child.kind}:${child.value}:${child.typ}'
3214 }
3215 }
3216 panic('internal error: empty decl_assign child in ${t.cur_fn_name}: count=${node.children_count} typ=${node.typ} value=${node.value} children=${parts.join('|')}')
3217 }
3218 mut inferred_typ := ''
3219 if node.children_count > 2 && !isnil(t.tc) {
3220 rhs_id := t.a.child(&node, 1)
3221 if rhs_types := t.multi_return_types_for_expr(rhs_id, node.children_count - 1) {
3222 for j, field_type in rhs_types {
3223 lhs_idx := if j == 0 { 0 } else { j + 1 }
3224 if lhs_idx >= node.children_count {
3225 continue
3226 }
3227 lhs := t.a.child_node(&node, lhs_idx)
3228 if lhs.kind == .ident && lhs.value.len > 0 && lhs.value != '_' {
3229 t.set_var_type(lhs.value, t.normalize_type_alias(field_type.name()))
3230 }
3231 }
3232 }
3233 }
3234 if expanded := t.try_expand_multi_return_decl(node) {
3235 return expanded
3236 }
3237 if expanded := t.try_expand_plain_multi_decl(node) {
3238 return expanded
3239 }
3240 // Track the variable type for the common 2-child case.
3241 if node.children_count == 2 {
3242 lhs := t.a.child_node(&node, 0)
3243 if lhs.kind == .ident && lhs.value.len > 0 {
3244 mut typ := t.infer_decl_type(node)
3245 rhs_id := t.a.child(&node, 1)
3246 rhs := t.a.nodes[int(rhs_id)]
3247 if rhs.kind == .call {
3248 call_typ := t.node_type(rhs_id)
3249 if decl_type_is_usable(call_typ) {
3250 typ = call_typ
3251 } else if !decl_type_is_usable(typ) {
3252 raw_call_typ := t.get_call_return_type(rhs_id, rhs)
3253 if raw_call_typ.len > 0 {
3254 typ = raw_call_typ
3255 }
3256 }
3257 generic_typ := t.concrete_generic_call_return_type(rhs_id, rhs)
3258 if generic_typ.len > 0 {
3259 typ = generic_typ
3260 }
3261 }
3262 if rhs.kind == .call && t.is_strings_builder_new_call(rhs_id, rhs) {
3263 typ = 'strings.Builder'
3264 } else if rhs.kind == .if_expr {
3265 if_typ := t.if_expr_result_type(rhs_id, rhs)
3266 if if_typ.len > 0 {
3267 typ = if_typ
3268 }
3269 } else if rhs.kind == .match_stmt {
3270 match_typ := t.match_expr_type(rhs)
3271 if match_typ.len > 0 {
3272 typ = match_typ
3273 }
3274 } else if rhs.kind == .block {
3275 block_typ := t.stmt_value_type(rhs_id)
3276 if block_typ.len > 0 {
3277 typ = block_typ
3278 }
3279 } else if rhs.kind == .or_expr && rhs.children_count > 0 {
3280 or_source_id := t.a.child(&rhs, 0)
3281 if info := t.map_index_info(or_source_id) {
3282 typ = info.value_type
3283 } else if info := t.array_index_info(or_source_id) {
3284 typ = info.value_type
3285 } else {
3286 or_body_id := if rhs.children_count > 1 {
3287 t.a.child(&rhs, 1)
3288 } else {
3289 flat.empty_node
3290 }
3291 fallback_type := if typ.len > 0 { typ } else { t.stmt_value_type(or_body_id) }
3292 expr_type, value_type := t.or_expr_types(or_source_id, fallback_type)
3293 if t.is_optional_type_name(expr_type) && value_type.len > 0
3294 && value_type != 'void' {
3295 typ = value_type
3296 }
3297 }
3298 }
3299 if node.typ.len == 0 {
3300 if rhs.kind == .array_literal && t.is_fixed_array_type(typ) {
3301 typ = '[]${fixed_array_elem_type(typ)}'
3302 t.a.nodes[int(rhs_id)].typ = typ
3303 }
3304 }
3305 if typ.len > 0 {
3306 t.set_var_type(lhs.value, typ)
3307 inferred_typ = typ
3308 }
3309 }
3310 }
3311 // A value local whose address escapes (`p := &v` with `p` returned) is moved to the heap
3312 // at its own declaration so writes after the alias are visible to the caller. Must run
3313 // before the `p := &v` alias is transformed (the source is declared first).
3314 if node.children_count == 2 {
3315 src := t.a.child_node(&node, 0)
3316 if src.kind == .ident && src.value in t.escaping_amp_sources
3317 && src.value !in t.heaped_amp_locals && t.heapable_value_type(inferred_typ) {
3318 return t.heap_escaping_source_decl(node, src.value, inferred_typ)
3319 }
3320 }
3321 mut new_children := []flat.NodeId{cap: int(node.children_count)}
3322 for i in 0 .. node.children_count {
3323 child_id := t.a.child(&node, i)
3324 if i == 0 || (node.children_count > 2 && i > 1) {
3325 new_children << t.transform_lvalue(child_id)
3326 } else if node.children_count == 2 && t.try_heap_escaping_amp(node, child_id) {
3327 new_children << t.heap_escaping_amp_rhs(child_id)
3328 // When `v` was heap-moved it is already a `&T`, so `p := &v` is really `p := v`
3329 // (a `&T`), not `&&T` as the literal `&v` would infer. Adopt the source's pointer
3330 // type for `p` so its declaration and later uses are consistent.
3331 amp := t.a.nodes[int(child_id)]
3332 if amp.children_count > 0 {
3333 amp_src := t.a.nodes[int(t.a.child(&, 0))]
3334 if amp_src.kind == .ident && amp_src.value in t.heaped_amp_locals {
3335 inferred_typ = t.var_type(amp_src.value)
3336 t.set_var_type(t.a.nodes[int(t.a.child(&node, 0))].value, inferred_typ)
3337 }
3338 }
3339 } else {
3340 lhs_id := t.a.child(&node, 0)
3341 lhs_type := if inferred_typ.len > 0 {
3342 inferred_typ
3343 } else if node.typ.len > 0 {
3344 node.typ
3345 } else {
3346 t.lvalue_type(lhs_id)
3347 }
3348 sum_target := t.assignment_sum_target(lhs_id, child_id, lhs_type)
3349 if sum_target.len > 0 && !t.expr_has_smartcast(child_id) {
3350 new_children << t.wrap_sum_value(child_id, sum_target)
3351 } else {
3352 new_children << t.transform_expr_for_type(child_id, lhs_type)
3353 }
3354 }
3355 }
3356 if node.children_count == 2 && !decl_type_is_usable(node.typ) {
3357 lhs := t.a.nodes[int(new_children[0])]
3358 if lhs.kind == .ident && lhs.value.len > 0 {
3359 rhs_typ := t.node_type(new_children[1])
3360 if decl_type_is_usable(rhs_typ)
3361 && (inferred_typ.len == 0 || inferred_typ in ['array', 'map', 'unknown']) {
3362 t.set_var_type(lhs.value, rhs_typ)
3363 inferred_typ = rhs_typ
3364 }
3365 }
3366 }
3367 start := t.a.children.len
3368 for nc in new_children {
3369 t.a.children << nc
3370 }
3371 new_id := t.a.add_node(flat.Node{
3372 kind: .decl_assign
3373 op: node.op
3374 children_start: start
3375 children_count: node.children_count
3376 pos: node.pos
3377 value: node.value
3378 typ: if inferred_typ.len > 0 { inferred_typ } else { node.typ }
3379 })
3380 return t.with_pending_before(new_id)
3381}
3382
3383fn (mut t Transformer) try_expand_plain_multi_decl(node flat.Node) ?[]flat.NodeId {
3384 if node.kind != .decl_assign || node.children_count < 4 || node.children_count % 2 != 0 {
3385 return none
3386 }
3387 mut result := []flat.NodeId{}
3388 for i := 0; i < node.children_count; i += 2 {
3389 lhs_id := t.a.child(&node, i)
3390 rhs_id := t.a.child(&node, i + 1)
3391 lhs := t.a.nodes[int(lhs_id)]
3392 rhs_node := t.a.nodes[int(rhs_id)]
3393 generic_rhs_typ := if rhs_node.kind == .call {
3394 t.concrete_generic_call_return_type(rhs_id, rhs_node)
3395 } else {
3396 ''
3397 }
3398 rhs := t.transform_expr(rhs_id)
3399 t.drain_pending(mut result)
3400 if lhs.kind != .ident || lhs.value == '_' {
3401 continue
3402 }
3403 rhs_authority := t.decl_rhs_type(rhs_id)
3404 mut typ := if t.is_fn_pointer_type_name(rhs_authority) { rhs_authority } else { '' }
3405 if typ.len == 0 {
3406 typ = generic_rhs_typ
3407 }
3408 if typ.len == 0 {
3409 typ = t.node_type(rhs)
3410 }
3411 if typ.len == 0 {
3412 typ = t.node_type(rhs_id)
3413 }
3414 if typ.len == 0 {
3415 typ = rhs_authority
3416 }
3417 if typ.len == 0 && lhs.typ.len > 0 {
3418 typ = lhs.typ
3419 }
3420 if typ.len > 0 {
3421 typ = t.normalize_type_alias(typ)
3422 t.set_var_type(lhs.value, typ)
3423 result << t.make_decl_assign_typed(lhs.value, rhs, typ)
3424 } else {
3425 result << t.make_decl_assign(lhs.value, rhs)
3426 }
3427 }
3428 return result
3429}
3430
3431// expr_has_smartcast converts expr has smartcast data for transform.
3432fn (t &Transformer) expr_has_smartcast(id flat.NodeId) bool {
3433 key := t.expr_key(id)
3434 return t.has_smartcast(key)
3435}
3436
3437// try_expand_multi_return_decl supports try expand multi return decl handling for Transformer.
3438fn (mut t Transformer) try_expand_multi_return_decl(node flat.Node) ?[]flat.NodeId {
3439 if node.kind != .decl_assign || node.children_count < 3 || isnil(t.tc) {
3440 return none
3441 }
3442 rhs_id := t.a.child(&node, 1)
3443 rhs := t.a.nodes[int(rhs_id)]
3444 lhs_ids := t.multi_assign_lhs_ids(node)
3445 if rhs.kind == .if_expr {
3446 return t.expand_multi_return_if_decl(rhs_id, rhs, lhs_ids)
3447 }
3448 if rhs_types := t.multi_return_types_for_expr(rhs_id, lhs_ids.len) {
3449 tmp_name := t.new_temp('multi_ret')
3450 mut result := []flat.NodeId{}
3451 new_rhs := t.transform_expr(rhs_id)
3452 t.drain_pending(mut result)
3453 result << t.make_decl_assign_typed(tmp_name, new_rhs, t.multi_return_type_name(rhs_types))
3454 for j, field_type in rhs_types {
3455 if j >= lhs_ids.len {
3456 continue
3457 }
3458 lhs_id := lhs_ids[j]
3459 lhs := t.a.nodes[int(lhs_id)]
3460 if lhs.kind != .ident || lhs.value == '_' {
3461 continue
3462 }
3463 field_name := 'arg${j}'
3464 field_type_name := field_type.name()
3465 field := t.make_selector(t.make_ident(tmp_name), field_name, field_type_name)
3466 t.set_var_type(lhs.value, t.normalize_type_alias(field_type_name))
3467 result << t.make_decl_assign_typed(lhs.value, field, field_type_name)
3468 }
3469 return result
3470 }
3471 return none
3472}
3473
3474// try_expand_multi_return_assign supports try expand multi return assign handling for Transformer.
3475fn (mut t Transformer) try_expand_multi_return_assign(node flat.Node) ?[]flat.NodeId {
3476 if node.kind != .assign || node.children_count < 3 || isnil(t.tc) {
3477 return none
3478 }
3479 rhs_id := t.a.child(&node, 1)
3480 rhs := t.a.nodes[int(rhs_id)]
3481 lhs_ids := t.multi_assign_lhs_ids(node)
3482 if rhs.kind == .if_expr {
3483 return t.expand_multi_return_if_assign(rhs_id, rhs, lhs_ids)
3484 }
3485 if rhs_types := t.multi_return_types_for_expr(rhs_id, lhs_ids.len) {
3486 tmp_name := t.new_temp('multi_ret')
3487 mut result := []flat.NodeId{}
3488 new_rhs := t.transform_expr(rhs_id)
3489 t.drain_pending(mut result)
3490 result << t.make_decl_assign_typed(tmp_name, new_rhs, t.multi_return_type_name(rhs_types))
3491 for j, field_type in rhs_types {
3492 if j >= lhs_ids.len {
3493 continue
3494 }
3495 lhs_id := lhs_ids[j]
3496 lhs := t.a.nodes[int(lhs_id)]
3497 if lhs.kind == .ident && lhs.value == '_' {
3498 continue
3499 }
3500 field_name := 'arg${j}'
3501 field_type_name := field_type.name()
3502 field := t.make_selector(t.make_ident(tmp_name), field_name, field_type_name)
3503 result << t.make_assign(t.transform_lvalue(lhs_id), field)
3504 }
3505 return result
3506 }
3507 return none
3508}
3509
3510// try_expand_plain_multi_assign supports try expand plain multi assign handling for Transformer.
3511fn (mut t Transformer) try_expand_plain_multi_assign(node flat.Node) ?[]flat.NodeId {
3512 if node.kind != .assign || node.op != .assign || node.children_count < 4
3513 || node.children_count % 2 != 0 {
3514 return none
3515 }
3516 mut result := []flat.NodeId{}
3517 mut lhs_ids := []flat.NodeId{}
3518 mut tmp_names := []string{}
3519 for i := 0; i < node.children_count; i += 2 {
3520 lhs_id := t.a.child(&node, i)
3521 rhs_id := t.a.child(&node, i + 1)
3522 lhs_ids << lhs_id
3523 lhs_type := t.lvalue_type(lhs_id)
3524 rhs := if lhs_type.len > 0 {
3525 t.transform_expr_for_type(rhs_id, lhs_type)
3526 } else {
3527 t.transform_expr(rhs_id)
3528 }
3529 t.drain_pending(mut result)
3530 tmp_name := t.new_temp('assign')
3531 tmp_type := if lhs_type.len > 0 { lhs_type } else { t.node_type(rhs_id) }
3532 result << t.make_decl_assign_typed(tmp_name, rhs, tmp_type)
3533 tmp_names << tmp_name
3534 }
3535 for i, lhs_id in lhs_ids {
3536 lhs := t.a.nodes[int(lhs_id)]
3537 if lhs.kind == .ident && lhs.value == '_' {
3538 continue
3539 }
3540 result << t.make_assign(t.transform_lvalue(lhs_id), t.make_ident(tmp_names[i]))
3541 }
3542 return result
3543}
3544
3545// multi_assign_lhs_ids supports multi assign lhs ids handling for Transformer.
3546fn (t &Transformer) multi_assign_lhs_ids(node flat.Node) []flat.NodeId {
3547 mut lhs_ids := []flat.NodeId{}
3548 if node.children_count > 0 {
3549 lhs_ids << t.a.child(&node, 0)
3550 }
3551 for i in 2 .. node.children_count {
3552 lhs_ids << t.a.child(&node, i)
3553 }
3554 return lhs_ids
3555}
3556
3557// multi_return_types_for_expr supports multi return types for expr handling for Transformer.
3558fn (t &Transformer) multi_return_types_for_expr(id flat.NodeId, expected_count int) ?[]types.Type {
3559 if int(id) < 0 || isnil(t.tc) {
3560 return none
3561 }
3562 if typ := t.tc.expr_type(id) {
3563 if items := multi_return_types_from_type(typ, expected_count) {
3564 return items
3565 }
3566 }
3567 node := t.a.nodes[int(id)]
3568 if node.kind == .or_expr && node.children_count > 0 {
3569 return t.multi_return_types_for_expr(t.a.child(&node, 0), expected_count)
3570 }
3571 if node.kind == .match_stmt {
3572 return t.match_multi_return_types(node, expected_count)
3573 }
3574 if node.kind == .expr_stmt {
3575 return t.expr_stmt_multi_return_types(node, expected_count)
3576 }
3577 if node.kind == .block {
3578 return t.block_multi_return_types(node, expected_count)
3579 }
3580 mut typ_name := node.typ
3581 if node.kind == .call {
3582 ret := t.get_call_return_type(id, node)
3583 if ret.len > 0 {
3584 typ_name = ret
3585 }
3586 } else if typ_name.len == 0 {
3587 typ_name = t.resolve_expr_type(id)
3588 }
3589 if typ_name.len == 0 {
3590 if node.kind == .call {
3591 return t.find_multi_return_call_types(node, expected_count)
3592 }
3593 return none
3594 }
3595 typ := t.tc.parse_type(typ_name)
3596 if items := multi_return_types_from_type(typ, expected_count) {
3597 return items
3598 }
3599 if node.kind == .call {
3600 return t.find_multi_return_call_types(node, expected_count)
3601 }
3602 return none
3603}
3604
3605fn (t &Transformer) match_multi_return_types(node flat.Node, expected_count int) ?[]types.Type {
3606 if node.children_count < 2 {
3607 return none
3608 }
3609 for i in 1 .. node.children_count {
3610 branch := t.a.child_node(&node, i)
3611 if branch.kind != .match_branch {
3612 continue
3613 }
3614 body_start := if branch.value == 'else' { 0 } else { t.count_conds(*branch) }
3615 if branch.children_count <= body_start {
3616 continue
3617 }
3618 tail_id := t.a.child(branch, branch.children_count - 1)
3619 if items := t.multi_return_types_for_expr(tail_id, expected_count) {
3620 return items
3621 }
3622 }
3623 return none
3624}
3625
3626fn (t &Transformer) expr_stmt_multi_return_types(node flat.Node, expected_count int) ?[]types.Type {
3627 if expected_count <= 0 || node.children_count != expected_count {
3628 return none
3629 }
3630 mut result := []types.Type{cap: expected_count}
3631 for i in 0 .. node.children_count {
3632 child_id := t.a.child(&node, i)
3633 mut typ_name := t.node_type(child_id)
3634 if typ_name.len == 0 {
3635 typ_name = t.resolve_expr_type(child_id)
3636 }
3637 if typ_name.len == 0 {
3638 return none
3639 }
3640 result << t.tc.parse_type(typ_name)
3641 }
3642 return result
3643}
3644
3645fn (t &Transformer) block_multi_return_types(node flat.Node, expected_count int) ?[]types.Type {