v / vlib / v2 / transformer / expr.v
3845 lines · 3727 sloc · 118.65 KB
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module transformer
6
7import v2.ast
8import v2.types
9import v2.token
10
11fn (t &Transformer) active_local_decl_type_for_expr(expr ast.Expr) ?types.Type {
12 match expr {
13 ast.Ident {
14 return t.lookup_local_decl_type(expr.name)
15 }
16 ast.ParenExpr {
17 return t.active_local_decl_type_for_expr(expr.expr)
18 }
19 ast.ModifierExpr {
20 return t.active_local_decl_type_for_expr(expr.expr)
21 }
22 ast.ComptimeExpr {
23 return t.active_local_decl_type_for_expr(expr.expr)
24 }
25 else {}
26 }
27
28 return none
29}
30
31fn (mut t Transformer) transform_expr(expr ast.Expr) ast.Expr {
32 // Guard against corrupt Expr with NULL data pointer (ARM64 backend issue).
33 // Default-initialized sum types have data_ptr=0, which crashes on field access.
34 if !expr_has_valid_data(expr) {
35 return expr
36 }
37 return match expr {
38 ast.CallExpr {
39 t.transform_call_expr(expr)
40 }
41 ast.CallOrCastExpr {
42 t.transform_call_or_cast_expr(expr)
43 }
44 ast.IfExpr {
45 t.transform_if_expr(expr)
46 }
47 ast.InfixExpr {
48 t.transform_infix_expr(expr)
49 }
50 ast.ParenExpr {
51 ast.Expr(ast.ParenExpr{
52 expr: t.transform_expr(expr.expr)
53 pos: expr.pos
54 })
55 }
56 ast.PrefixExpr {
57 if expr.op == .amp {
58 if pointer_cast := t.amp_type_cast_expr(expr.expr) {
59 return t.transform_expr(pointer_cast)
60 }
61 }
62 // Lower `&{base | field: val}` (AssocExpr) in the transformer so backends do not
63 // need to deal with address-of rvalue compound expressions.
64 if expr.op == .amp {
65 if assoc := t.unwrap_assoc_expr(expr.expr) {
66 return t.lower_assoc_expr(assoc, true)
67 }
68 }
69 if expr.op == .arrow && expr.expr is ast.OrExpr {
70 or_expr := expr.expr as ast.OrExpr
71 return t.transform_expr(ast.Expr(ast.OrExpr{
72 expr: ast.Expr(ast.PrefixExpr{
73 op: .arrow
74 expr: or_expr.expr
75 pos: expr.pos
76 })
77 stmts: or_expr.stmts
78 pos: or_expr.pos
79 }))
80 }
81 ast.Expr(ast.PrefixExpr{
82 op: expr.op
83 expr: t.transform_expr(expr.expr)
84 pos: expr.pos
85 })
86 }
87 ast.PostfixExpr {
88 if expr.op in [.not, .question] {
89 mut inner := t.transform_expr(expr.expr)
90 // For string range with `!` propagation, use checked version
91 if expr.expr is ast.IndexExpr && expr.expr.expr is ast.RangeExpr
92 && t.is_string_expr(expr.expr.lhs) {
93 inner = t.rename_substr_to_checked(inner)
94 }
95 is_native_backend := t.pref != unsafe { nil } && t.is_native_be
96 if is_native_backend {
97 return ast.Expr(ast.PostfixExpr{
98 op: expr.op
99 expr: inner
100 pos: expr.pos
101 })
102 }
103 // Non-SSA backends still lower `expr!`/`expr?` in the transformer.
104 // Keep codegen valid by converting them to a cast of the underlying
105 // result/option expression to the checker-inferred value type.
106 mut type_name := ''
107 if inner_type := t.get_expr_type(expr.expr) {
108 match inner_type {
109 types.ResultType {
110 type_name = t.type_to_c_name(inner_type.base_type)
111 }
112 types.OptionType {
113 type_name = t.type_to_c_name(inner_type.base_type)
114 }
115 else {}
116 }
117 }
118 if type_name == '' {
119 if typ := t.get_expr_type(expr) {
120 type_name = t.type_to_c_name(typ)
121 }
122 }
123 if type_name != '' {
124 return ast.CastExpr{
125 typ: ast.Expr(ast.Ident{
126 name: type_name
127 })
128 expr: inner
129 }
130 }
131 inner
132 } else {
133 ast.Expr(ast.PostfixExpr{
134 op: expr.op
135 expr: t.transform_expr(expr.expr)
136 pos: expr.pos
137 })
138 }
139 }
140 ast.CastExpr {
141 // Casts to sum types must be lowered to explicit sum type initialization,
142 // since C does not allow casting a variant struct to the sum type struct.
143 sumtype_name := t.type_expr_name_full(expr.typ)
144 if sumtype_name != '' && t.is_sum_type(sumtype_name) {
145 if wrapped := t.wrap_sumtype_value(expr.expr, sumtype_name) {
146 return wrapped
147 }
148 }
149 ast.Expr(ast.CastExpr{
150 typ: expr.typ
151 expr: t.transform_expr(expr.expr)
152 pos: expr.pos
153 })
154 }
155 ast.IndexExpr {
156 t.transform_index_expr(expr)
157 }
158 ast.ArrayInitExpr {
159 t.transform_array_init_expr(expr)
160 }
161 ast.MapInitExpr {
162 t.transform_map_init_expr(expr)
163 }
164 ast.SqlExpr {
165 t.transform_sql_expr(expr)
166 }
167 ast.MatchExpr {
168 t.transform_match_expr(expr)
169 }
170 ast.ComptimeExpr {
171 t.transform_comptime_expr(expr)
172 }
173 ast.InitExpr {
174 t.transform_init_expr(expr)
175 }
176 ast.UnsafeExpr {
177 // Normalize `unsafe { nil }` to plain `nil`.
178 // This keeps pointer-null semantics and avoids backend-specific null lowering bugs.
179 if t.is_unsafe_nil_expr(expr) {
180 ast.Expr(ast.Ident{
181 name: 'nil'
182 pos: expr.pos
183 })
184 } else {
185 ast.Expr(ast.UnsafeExpr{
186 stmts: t.transform_stmts(expr.stmts)
187 })
188 }
189 }
190 ast.LockExpr {
191 // Lower to mutex lock/unlock calls wrapped in UnsafeExpr (compound expression)
192 mut stmts := t.expand_lock_expr(expr)
193 // When used as a value expression (GCC compound expr), the last statement's
194 // value is returned. But the unlock calls come after the body, making the
195 // compound expr return void. Fix: duplicate the value-producing statement
196 // (last body stmt) after the unlock calls so it becomes the final expression.
197 n_unlocks := expr.lock_exprs.len + expr.rlock_exprs.len
198 if n_unlocks > 0 && stmts.len > n_unlocks {
199 body_end := stmts.len - n_unlocks
200 stmts << stmts[body_end - 1]
201 }
202 ast.Expr(ast.UnsafeExpr{
203 stmts: stmts
204 })
205 }
206 ast.AssocExpr {
207 t.lower_assoc_expr(expr, false)
208 }
209 ast.FieldInit {
210 // Transform the value inside field initializations (e.g., fn(name: expr))
211 ast.Expr(ast.FieldInit{
212 name: expr.name
213 value: t.transform_expr(expr.value)
214 })
215 }
216 ast.SelectorExpr {
217 t.transform_selector_expr(expr)
218 }
219 ast.Ident {
220 if expr.name == '@VMODROOT' {
221 return ast.Expr(t.vmodroot_string_literal(expr.pos))
222 }
223 // Check for smart cast on simple identifiers (e.g., if x is Type { x })
224 if ctx := t.find_smartcast_for_expr(expr.name) {
225 return t.apply_smartcast_direct_ctx(expr, ctx)
226 }
227 ast.Expr(expr)
228 }
229 ast.StringInterLiteral {
230 // Transform interpolations, applying smart cast if needed
231 t.transform_string_inter_literal(expr)
232 }
233 ast.AsCastExpr {
234 // Keep explicit `as` casts as casts. A same-expression smartcast can
235 // otherwise rewrite the operand into a direct sum payload access and hide
236 // the original storage type from the backend, which is wrong for nested
237 // sum types like Expr(Type(ArrayFixedType)).
238 expr_key := t.expr_to_string(expr.expr)
239 saved_smartcast_stack := t.smartcast_stack.clone()
240 saved_smartcast_expr_counts := t.smartcast_expr_counts.clone()
241 for _ in 0 .. smartcast_search_limit {
242 if _ := t.remove_smartcast_for_expr(expr_key) {
243 continue
244 }
245 break
246 }
247 transformed_inner := t.transform_expr(expr.expr)
248 t.smartcast_stack = saved_smartcast_stack.clone()
249 t.smartcast_expr_counts = saved_smartcast_expr_counts.clone()
250 ast.Expr(ast.AsCastExpr{
251 expr: transformed_inner
252 typ: expr.typ
253 pos: expr.pos
254 })
255 }
256 ast.OrExpr {
257 // OrExpr in expression context (e.g., nested, in return, in for-loop condition)
258 mut prefix_stmts := []ast.Stmt{}
259 result_expr := t.expand_single_or_expr(expr, mut prefix_stmts)
260 if prefix_stmts.len > 0 {
261 // Wrap in UnsafeExpr — cleanc emits as GCC compound expression ({ ... })
262 prefix_stmts << ast.ExprStmt{
263 expr: result_expr
264 }
265 ast.Expr(ast.UnsafeExpr{
266 stmts: prefix_stmts
267 })
268 } else {
269 result_expr
270 }
271 }
272 ast.IfGuardExpr {
273 // IfGuardExpr should only appear as IfExpr condition, handled by transform_if_expr.
274 // If it somehow reaches here standalone, just evaluate the RHS.
275 if expr.stmt.rhs.len > 0 {
276 return t.transform_expr(expr.stmt.rhs[0])
277 }
278 expr
279 }
280 ast.GenericArgs {
281 // typeof[T] — keep as-is so typeof[T]().name can be resolved later
282 if t.is_typeof_generic_args(expr) {
283 return expr
284 }
285 // Disambiguate `x[y]` parsed as GenericArgs: if lhs is not callable and there
286 // is a single argument, this is an index expression.
287 if expr.args.len == 1 {
288 if lhs_type := t.get_expr_type(expr.lhs) {
289 if !t.is_callable_type(lhs_type) {
290 return t.transform_index_expr(ast.IndexExpr{
291 lhs: expr.lhs
292 expr: expr.args[0]
293 is_gated: false
294 pos: expr.pos
295 })
296 }
297 }
298 }
299 // Keep selectors as selectors so module and method calls still lower
300 // through the normal call paths after specialization.
301 t.specialize_generic_callable_expr(expr.lhs, expr.args, expr.pos)
302 }
303 ast.GenericArgOrIndexExpr {
304 // Disambiguate parser ambiguity `x[y]`:
305 // - callable lhs => generic arg specialization token (`x_T`)
306 // - otherwise => normal index expression
307 if lhs_type := t.get_expr_type(expr.lhs) {
308 if t.is_callable_type(lhs_type) {
309 return t.specialize_generic_callable_expr(expr.lhs, [expr.expr], expr.pos)
310 }
311 }
312 return t.transform_index_expr(ast.IndexExpr{
313 lhs: expr.lhs
314 expr: expr.expr
315 is_gated: false
316 pos: expr.pos
317 })
318 }
319 ast.ModifierExpr {
320 ast.Expr(ast.ModifierExpr{
321 kind: expr.kind
322 expr: t.transform_expr(expr.expr)
323 pos: expr.pos
324 })
325 }
326 ast.FnLiteral {
327 ast.Expr(ast.FnLiteral{
328 typ: expr.typ
329 captured_vars: expr.captured_vars
330 stmts: t.transform_stmts(expr.stmts)
331 pos: expr.pos
332 })
333 }
334 ast.LambdaExpr {
335 ast.Expr(ast.LambdaExpr{
336 args: expr.args
337 expr: t.transform_expr(expr.expr)
338 pos: expr.pos
339 })
340 }
341 ast.KeywordOperator {
342 if expr.op == .key_typeof && expr.exprs.len > 0 {
343 type_name := t.resolve_typeof_expr(expr.exprs[0])
344 if type_name != '' {
345 return ast.StringLiteral{
346 kind: .v
347 value: quote_v_string_literal(type_name)
348 pos: expr.pos
349 }
350 }
351 }
352 if expr.op == .key_go && expr.exprs.len > 0 {
353 return t.lower_go_call(expr)
354 }
355 ast.Expr(expr)
356 }
357 else {
358 expr
359 }
360 }
361}
362
363fn (t &Transformer) amp_type_cast_expr(expr ast.Expr) ?ast.Expr {
364 match expr {
365 ast.IndexExpr {
366 if expr.lhs !is ast.CallOrCastExpr {
367 return none
368 }
369 cast_lhs := expr.lhs as ast.CallOrCastExpr
370 if !t.call_or_cast_lhs_is_type(cast_lhs.lhs) {
371 return none
372 }
373 return ast.IndexExpr{
374 lhs: ast.CastExpr{
375 typ: ast.PrefixExpr{
376 op: .amp
377 expr: cast_lhs.lhs
378 pos: cast_lhs.pos
379 }
380 expr: cast_lhs.expr
381 pos: cast_lhs.pos
382 }
383 expr: expr.expr
384 is_gated: expr.is_gated
385 pos: expr.pos
386 }
387 }
388 ast.SelectorExpr {
389 if expr.lhs !is ast.CallOrCastExpr {
390 return none
391 }
392 cast_lhs := expr.lhs as ast.CallOrCastExpr
393 if !t.call_or_cast_lhs_is_type(cast_lhs.lhs) {
394 return none
395 }
396 return ast.SelectorExpr{
397 lhs: ast.CastExpr{
398 typ: ast.PrefixExpr{
399 op: .amp
400 expr: cast_lhs.lhs
401 pos: cast_lhs.pos
402 }
403 expr: cast_lhs.expr
404 pos: cast_lhs.pos
405 }
406 rhs: expr.rhs
407 pos: expr.pos
408 }
409 }
410 else {
411 return none
412 }
413 }
414}
415
416// is_typeof_generic_args checks if a GenericArgs node is typeof[T]
417// without using an `is` check that would trigger smartcast narrowing.
418fn (t &Transformer) is_typeof_generic_args(ga ast.GenericArgs) bool {
419 return ga.lhs.name() == 'typeof'
420}
421
422fn (t &Transformer) resolve_typeof_generic_arg_type_name(arg ast.Expr) string {
423 type_name_from_expr := t.resolve_typeof_expr(arg)
424 if type_name_from_expr != '' {
425 return type_name_from_expr
426 }
427 type_name := t.expr_to_type_name(arg)
428 if type_name == '' {
429 return ''
430 }
431 return c_name_to_v_name(type_name)
432}
433
434fn (t &Transformer) resolve_specialized_typeof_call_type_name(call_name string) string {
435 marker := 'typeof_T_'
436 idx := call_name.index(marker) or { return '' }
437 type_name := call_name[idx + marker.len..]
438 if type_name == '' {
439 return ''
440 }
441 return c_name_to_v_name(type_name)
442}
443
444fn (t &Transformer) resolve_typeof_call_lhs_type_name(lhs ast.Expr) string {
445 if lhs is ast.GenericArgs {
446 if t.is_typeof_generic_args(lhs) && lhs.args.len > 0 {
447 return t.resolve_typeof_generic_arg_type_name(lhs.args[0])
448 }
449 }
450 if lhs is ast.GenericArgOrIndexExpr {
451 if lhs.lhs.name() == 'typeof' {
452 return t.resolve_typeof_generic_arg_type_name(lhs.expr)
453 }
454 }
455 if lhs is ast.Ident {
456 return t.resolve_specialized_typeof_call_type_name(lhs.name)
457 }
458 return ''
459}
460
461fn typeof_selector_result(type_name string, selector string, pos token.Pos) ?ast.Expr {
462 if type_name == '' {
463 return none
464 }
465 if selector == 'idx' {
466 return ast.Expr(typeof_idx_literal(type_name, pos))
467 }
468 if selector == 'name' {
469 return ast.Expr(ast.StringLiteral{
470 kind: .v
471 value: quote_v_string_literal(type_name)
472 pos: pos
473 })
474 }
475 return none
476}
477
478fn (mut t Transformer) transform_index_expr(expr ast.IndexExpr) ast.Expr {
479 // Lower slices in transformer so backends do not need slice-specific type logic.
480 if expr.expr is ast.RangeExpr {
481 lhs := t.transform_expr(expr.lhs)
482 return t.transform_slice_index_expr(lhs, expr.lhs, expr.expr, expr.is_gated)
483 }
484
485 // Keep gated indexing as-is (`arr#[i]`).
486 if expr.is_gated {
487 return ast.IndexExpr{
488 lhs: t.transform_expr(expr.lhs)
489 expr: t.transform_expr(expr.expr)
490 is_gated: expr.is_gated
491 pos: expr.pos
492 }
493 }
494
495 // Lower map reads `m[key]` to `map__get(&m, &key, &zero)` in transformer so backends
496 // do not need map-specific IndexExpr logic.
497 mut map_expr_type_opt := t.map_index_lhs_type(expr.lhs)
498 if map_expr_type_opt == none && expr.lhs is ast.Ident {
499 map_expr_type_opt = t.lookup_var_type((expr.lhs as ast.Ident).name)
500 }
501 if map_expr_typ := map_expr_type_opt {
502 if map_type := t.unwrap_map_type(map_expr_typ) {
503 if t.is_eval_backend() {
504 return ast.IndexExpr{
505 lhs: t.transform_expr(expr.lhs)
506 expr: t.transform_expr(expr.expr)
507 is_gated: expr.is_gated
508 pos: expr.pos
509 }
510 }
511 mut stmts := []ast.Stmt{}
512
513 // Map arg: map__get expects a pointer to the map.
514 map_arg := t.map_expr_to_runtime_ptr(expr.lhs, map_expr_typ, map_type)
515
516 // Key temp (so we can take its address safely for the duration of the whole expression).
517 key_tmp := t.gen_temp_name()
518 key_ident := t.typed_temp_ident(key_tmp, map_type.key_type)
519 mut key_lhs := []ast.Expr{cap: 1}
520 key_lhs << ast.Expr(key_ident)
521 mut key_rhs := []ast.Expr{cap: 1}
522 key_rhs << t.transform_expr(expr.expr)
523 stmts << ast.Stmt(ast.AssignStmt{
524 op: .decl_assign
525 lhs: key_lhs
526 rhs: key_rhs
527 pos: key_ident.pos
528 })
529
530 // Zero/default temp.
531 zero_tmp := t.gen_temp_name()
532 zero_ident := t.typed_temp_ident(zero_tmp, map_type.value_type)
533 mut zero_lhs := []ast.Expr{cap: 1}
534 zero_lhs << ast.Expr(zero_ident)
535 mut zero_rhs := []ast.Expr{cap: 1}
536 zero_rhs << t.zero_value_expr_for_type(map_type.value_type)
537 stmts << ast.Stmt(ast.AssignStmt{
538 op: .decl_assign
539 lhs: zero_lhs
540 rhs: zero_rhs
541 pos: zero_ident.pos
542 })
543
544 get_call := ast.CallExpr{
545 lhs: ast.Ident{
546 name: 'map__get'
547 }
548 args: [
549 map_arg,
550 t.voidptr_cast(ast.Expr(ast.PrefixExpr{
551 op: .amp
552 expr: key_ident
553 })),
554 t.voidptr_cast(ast.Expr(ast.PrefixExpr{
555 op: .amp
556 expr: zero_ident
557 })),
558 ]
559 }
560 cast_ptr_type := ast.Expr(ast.PrefixExpr{
561 op: .amp
562 expr: t.type_to_ast_type_expr(map_type.value_type)
563 })
564 deref_pos := t.next_synth_pos()
565 t.register_synth_type(deref_pos, map_type.value_type)
566 typed_ptr := ast.Expr(ast.CastExpr{
567 typ: cast_ptr_type
568 expr: get_call
569 })
570 deref_expr := ast.Expr(ast.PrefixExpr{
571 op: .mul
572 expr: typed_ptr
573 pos: deref_pos
574 })
575 stmts << ast.Stmt(ast.ExprStmt{
576 expr: ast.Expr(ast.ParenExpr{
577 expr: deref_expr
578 pos: deref_pos
579 })
580 })
581
582 return ast.Expr(ast.UnsafeExpr{
583 stmts: stmts
584 pos: deref_pos
585 })
586 }
587 }
588
589 return ast.IndexExpr{
590 lhs: t.transform_expr(expr.lhs)
591 expr: t.transform_expr(expr.expr)
592 is_gated: expr.is_gated
593 pos: expr.pos
594 }
595}
596
597// is_simple_slice_bound reports whether a slice bound expression is free of
598// side effects and cheap enough to reference more than once (so a fixed-array
599// slice can inline it instead of binding it to a temp).
600fn is_simple_slice_bound(e ast.Expr) bool {
601 return match e {
602 ast.BasicLiteral { true }
603 ast.Ident { true }
604 ast.SelectorExpr { is_simple_slice_bound(e.lhs) }
605 ast.ParenExpr { is_simple_slice_bound(e.expr) }
606 else { false }
607 }
608}
609
610fn (mut t Transformer) transform_slice_index_expr(lhs ast.Expr, orig_lhs ast.Expr, range ast.RangeExpr, _is_gated bool) ast.Expr {
611 start_expr := if range.start is ast.EmptyExpr {
612 ast.Expr(ast.BasicLiteral{
613 kind: .number
614 value: '0'
615 })
616 } else {
617 t.transform_expr(range.start)
618 }
619
620 // Build end expression for lowering target calls:
621 // `a..b` -> b, `a...b` -> b + 1, `a..` -> lhs.len.
622 mut end_expr := ast.Expr(ast.empty_expr)
623 if range.end is ast.EmptyExpr {
624 end_expr = t.synth_selector(lhs, 'len', types.Type(types.int_))
625 } else {
626 end_expr = t.transform_expr(range.end)
627 if range.op == .ellipsis {
628 end_expr = ast.Expr(ast.InfixExpr{
629 op: .plus
630 lhs: end_expr
631 rhs: ast.BasicLiteral{
632 kind: .number
633 value: '1'
634 }
635 })
636 }
637 }
638 string_end_expr := if range.end is ast.EmptyExpr {
639 ast.Expr(ast.BasicLiteral{
640 kind: .number
641 value: '2147483647'
642 })
643 } else {
644 end_expr
645 }
646 array_slice_fn_name := if range.end is ast.EmptyExpr {
647 'array__slice_ni'
648 } else {
649 'array__slice'
650 }
651
652 // Prefer semantic string detection over position-based type tags.
653 // Expression positions are often shared with parent index/slice nodes,
654 // which can incorrectly stamp the source as array-like.
655 if t.is_string_expr(orig_lhs) || t.is_string_expr(lhs) {
656 return ast.CallExpr{
657 lhs: ast.Ident{
658 name: 'string__substr'
659 }
660 args: [lhs, start_expr, string_end_expr]
661 }
662 }
663
664 if lhs_type := t.get_expr_type(orig_lhs) {
665 match lhs_type {
666 types.String {
667 return ast.CallExpr{
668 lhs: ast.Ident{
669 name: 'string__substr'
670 }
671 args: [lhs, start_expr, string_end_expr]
672 }
673 }
674 types.Alias {
675 if lhs_type.name == 'string' || lhs_type.base_type is types.String {
676 return ast.CallExpr{
677 lhs: ast.Ident{
678 name: 'string__substr'
679 }
680 args: [lhs, start_expr, string_end_expr]
681 }
682 }
683 }
684 types.Array {
685 return ast.CallExpr{
686 lhs: ast.Ident{
687 name: array_slice_fn_name
688 }
689 args: [lhs, start_expr, end_expr]
690 }
691 }
692 types.ArrayFixed {
693 elem_c_name := lhs_type.elem_type.name()
694 // For a fixed array the default high bound (`a[..]`) is the
695 // compile-time length, not a runtime `.len` selector. Emitting
696 // the selector (`u.b.len`) leaves cleanc unable to type the
697 // bound temp and it falls back to the fixed-array type, which
698 // then breaks the `new_array_from_c_array` pointer arithmetic.
699 fixed_end_expr := if range.end is ast.EmptyExpr {
700 ast.Expr(ast.BasicLiteral{
701 kind: .number
702 value: lhs_type.len.str()
703 })
704 } else {
705 end_expr
706 }
707 sizeof_expr := ast.Expr(ast.KeywordOperator{
708 op: .key_sizeof
709 exprs: [
710 ast.Expr(ast.Ident{
711 name: elem_c_name
712 }),
713 ]
714 })
715 // When both bounds are side-effect free we can reference them
716 // directly: `new_array_from_c_array(end - start, end - start,
717 // sizeof(T), &a[start])`. This avoids emitting bound temps,
718 // which the cleanc backend can mis-type as the fixed-array type
719 // (turning `end - start` into pointer arithmetic on a struct).
720 if is_simple_slice_bound(start_expr) && is_simple_slice_bound(fixed_end_expr) {
721 len_expr := ast.Expr(ast.InfixExpr{
722 op: .minus
723 lhs: fixed_end_expr
724 rhs: start_expr
725 })
726 return ast.CallExpr{
727 lhs: ast.Ident{
728 name: 'new_array_from_c_array'
729 }
730 args: [
731 len_expr,
732 len_expr,
733 sizeof_expr,
734 ast.Expr(ast.PrefixExpr{
735 op: .amp
736 expr: ast.IndexExpr{
737 lhs: lhs
738 expr: start_expr
739 }
740 }),
741 ]
742 }
743 }
744 // Complex bounds (calls, arithmetic, ...) must be evaluated once,
745 // so bind them to typed temps before the slice call.
746 start_ident := t.gen_typed_temp_ident(types.Type(types.int_))
747 end_ident := t.gen_typed_temp_ident(types.Type(types.int_))
748 len_expr := ast.Expr(ast.InfixExpr{
749 op: .minus
750 lhs: ast.Expr(end_ident)
751 rhs: ast.Expr(start_ident)
752 })
753 slice_call := ast.Expr(ast.CallExpr{
754 lhs: ast.Ident{
755 name: 'new_array_from_c_array'
756 }
757 args: [
758 len_expr,
759 len_expr,
760 sizeof_expr,
761 ast.Expr(ast.PrefixExpr{
762 op: .amp
763 expr: ast.IndexExpr{
764 lhs: lhs
765 expr: ast.Expr(start_ident)
766 }
767 }),
768 ]
769 })
770 return ast.UnsafeExpr{
771 stmts: [
772 ast.Stmt(ast.AssignStmt{
773 op: .decl_assign
774 lhs: [ast.Expr(start_ident)]
775 rhs: [start_expr]
776 pos: start_ident.pos
777 }),
778 ast.Stmt(ast.AssignStmt{
779 op: .decl_assign
780 lhs: [ast.Expr(end_ident)]
781 rhs: [fixed_end_expr]
782 pos: end_ident.pos
783 }),
784 ast.Stmt(ast.ExprStmt{
785 expr: slice_call
786 }),
787 ]
788 }
789 }
790 types.Pointer {
791 if lhs_type.base_type is types.Array {
792 return ast.CallExpr{
793 lhs: ast.Ident{
794 name: array_slice_fn_name
795 }
796 args: [
797 ast.Expr(ast.PrefixExpr{
798 op: .mul
799 expr: lhs
800 }),
801 start_expr,
802 end_expr,
803 ]
804 }
805 }
806 }
807 else {}
808 }
809 }
810 // Fallback when env type lookup misses selector/if-guard positions.
811 if t.infer_expr_type(orig_lhs) == 'string' {
812 return ast.CallExpr{
813 lhs: ast.Ident{
814 name: 'string__substr'
815 }
816 args: [lhs, start_expr, string_end_expr]
817 }
818 }
819
820 // Type lookup failed; default to array__slice (most common case).
821 return ast.CallExpr{
822 lhs: ast.Ident{
823 name: array_slice_fn_name
824 }
825 args: [lhs, start_expr, end_expr]
826 }
827}
828
829// transform_selector_expr transforms a selector expression, applying smart cast if applicable
830fn (mut t Transformer) transform_selector_expr(expr ast.SelectorExpr) ast.Expr {
831 // typeof(x).name -> string literal with V type name
832 if expr.lhs is ast.KeywordOperator && expr.lhs.op == .key_typeof
833 && expr.rhs.name in ['name', 'idx'] {
834 if expr.lhs.exprs.len > 0 {
835 type_name := t.resolve_typeof_expr(expr.lhs.exprs[0])
836 if result := typeof_selector_result(type_name, expr.rhs.name, expr.pos) {
837 return result
838 }
839 }
840 }
841 // typeof[T]().name -> string literal with V type name
842 // Parser creates: SelectorExpr{lhs: CallExpr{lhs: GenericArgs{lhs: Ident{"typeof"}, args: [T]}}, rhs: "name"}
843 if expr.rhs.name in ['name', 'idx'] && expr.lhs is ast.CallExpr {
844 call := expr.lhs as ast.CallExpr
845 type_name := t.resolve_typeof_call_lhs_type_name(call.lhs)
846 if result := typeof_selector_result(type_name, expr.rhs.name, expr.pos) {
847 return result
848 }
849 }
850 if expr.rhs.name in ['name', 'idx'] && expr.lhs is ast.CallOrCastExpr {
851 call := expr.lhs as ast.CallOrCastExpr
852 if call.expr is ast.EmptyExpr {
853 type_name := t.resolve_typeof_call_lhs_type_name(call.lhs)
854 if result := typeof_selector_result(type_name, expr.rhs.name, expr.pos) {
855 return result
856 }
857 }
858 }
859 // Generated sumtype representation fields already have their base expression
860 // lowered. Do not apply smartcasts to them again on a later transform pass.
861 if expr.rhs.name in ['_tag', '_data'] || (expr.rhs.name.starts_with('_')
862 && expr.lhs is ast.SelectorExpr && (expr.lhs as ast.SelectorExpr).rhs.name == '_data') {
863 return ast.Expr(expr)
864 }
865 // Check for smart cast field access: check ALL contexts in the stack
866 if t.has_active_smartcast() {
867 full_str := t.expr_to_string(expr)
868 // First check if the ENTIRE selector matches a direct smartcast context
869 // This handles cases like `sel := rhs_expr.lhs` inside `if rhs_expr.lhs is SelectorExpr`
870 if direct_ctx := t.find_smartcast_for_expr(full_str) {
871 // Direct access to smartcast variable - apply direct smartcast
872 return t.apply_smartcast_direct_ctx(expr, direct_ctx)
873 }
874 // Check if LHS matches any smartcast context for field access
875 lhs_str := t.expr_to_string(expr.lhs)
876 if ctx := t.find_smartcast_for_expr(lhs_str) {
877 // This is a field access on the smartcast variable
878 // e.g., w.valera.len when w.valera is smartcast to string
879 return t.apply_smartcast_field_access_ctx(expr.lhs, expr.rhs.name, ctx)
880 }
881 }
882 if expr.lhs is ast.Ident && expr.lhs.name == 'os' && expr.rhs.name == 'args' {
883 return ast.CallExpr{
884 lhs: ast.Ident{
885 name: 'arguments'
886 pos: expr.pos
887 }
888 pos: expr.pos
889 }
890 }
891 // Handle module-qualified enum value access: module.EnumType.value -> module__EnumType__value
892 // Also handle nested module references: rand.seed.time_seed_array -> seed__time_seed_array
893 if expr.lhs is ast.SelectorExpr {
894 lhs_sel := expr.lhs as ast.SelectorExpr
895 if lhs_sel.lhs is ast.Ident {
896 module_name := lhs_sel.lhs.name
897 type_name := lhs_sel.rhs.name
898 qualified := '${module_name}__${type_name}'
899 if typ := t.lookup_type(qualified) {
900 if typ is types.Enum {
901 t.register_synth_type(expr.pos, typ)
902 return ast.Ident{
903 name: t.enum_member_ident_for_lookup(qualified, typ, expr.rhs.name)
904 pos: expr.pos
905 }
906 }
907 }
908 // Nested module reference: rand.seed.time_seed_array -> seed__time_seed_array
909 // Only resolve when both the outer ident is a module AND the inner name
910 // is also a sub-module (not a variable like os.args.len).
911 if t.is_module_ident(module_name) {
912 sub_mod := lhs_sel.rhs.name
913 fn_name := expr.rhs.name
914 // Check if sub_mod is actually a module scope
915 if t.get_module_scope(sub_mod) != none {
916 return ast.Ident{
917 name: '${sub_mod}__${fn_name}'
918 pos: expr.pos
919 }
920 }
921 // Try full qualified name (module__sub_mod as scope key)
922 full_mod := '${module_name}__${sub_mod}'
923 if t.get_module_scope(full_mod) != none {
924 return ast.Ident{
925 name: '${full_mod}__${fn_name}'
926 pos: expr.pos
927 }
928 }
929 }
930 }
931 }
932 // Handle same-module enum access: Op.identify -> discord__Op__identify
933 if expr.lhs is ast.Ident {
934 lhs_name := expr.lhs.name
935 // Check if LHS is an enum type in the current module
936 qualified := if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin'
937 && !lhs_name.contains('__') {
938 '${t.cur_module}__${lhs_name}'
939 } else {
940 lhs_name
941 }
942 if typ := t.lookup_type(qualified) {
943 if typ is types.Enum {
944 t.register_synth_type(expr.pos, typ)
945 return ast.Ident{
946 name: t.enum_member_ident_for_lookup(qualified, typ, expr.rhs.name)
947 pos: expr.pos
948 }
949 }
950 }
951 }
952 // Default transformation
953 return ast.SelectorExpr{
954 lhs: t.transform_expr(expr.lhs)
955 rhs: expr.rhs
956 pos: expr.pos
957 }
958}
959
960// transform_string_inter_literal transforms string interpolations, applying smart cast where needed
961fn (mut t Transformer) transform_match_expr(expr ast.MatchExpr) ast.Expr {
962 match_expr, branches := t.transform_match_expr_parts(expr)
963 return t.lower_match_expr_to_if(match_expr, branches)
964}
965
966fn sumtype_match_variant_base_name(name string) string {
967 c_name := name.replace('.', '__')
968 if bracket_idx := c_name.index('[') {
969 return c_name[..bracket_idx]
970 }
971 return c_name
972}
973
974fn sumtype_match_generic_base_is_unique(sumtype_name string, variants []string, base_variant string) bool {
975 mut matches := 0
976 for variant in variants {
977 if sum_type_variant_matches_for_sumtype(sumtype_name,
978 sumtype_match_variant_base_name(variant), base_variant)
979 {
980 matches++
981 if matches > 1 {
982 return false
983 }
984 }
985 }
986 return matches == 1
987}
988
989fn (t &Transformer) generic_match_branch_variant_info(lhs ast.Expr, args []ast.Expr) (string, string, string, bool) {
990 base_name := t.type_expr_name(lhs)
991 if base_name == '' {
992 return '', '', '', false
993 }
994 base_full := t.type_expr_name_full(lhs)
995 suffix := t.generic_specialization_suffix(args)
996 variant_full := if base_full != '' && suffix != '' { base_full + suffix } else { base_full }
997 variant_module := if base_full.contains('__') {
998 base_full.all_before_last('__')
999 } else {
1000 ''
1001 }
1002 return base_name, variant_full, variant_module, true
1003}
1004
1005fn (t &Transformer) match_branch_variant_info(c ast.Expr) (string, string, string, bool, bool) {
1006 if c is ast.Ident {
1007 c_variant_name := c.name
1008 c_variant_name_full := if t.cur_module != '' && t.cur_module != 'main'
1009 && t.cur_module != 'builtin'
1010 && c.name !in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'byte', 'rune', 'f32', 'f64', 'usize', 'isize', 'bool', 'string', 'voidptr', 'charptr', 'byteptr'] {
1011 '${t.cur_module}__${c.name}'
1012 } else {
1013 c.name
1014 }
1015 return c_variant_name, c_variant_name_full, '', false, c_variant_name != ''
1016 }
1017 if c is ast.SelectorExpr {
1018 c_variant_name := c.rhs.name
1019 c_variant_name_full := t.type_expr_name_full(c)
1020 c_variant_module := if c_variant_name_full.contains('__') {
1021 c_variant_name_full.all_before_last('__')
1022 } else {
1023 ''
1024 }
1025 return c_variant_name, c_variant_name_full, c_variant_module, false, c_variant_name != ''
1026 }
1027 if c is ast.Type {
1028 if c is ast.GenericType {
1029 c_variant_name, c_variant_name_full, c_variant_module, _ := t.generic_match_branch_variant_info(c.name,
1030 c.params)
1031 return c_variant_name, c_variant_name_full, c_variant_module, true, c_variant_name != ''
1032 }
1033 c_variant_name := t.type_variant_name(c)
1034 return c_variant_name, t.type_variant_name_full(c), '', false, c_variant_name != ''
1035 }
1036 if c is ast.GenericArgs {
1037 c_variant_name, c_variant_name_full, c_variant_module, _ := t.generic_match_branch_variant_info(c.lhs,
1038 c.args)
1039 return c_variant_name, c_variant_name_full, c_variant_module, true, c_variant_name != ''
1040 }
1041 if c is ast.GenericArgOrIndexExpr {
1042 c_variant_name, c_variant_name_full, c_variant_module, _ := t.generic_match_branch_variant_info(c.lhs, [
1043 c.expr,
1044 ])
1045 return c_variant_name, c_variant_name_full, c_variant_module, true, c_variant_name != ''
1046 }
1047 return '', '', '', false, false
1048}
1049
1050// transform_match_expr_parts returns the two inputs to `lower_match_expr_to_if`
1051// — the transformed match expression (either `_tag` access for sumtype matches
1052// or the plain transformed `expr.expr` for non-sumtype matches) and the
1053// transformed branch list. The flat-write arm calls this directly + then
1054// invokes `lower_match_expr_to_if_flat` to skip every nested IfExpr wrapper-
1055// struct allocation in the lowered chain. The legacy `transform_match_expr`
1056// above wraps it for non-flat callers via `lower_match_expr_to_if`. Same
1057// `_parts` extraction template as `transform_return_stmt_parts` (session 59)
1058// and `transform_assign_stmt_parts` (session 60).
1059fn (mut t Transformer) transform_match_expr_parts(expr ast.MatchExpr) (ast.Expr, []ast.MatchBranch) {
1060 // Check if matching on a sum type
1061 mut sumtype_name := t.get_sumtype_name_for_expr(expr.expr)
1062 smartcast_expr := t.expr_to_string(expr.expr)
1063
1064 // Verify that it's actually a sum type match by checking branch conditions.
1065 // If conditions are string/number literals, it's NOT a sum type match even if
1066 // the expression type looks like a sum type.
1067 if sumtype_name != '' && expr.branches.len > 0 {
1068 first_branch := expr.branches[0]
1069 if first_branch.cond.len > 0 {
1070 first_cond := first_branch.cond[0]
1071 if first_cond is ast.BasicLiteral || first_cond is ast.StringLiteral
1072 || first_cond is ast.StringInterLiteral {
1073 sumtype_name = ''
1074 }
1075 }
1076 }
1077
1078 if sumtype_name != '' {
1079 // Sum type match - set up smartcast context for each branch
1080 variants := t.get_sum_type_variants(sumtype_name)
1081
1082 mut branches := []ast.MatchBranch{cap: expr.branches.len}
1083 for branch in expr.branches {
1084 if branch.cond.len > 0 {
1085 mut cond_tags := []int{cap: branch.cond.len}
1086 mut cond_variants := []string{cap: branch.cond.len}
1087 mut cond_variants_full := []string{cap: branch.cond.len}
1088 mut can_split_branch := true
1089
1090 for c in branch.cond {
1091 c_variant_name, c_variant_name_full, c_variant_module, c_variant_is_generic, ok :=
1092 t.match_branch_variant_info(c)
1093 if !ok {
1094 can_split_branch = false
1095 break
1096 }
1097
1098 qualified_variant := if c_variant_module != ''
1099 && !c_variant_name.starts_with('Array_')
1100 && !c_variant_name.starts_with('Map_') {
1101 '${c_variant_module}__${c_variant_name}'
1102 } else {
1103 c_variant_name
1104 }
1105 qualified_variant_full := if c_variant_name_full != ''
1106 && c_variant_name_full != c_variant_name {
1107 c_variant_name_full
1108 } else if c_variant_module != '' {
1109 '${c_variant_module}__${c_variant_name}'
1110 } else {
1111 c_variant_name
1112 }
1113
1114 mut c_tag := -1
1115 for i, v in variants {
1116 if c_variant_is_generic
1117 && sum_type_variant_matches_for_sumtype(sumtype_name, v, qualified_variant_full) {
1118 c_tag = i
1119 break
1120 }
1121 if sum_type_variant_matches_for_sumtype(sumtype_name, v, qualified_variant) {
1122 c_tag = i
1123 break
1124 }
1125 if c_variant_is_generic
1126 && sumtype_match_generic_base_is_unique(sumtype_name, variants, qualified_variant)
1127 && sum_type_variant_matches_for_sumtype(sumtype_name, sumtype_match_variant_base_name(v), qualified_variant) {
1128 c_tag = i
1129 break
1130 }
1131 // Handle array variant matching:
1132 // c_variant_name is 'Array_Attribute' or 'Array_ast__Attribute' (C format)
1133 // v is '[]Attribute' or '[]ast__Attribute' (V format from types.Array.name())
1134 if c_variant_name.starts_with('Array_') && v.starts_with('[]') {
1135 c_elem := c_variant_name[6..] // Strip 'Array_'
1136 v_elem := v[2..] // Strip '[]'
1137 c_elem_short := if c_elem.contains('__') {
1138 c_elem.all_after_last('__')
1139 } else {
1140 c_elem
1141 }
1142 v_elem_short := if v_elem.contains('__') {
1143 v_elem.all_after_last('__')
1144 } else {
1145 v_elem
1146 }
1147 if c_elem == v_elem || c_elem_short == v_elem_short {
1148 c_tag = i
1149 break
1150 }
1151 }
1152 // Handle fixed array variant matching
1153 if c_variant_name.starts_with('Array_fixed_') && v.starts_with('[') {
1154 // TODO: implement fixed array matching if needed
1155 }
1156 // Handle map variant matching
1157 if c_variant_name.starts_with('Map_') && v.starts_with('map[') {
1158 // TODO: implement map matching if needed
1159 }
1160 }
1161
1162 if c_tag < 0 {
1163 can_split_branch = false
1164 break
1165 }
1166 cond_tags << c_tag
1167 cond_variants << if c_variant_is_generic && qualified_variant_full != '' {
1168 qualified_variant_full
1169 } else {
1170 qualified_variant
1171 }
1172 cond_variants_full << qualified_variant_full
1173 }
1174
1175 if can_split_branch && cond_tags.len > 0 {
1176 // When a branch has multiple sum variants, each condition needs its own
1177 // smartcast context. Splitting preserves correct dispatch in branch bodies.
1178 for i, c_tag in cond_tags {
1179 smartcast_stack_before := t.smartcast_stack.clone()
1180 smartcast_counts_before := t.smartcast_expr_counts.clone()
1181 t.push_smartcast_full(smartcast_expr, cond_variants[i],
1182 cond_variants_full[i], sumtype_name)
1183 mut transformed_stmts := t.transform_match_branch_stmts(branch.stmts)
1184 if t.sumtype_return_wrap != '' {
1185 transformed_stmts = t.wrap_sumtype_return_branch_tail_stmts(transformed_stmts,
1186 t.sumtype_return_wrap)
1187 }
1188 t.smartcast_stack = smartcast_stack_before.clone()
1189 t.smartcast_expr_counts = smartcast_counts_before.clone()
1190
1191 branches << ast.MatchBranch{
1192 cond: [
1193 ast.Expr(ast.BasicLiteral{
1194 kind: token.Token.number
1195 value: '${c_tag}'
1196 }),
1197 ]
1198 stmts: transformed_stmts
1199 pos: branch.pos
1200 }
1201 }
1202 } else {
1203 // No variant name found, just transform normally
1204 mut fallback_stmts := t.transform_match_branch_stmts(branch.stmts)
1205 if t.sumtype_return_wrap != '' {
1206 fallback_stmts = t.wrap_sumtype_return_branch_tail_stmts(fallback_stmts,
1207 t.sumtype_return_wrap)
1208 }
1209 branches << ast.MatchBranch{
1210 cond: branch.cond
1211 stmts: fallback_stmts
1212 pos: branch.pos
1213 }
1214 }
1215 } else {
1216 // else branch - no smartcast context
1217 mut else_stmts := t.transform_match_branch_stmts(branch.stmts)
1218 if t.sumtype_return_wrap != '' {
1219 else_stmts = t.wrap_sumtype_return_branch_tail_stmts(else_stmts,
1220 t.sumtype_return_wrap)
1221 }
1222 branches << ast.MatchBranch{
1223 cond: branch.cond
1224 stmts: else_stmts
1225 pos: branch.pos
1226 }
1227 }
1228 }
1229
1230 // Transform match expression to use _tag field.
1231 // For nested sum type matches (e.g., match o { Inner { match o { Small { ... } } } }),
1232 // the expression is already smartcasted to the inner sum type. We must keep the
1233 // smartcast so we access the INNER type's _tag, not the outer one.
1234 // Only remove smartcast contexts when this is NOT a nested sum type match.
1235 mut is_nested_sumtype_match := false
1236 if ctx := t.find_smartcast_for_expr(smartcast_expr) {
1237 if t.is_sum_type(ctx.variant) || t.is_sum_type(if ctx.variant.contains('__') {
1238 ctx.variant.all_after_last('__')
1239 } else {
1240 ctx.variant
1241 }) {
1242 is_nested_sumtype_match = true
1243 }
1244 }
1245 transformed_match_expr := if !is_nested_sumtype_match && expr.expr is ast.Ident {
1246 ast.Expr(expr.expr)
1247 } else {
1248 t.transform_expr(expr.expr)
1249 }
1250 tag_access := t.synth_selector(transformed_match_expr, '_tag', types.Type(types.int_))
1251
1252 return tag_access, branches
1253 }
1254
1255 // Non-sum type match - simple transformation
1256 // Determine if match expression is an enum type so we can resolve shorthands (.red → Color__red)
1257 mut enum_type_name := ''
1258 if typ := t.get_expr_type(expr.expr) {
1259 c_name := t.type_to_c_name(typ)
1260 if c_name != '' {
1261 if resolved := t.lookup_type(c_name) {
1262 if resolved is types.Enum {
1263 enum_type_name = c_name
1264 }
1265 }
1266 }
1267 }
1268 mut branches := []ast.MatchBranch{cap: expr.branches.len}
1269 for branch in expr.branches {
1270 mut conds := branch.cond.clone()
1271 if enum_type_name != '' {
1272 conds = []ast.Expr{cap: branch.cond.len}
1273 for c in branch.cond {
1274 conds << t.resolve_enum_shorthand(c, enum_type_name)
1275 }
1276 }
1277 branches << ast.MatchBranch{
1278 cond: conds
1279 stmts: t.transform_match_branch_stmts(branch.stmts)
1280 pos: branch.pos
1281 }
1282 }
1283 return t.transform_expr(expr.expr), branches
1284}
1285
1286fn (mut t Transformer) transform_match_branch_stmts(stmts []ast.Stmt) []ast.Stmt {
1287 if stmts.len == 0 {
1288 return []ast.Stmt{}
1289 }
1290 if !t.preserve_match_branch_value {
1291 return t.transform_stmts(stmts)
1292 }
1293 last := stmts[stmts.len - 1]
1294 if last is ast.ExprStmt {
1295 mut out := []ast.Stmt{}
1296 if stmts.len > 1 {
1297 out << t.transform_stmts(stmts[..stmts.len - 1])
1298 }
1299 saved_pending := t.pending_stmts
1300 t.pending_stmts = []ast.Stmt{}
1301 saved_skip_if_value_lowering := t.skip_if_value_lowering
1302 if last.expr is ast.IfExpr {
1303 t.skip_if_value_lowering = true
1304 }
1305 mut branch_expr := last.expr
1306 if t.cur_fn_ret_type_name != '' {
1307 if ret_type := t.lookup_type(t.cur_fn_ret_type_name) {
1308 if ret_type is types.Enum {
1309 branch_expr = t.resolve_expr_with_expected_type(branch_expr, ret_type)
1310 }
1311 }
1312 }
1313 transformed_expr := t.transform_expr(branch_expr)
1314 t.skip_if_value_lowering = saved_skip_if_value_lowering
1315 final_pending := t.pending_stmts
1316 t.pending_stmts = saved_pending
1317 for stmt in final_pending {
1318 out << stmt
1319 }
1320 out << ast.Stmt(ast.ExprStmt{
1321 expr: transformed_expr
1322 })
1323 return out
1324 }
1325 return t.transform_stmts(stmts)
1326}
1327
1328fn (mut t Transformer) wrap_sumtype_return_branch_tail_stmts(stmts []ast.Stmt, sumtype_name string) []ast.Stmt {
1329 if stmts.len == 0 {
1330 return stmts
1331 }
1332 last_idx := stmts.len - 1
1333 last_stmt := stmts[last_idx]
1334 if last_stmt !is ast.ExprStmt {
1335 return stmts
1336 }
1337 mut out := stmts.clone()
1338 out[last_idx] = ast.Stmt(ast.ExprStmt{
1339 expr: t.wrap_sumtype_return_branch_tail_expr((last_stmt as ast.ExprStmt).expr,
1340 sumtype_name, stmts[..last_idx])
1341 })
1342 return out
1343}
1344
1345fn (mut t Transformer) wrap_sumtype_return_branch_tail_expr(expr ast.Expr, sumtype_name string, prefix []ast.Stmt) ast.Expr {
1346 if expr is ast.IfExpr {
1347 return ast.Expr(ast.IfExpr{
1348 cond: expr.cond
1349 stmts: t.wrap_sumtype_return_branch_tail_stmts(expr.stmts, sumtype_name)
1350 else_expr: t.wrap_sumtype_return_branch_tail_expr(expr.else_expr, sumtype_name,
1351 []ast.Stmt{})
1352 pos: expr.pos
1353 })
1354 }
1355 info := t.sumtype_wrap_info_for_name(sumtype_name) or { return expr }
1356 return t.wrap_sumtype_return_tail_leaf_expr(expr, info, prefix)
1357}
1358
1359fn (mut t Transformer) wrap_sumtype_return_tail_leaf_expr(expr ast.Expr, info ConcreteSumtypeWrapInfo, prefix []ast.Stmt) ast.Expr {
1360 if wrapped := t.wrap_sumtype_value_transformed_with_variants(expr, info.name, info.variants) {
1361 return wrapped
1362 }
1363 if expr is ast.Ident {
1364 if typ := t.lookup_var_type(expr.name) {
1365 type_name := t.type_to_c_name(typ)
1366 if type_name != '' && type_name != 'void'
1367 && !t.is_same_sumtype_name(type_name, info.name) {
1368 if variant_name := t.match_variant(type_name, info.variants) {
1369 return t.build_sumtype_init_with_variants(expr, variant_name, info.name,
1370 info.variants) or { expr }
1371 }
1372 }
1373 }
1374 if variant_name := t.sumtype_return_variant_for_tail_ident(expr.name, info, prefix) {
1375 return t.build_sumtype_init_with_variants(expr, variant_name, info.name, info.variants) or {
1376 expr
1377 }
1378 }
1379 }
1380 return expr
1381}
1382
1383fn (mut t Transformer) sumtype_return_variant_for_tail_ident(name string, info ConcreteSumtypeWrapInfo, prefix []ast.Stmt) ?string {
1384 if name == '' || prefix.len == 0 {
1385 return none
1386 }
1387 for i := prefix.len - 1; i >= 0; i-- {
1388 stmt := prefix[i]
1389 if stmt !is ast.AssignStmt {
1390 continue
1391 }
1392 assign := stmt as ast.AssignStmt
1393 if assign.lhs.len != 1 || assign.rhs.len != 1 {
1394 continue
1395 }
1396 lhs := assign.lhs[0]
1397 if lhs !is ast.Ident || (lhs as ast.Ident).name != name {
1398 continue
1399 }
1400 if typ := t.get_expr_type(lhs) {
1401 if variant_name := t.sumtype_return_variant_from_type(typ, info) {
1402 return variant_name
1403 }
1404 }
1405 if variant_name := t.sumtype_return_variant_from_expr(assign.rhs[0], info) {
1406 return variant_name
1407 }
1408 }
1409 return none
1410}
1411
1412fn (mut t Transformer) sumtype_return_variant_from_expr(expr ast.Expr, info ConcreteSumtypeWrapInfo) ?string {
1413 if typ := t.get_expr_type(expr) {
1414 if variant_name := t.sumtype_return_variant_from_type(typ, info) {
1415 return variant_name
1416 }
1417 }
1418 if expr is ast.InitExpr {
1419 init_variant_name := t.init_expr_sumtype_variant_name(expr, info.variants, info.name)
1420 if init_variant_name != '' {
1421 return init_variant_name
1422 }
1423 }
1424 if expr is ast.CastExpr {
1425 return t.sumtype_return_variant_from_type_expr(expr.typ, info)
1426 }
1427 if expr is ast.ParenExpr {
1428 return t.sumtype_return_variant_from_expr(expr.expr, info)
1429 }
1430 if expr is ast.ModifierExpr {
1431 return t.sumtype_return_variant_from_expr(expr.expr, info)
1432 }
1433 return none
1434}
1435
1436fn (mut t Transformer) sumtype_return_variant_from_type_expr(typ_expr ast.Expr, info ConcreteSumtypeWrapInfo) ?string {
1437 concrete_typ_expr := if t.cur_monomorphized_fn_bindings.len > 0 {
1438 t.substitute_type_in_expr(typ_expr, t.cur_monomorphized_fn_bindings)
1439 } else {
1440 typ_expr
1441 }
1442 if typ := t.type_from_init_expr(ast.InitExpr{
1443 typ: concrete_typ_expr
1444 })
1445 {
1446 if variant_name := t.sumtype_return_variant_from_type(typ, info) {
1447 return variant_name
1448 }
1449 }
1450 type_name := t.get_init_expr_type_name(concrete_typ_expr)
1451 if type_name == '' || t.is_same_sumtype_name(type_name, info.name) {
1452 return none
1453 }
1454 return t.match_variant(type_name, info.variants)
1455}
1456
1457fn (t &Transformer) sumtype_return_variant_from_type(typ types.Type, info ConcreteSumtypeWrapInfo) ?string {
1458 mut type_name := t.type_to_c_name(typ)
1459 if type_name == '' {
1460 type_name = typ.name()
1461 }
1462 if type_name == '' || type_name == 'void' || t.is_same_sumtype_name(type_name, info.name) {
1463 return none
1464 }
1465 return t.match_variant(type_name, info.variants)
1466}
1467
1468// lower_match_expr_to_if converts a transformed match expression into a nested IfExpr chain.
1469// Backends only need to support IfExpr after this lowering.
1470fn (mut t Transformer) lower_match_expr_to_if(match_expr ast.Expr, branches []ast.MatchBranch) ast.Expr {
1471 is_match_true := match_expr is ast.BasicLiteral && match_expr.kind == .key_true
1472 is_match_false := match_expr is ast.BasicLiteral && match_expr.kind == .key_false
1473
1474 mut current := ast.Expr(ast.empty_expr)
1475 for i := branches.len - 1; i >= 0; i-- {
1476 branch := branches[i]
1477 if branch.cond.len == 0 {
1478 current = ast.Expr(ast.IfExpr{
1479 cond: ast.empty_expr
1480 stmts: branch.stmts
1481 else_expr: current
1482 })
1483 continue
1484 }
1485
1486 branch_cond := t.build_match_branch_cond(match_expr, branch.cond, is_match_true,
1487 is_match_false)
1488 current = ast.Expr(ast.IfExpr{
1489 cond: branch_cond
1490 stmts: branch.stmts
1491 else_expr: current
1492 })
1493 }
1494 return current
1495}
1496
1497// lower_match_expr_to_if_flat is the direct-emit mirror of
1498// `lower_match_expr_to_if`. Emits the nested IfExpr chain straight into the
1499// flat builder via `emit_if_expr_by_ids`, skipping the `ast.IfExpr` wrapper-
1500// struct allocation per branch (the legacy helper allocates one IfExpr struct
1501// per branch — N-branch matches allocate N structs that the leaf `emit_expr`
1502// walk would still have to traverse). Children are already-transformed by
1503// `transform_match_expr_parts`, so leaf-encode via `out.emit_expr` /
1504// `out.emit_stmt` (mirrors `add_expr(IfExpr)`'s `push_expr`/`push_stmt`
1505// walk). The flat MatchExpr arm calls this after `_parts`.
1506fn (mut t Transformer) lower_match_expr_to_if_flat(match_expr ast.Expr, branches []ast.MatchBranch, mut out ast.FlatBuilder) ast.FlatNodeId {
1507 is_match_true := match_expr is ast.BasicLiteral && match_expr.kind == .key_true
1508 is_match_false := match_expr is ast.BasicLiteral && match_expr.kind == .key_false
1509
1510 mut current := out.emit_expr(ast.empty_expr)
1511 for i := branches.len - 1; i >= 0; i-- {
1512 branch := branches[i]
1513 mut stmt_ids := []ast.FlatNodeId{cap: branch.stmts.len}
1514 for s in branch.stmts {
1515 stmt_ids << out.emit_stmt(s)
1516 }
1517 if branch.cond.len == 0 {
1518 cond_id := out.emit_expr(ast.empty_expr)
1519 current = out.emit_if_expr_by_ids(cond_id, current, stmt_ids, token.Pos{})
1520 continue
1521 }
1522 branch_cond := t.build_match_branch_cond(match_expr, branch.cond, is_match_true,
1523 is_match_false)
1524 cond_id := out.emit_expr(branch_cond)
1525 current = out.emit_if_expr_by_ids(cond_id, current, stmt_ids, token.Pos{})
1526 }
1527 return current
1528}
1529
1530fn transformer_stmts_contain_label_stmt(stmts []ast.Stmt) bool {
1531 for stmt in stmts {
1532 if transformer_stmt_contains_label_stmt(stmt) {
1533 return true
1534 }
1535 }
1536 return false
1537}
1538
1539fn transformer_stmt_contains_label_stmt(stmt ast.Stmt) bool {
1540 if !stmt_has_valid_data(stmt) {
1541 return false
1542 }
1543 match stmt {
1544 ast.BlockStmt {
1545 return transformer_stmts_contain_label_stmt(stmt.stmts)
1546 }
1547 ast.ComptimeStmt {
1548 return transformer_stmt_contains_label_stmt(stmt.stmt)
1549 }
1550 ast.DeferStmt {
1551 return transformer_stmts_contain_label_stmt(stmt.stmts)
1552 }
1553 ast.ExprStmt {
1554 return transformer_expr_contains_label_stmt(stmt.expr)
1555 }
1556 ast.ForStmt {
1557 return transformer_stmt_contains_label_stmt(stmt.init)
1558 || transformer_stmt_contains_label_stmt(stmt.post)
1559 || transformer_expr_contains_label_stmt(stmt.cond)
1560 || transformer_stmts_contain_label_stmt(stmt.stmts)
1561 }
1562 ast.LabelStmt {
1563 return true
1564 }
1565 ast.ReturnStmt {
1566 for return_expr in stmt.exprs {
1567 if transformer_expr_contains_label_stmt(return_expr) {
1568 return true
1569 }
1570 }
1571 return false
1572 }
1573 else {
1574 return false
1575 }
1576 }
1577}
1578
1579fn transformer_expr_contains_label_stmt(expr ast.Expr) bool {
1580 if !expr_has_valid_data(expr) {
1581 return false
1582 }
1583 match expr {
1584 ast.IfExpr {
1585 return transformer_expr_contains_label_stmt(expr.cond)
1586 || transformer_stmts_contain_label_stmt(expr.stmts)
1587 || transformer_expr_contains_label_stmt(expr.else_expr)
1588 }
1589 ast.MatchExpr {
1590 for branch in expr.branches {
1591 if transformer_stmts_contain_label_stmt(branch.stmts) {
1592 return true
1593 }
1594 }
1595 return false
1596 }
1597 ast.SelectExpr {
1598 return transformer_stmts_contain_label_stmt(expr.stmts)
1599 || transformer_expr_contains_label_stmt(expr.next)
1600 }
1601 ast.UnsafeExpr {
1602 return transformer_stmts_contain_label_stmt(expr.stmts)
1603 }
1604 else {
1605 return false
1606 }
1607 }
1608}
1609
1610fn (mut t Transformer) transform_if_expr(expr ast.IfExpr) ast.Expr {
1611 has_labeled_else_branch := transformer_expr_contains_label_stmt(expr.else_expr)
1612 // Normalize long && chains containing `is` checks so smartcast lowering can
1613 // apply to patterns like `a && x is T && b`.
1614 if !has_labeled_else_branch && expr.cond is ast.InfixExpr && expr.cond.op == token.Token.and {
1615 terms := t.flatten_and_terms(expr.cond)
1616 if terms.len > 2 {
1617 mut is_idx := -1
1618 for i, term in terms {
1619 if term is ast.InfixExpr && term.op in [.key_is, .eq]
1620 && t.smartcast_context_from_is_check(term) != none {
1621 is_idx = i
1622 break
1623 }
1624 }
1625 if is_idx >= 0 {
1626 pre_terms := if is_idx > 0 { terms[..is_idx] } else { []ast.Expr{} }
1627 post_terms := if is_idx + 1 < terms.len { terms[is_idx + 1..] } else { []ast.Expr{} }
1628 mut inner_cond := terms[is_idx]
1629 if post_terms.len > 0 {
1630 post_cond := if post_terms.len == 1 {
1631 post_terms[0]
1632 } else {
1633 ast.Expr(ast.ParenExpr{
1634 expr: t.join_and_terms(post_terms)
1635 pos: post_terms[0].pos()
1636 })
1637 }
1638 inner_cond = ast.Expr(ast.InfixExpr{
1639 op: token.Token.and
1640 lhs: inner_cond
1641 rhs: post_cond
1642 })
1643 }
1644 inner_if := ast.IfExpr{
1645 cond: inner_cond
1646 stmts: expr.stmts
1647 else_expr: expr.else_expr
1648 pos: expr.pos
1649 }
1650 if pre_terms.len > 0 {
1651 outer_if := ast.IfExpr{
1652 cond: t.join_and_terms(pre_terms)
1653 stmts: [ast.Stmt(ast.ExprStmt{
1654 expr: inner_if
1655 })]
1656 else_expr: expr.else_expr
1657 pos: expr.pos
1658 }
1659 return t.transform_if_expr(outer_if)
1660 }
1661 return t.transform_if_expr(inner_if)
1662 }
1663 }
1664 }
1665
1666 // Check for compound && condition with is check: if x is Type && cond { ... }
1667 // Transform to nested ifs: if x is Type { if cond { ... } }
1668 // This allows the smart cast context to be active for the inner condition
1669 if !has_labeled_else_branch && expr.cond is ast.InfixExpr {
1670 cond := expr.cond as ast.InfixExpr
1671 if cond.op == .and {
1672 // Check if LHS is an is-check
1673 if cond.lhs is ast.InfixExpr {
1674 lhs_infix := cond.lhs as ast.InfixExpr
1675 if lhs_infix.op in [.key_is, .eq]
1676 && t.smartcast_context_from_is_check(lhs_infix) != none {
1677 // Transform: if x is Type && rest { body } else { else_body }
1678 // Handle directly to ensure else_body is transformed WITHOUT smartcast
1679 // Get variant info from lhs_infix (the is-check)
1680 mut variant_name := ''
1681 mut variant_module := ''
1682 if lhs_infix.rhs is ast.Ident {
1683 variant_name = (lhs_infix.rhs as ast.Ident).name
1684 } else if lhs_infix.rhs is ast.SelectorExpr {
1685 sel := lhs_infix.rhs as ast.SelectorExpr
1686 variant_name = sel.rhs.name
1687 if sel.lhs is ast.Ident {
1688 variant_module = (sel.lhs as ast.Ident).name
1689 }
1690 }
1691 if variant_name != '' {
1692 variant_lookup_name := sum_type_variant_name_with_module(variant_module,
1693 variant_name)
1694 mut sumtype_name := t.get_sumtype_name_for_expr(lhs_infix.lhs)
1695 if sumtype_name == '' {
1696 sumtype_name = t.find_sumtype_for_variant(variant_lookup_name)
1697 }
1698 if sumtype_name != '' {
1699 variants := t.get_sum_type_variants(sumtype_name)
1700 mut tag_value := -1
1701 for i, v in variants {
1702 if sum_type_variant_matches_for_sumtype(sumtype_name, v,
1703 variant_lookup_name)
1704 {
1705 tag_value = i
1706 break
1707 }
1708 }
1709 if tag_value >= 0 {
1710 smartcast_expr := t.expr_to_string(lhs_infix.lhs)
1711 qualified_variant := if variant_module != '' {
1712 '${variant_module}__${variant_name}'
1713 } else {
1714 variant_name
1715 }
1716 // For full variant name (type casts), always include module prefix
1717 qualified_variant_full := if variant_module != '' {
1718 '${variant_module}__${variant_name}'
1719 } else if t.cur_module != '' && t.cur_module != 'main'
1720 && t.cur_module != 'builtin' {
1721 '${t.cur_module}__${variant_name}'
1722 } else {
1723 variant_name
1724 }
1725 transformed_lhs := t.transform_tag_check_lhs_for_sumtype(lhs_infix.lhs,
1726 sumtype_name)
1727 // Push smartcast for body and inner condition
1728 smartcast_stack_before := t.smartcast_stack.clone()
1729 smartcast_counts_before := t.smartcast_expr_counts.clone()
1730 t.push_smartcast_full(smartcast_expr, qualified_variant,
1731 qualified_variant_full, sumtype_name)
1732 condition_smartcast_stack := t.smartcast_stack.clone()
1733 condition_smartcast_counts := t.smartcast_expr_counts.clone()
1734 // Check if inner condition (rest) is also an is-check
1735 mut inner_cond_lowered := false
1736 mut inner_tag_check := ast.Expr(ast.BasicLiteral{
1737 kind: token.Token.number
1738 value: '0'
1739 })
1740 inner_rhs_expr := smartcast_lhs_expr(cond.rhs)
1741 if inner_rhs_expr is ast.InfixExpr
1742 && (inner_rhs_expr as ast.InfixExpr).op in [.key_is, .eq] {
1743 orig_inner := inner_rhs_expr as ast.InfixExpr
1744 mut inner_smartcast_pushed := false
1745 if inner_ctx := t.smartcast_context_from_is_check(orig_inner) {
1746 t.push_smartcast_ctx(inner_ctx)
1747 inner_smartcast_pushed = true
1748 }
1749 mut inner_vname := ''
1750 mut inner_vmodule := ''
1751 if orig_inner.rhs is ast.Ident {
1752 inner_vname = (orig_inner.rhs as ast.Ident).name
1753 } else if orig_inner.rhs is ast.SelectorExpr {
1754 inner_sel := orig_inner.rhs as ast.SelectorExpr
1755 inner_vname = inner_sel.rhs.name
1756 if inner_sel.lhs is ast.Ident {
1757 inner_vmodule = (inner_sel.lhs as ast.Ident).name
1758 }
1759 }
1760 if inner_vname != '' {
1761 inner_lookup_name := sum_type_variant_name_with_module(inner_vmodule,
1762 inner_vname)
1763 // Get sum type from ORIGINAL expression (before smartcast)
1764 mut inner_stype :=
1765 t.get_sumtype_name_for_expr(orig_inner.lhs)
1766 if inner_stype == '' {
1767 inner_stype =
1768 t.find_sumtype_for_variant(inner_lookup_name)
1769 }
1770 if inner_stype != '' {
1771 inner_variants := t.get_sum_type_variants(inner_stype)
1772 mut inner_tag := -1
1773 for iv, iv_name in inner_variants {
1774 if sum_type_variant_matches_for_sumtype(inner_stype,
1775 iv_name, inner_lookup_name)
1776 {
1777 inner_tag = iv
1778 break
1779 }
1780 }
1781 if inner_tag >= 0 && !inner_smartcast_pushed {
1782 // Push inner smartcast for body
1783 inner_qv := if inner_vmodule != '' {
1784 '${inner_vmodule}__${inner_vname}'
1785 } else {
1786 inner_vname
1787 }
1788 inner_qvf := if inner_vmodule != '' {
1789 '${inner_vmodule}__${inner_vname}'
1790 } else if t.cur_module != ''
1791 && t.cur_module != 'main'
1792 && t.cur_module != 'builtin' {
1793 '${t.cur_module}__${inner_vname}'
1794 } else {
1795 inner_vname
1796 }
1797 inner_se := t.expr_to_string(orig_inner.lhs)
1798 t.push_smartcast_full(inner_se, inner_qv,
1799 inner_qvf, inner_stype)
1800 inner_smartcast_pushed = true
1801 }
1802 }
1803 }
1804 }
1805 active_smartcast_stack := t.smartcast_stack.clone()
1806 active_smartcast_counts := t.smartcast_expr_counts.clone()
1807 // Transform inner condition with smartcast (if not already lowered).
1808 // Expression expansions from the condition must run before the
1809 // nested if, while the outer smartcast is active.
1810 saved_rest_pending := t.pending_stmts
1811 t.pending_stmts = []ast.Stmt{}
1812 t.smartcast_stack = condition_smartcast_stack.clone()
1813 t.smartcast_expr_counts = condition_smartcast_counts.clone()
1814 transformed_rest := if inner_cond_lowered {
1815 inner_tag_check
1816 } else {
1817 t.transform_expr(cond.rhs)
1818 }
1819 rest_pending := t.pending_stmts
1820 t.pending_stmts = saved_rest_pending
1821 t.smartcast_stack = active_smartcast_stack.clone()
1822 t.smartcast_expr_counts = active_smartcast_counts.clone()
1823 mut seen_body_smartcasts := map[string]bool{}
1824 for sc in t.smartcast_stack {
1825 seen_body_smartcasts['${sc.expr}|${sc.variant}|${sc.variant_full}|${sc.sumtype}'] = true
1826 }
1827 for term in t.flatten_and_terms_unwrapped(cond.rhs) {
1828 if term is ast.InfixExpr {
1829 if body_ctx := t.smartcast_context_from_condition_term(term) {
1830 key := '${body_ctx.expr}|${body_ctx.variant}|${body_ctx.variant_full}|${body_ctx.sumtype}'
1831 if key !in seen_body_smartcasts {
1832 t.push_smartcast_ctx(body_ctx)
1833 seen_body_smartcasts[key] = true
1834 }
1835 }
1836 }
1837 }
1838 // Transform body with smartcast(s)
1839 transformed_body := t.transform_stmts(expr.stmts)
1840 // Restore the surrounding context before transforming else.
1841 t.smartcast_stack = smartcast_stack_before.clone()
1842 t.smartcast_expr_counts = smartcast_counts_before.clone()
1843 // Transform else WITHOUT smartcast
1844 transformed_else := t.transform_expr(expr.else_expr)
1845 // Build tag check
1846 tag_check := ast.InfixExpr{
1847 op: token.Token.eq
1848 lhs: t.synth_selector(transformed_lhs, '_tag',
1849 types.Type(types.int_))
1850 rhs: ast.BasicLiteral{
1851 kind: token.Token.number
1852 value: '${tag_value}'
1853 }
1854 pos: lhs_infix.pos
1855 }
1856 // Build inner if (with already-transformed components)
1857 inner_if := ast.IfExpr{
1858 cond: transformed_rest
1859 stmts: transformed_body
1860 else_expr: transformed_else
1861 pos: expr.pos
1862 }
1863 mut outer_stmts := []ast.Stmt{cap: rest_pending.len + 1}
1864 for pending_stmt in rest_pending {
1865 outer_stmts << pending_stmt
1866 }
1867 outer_stmts << ast.Stmt(ast.ExprStmt{
1868 expr: inner_if
1869 })
1870 // Build outer if
1871 return ast.IfExpr{
1872 cond: tag_check
1873 stmts: outer_stmts
1874 else_expr: transformed_else
1875 pos: expr.pos
1876 }
1877 }
1878 }
1879 }
1880 // Fallback: use smartcast_context_from_is_check to get the proper
1881 // context (interface-aware with __iface__ prefix, or plain).
1882 if iface_ctx := t.smartcast_context_from_is_check(lhs_infix) {
1883 // Transform else_expr FIRST without smartcast
1884 transformed_else_fallback := t.transform_expr(expr.else_expr)
1885 // Push interface-aware smartcast context
1886 t.push_smartcast_ctx(iface_ctx)
1887 // Transform inner condition and body with smartcast
1888 saved_rest_pending := t.pending_stmts
1889 t.pending_stmts = []ast.Stmt{}
1890 transformed_rest_fallback := t.transform_expr(cond.rhs)
1891 rest_pending := t.pending_stmts
1892 t.pending_stmts = saved_rest_pending
1893 transformed_body_fallback := t.transform_stmts(expr.stmts)
1894 // Pop smartcast
1895 t.pop_smartcast()
1896 inner_if := ast.IfExpr{
1897 cond: transformed_rest_fallback
1898 stmts: transformed_body_fallback
1899 else_expr: transformed_else_fallback
1900 pos: expr.pos
1901 }
1902 mut outer_stmts := []ast.Stmt{cap: rest_pending.len + 1}
1903 for pending_stmt in rest_pending {
1904 outer_stmts << pending_stmt
1905 }
1906 outer_stmts << ast.Stmt(ast.ExprStmt{
1907 expr: inner_if
1908 })
1909 // Keep original is-check condition (let cleanc handle it)
1910 return ast.IfExpr{
1911 cond: t.transform_expr(ast.Expr(lhs_infix))
1912 stmts: outer_stmts
1913 else_expr: transformed_else_fallback
1914 pos: expr.pos
1915 }
1916 }
1917 }
1918 }
1919 // Check if RHS is an is-check: if cond && x is Type { ... }
1920 // Transform to: if cond { if x is Type { ... } else { else_body } } else { else_body }
1921 if cond.rhs is ast.InfixExpr {
1922 rhs_infix := cond.rhs as ast.InfixExpr
1923 if rhs_infix.op in [.key_is, .eq]
1924 && t.smartcast_context_from_is_check(rhs_infix) != none {
1925 // Transform: if cond && x is Type { body } else { else_body }
1926 // To: if cond { if x is Type { body } else { else_body } } else { else_body }
1927 inner_if := ast.IfExpr{
1928 cond: ast.Expr(cond.rhs)
1929 stmts: expr.stmts
1930 else_expr: expr.else_expr
1931 pos: expr.pos
1932 }
1933 outer_if := ast.IfExpr{
1934 cond: cond.lhs
1935 stmts: [ast.Stmt(ast.ExprStmt{
1936 expr: inner_if
1937 })]
1938 else_expr: expr.else_expr
1939 pos: expr.pos
1940 }
1941 // Recursively transform - inner will handle smartcast
1942 return t.transform_if_expr(outer_if)
1943 }
1944 }
1945 }
1946 }
1947
1948 // Check for sum type smart cast: if x is Type { ... }
1949 if expr.cond is ast.InfixExpr {
1950 cond := expr.cond as ast.InfixExpr
1951 if cond.op in [.key_is, .eq] && t.smartcast_context_from_is_check(cond) != none {
1952 if ctx := t.smartcast_context_from_is_check(cond) {
1953 if tag_value := t.smartcast_context_tag_value(ctx) {
1954 transformed_lhs := t.transform_tag_check_lhs_for_sumtype(cond.lhs, ctx.sumtype)
1955
1956 t.push_smartcast_ctx(ctx)
1957 transformed_stmts := t.transform_stmts(expr.stmts)
1958 t.pop_smartcast()
1959
1960 tag_check := ast.InfixExpr{
1961 op: token.Token.eq
1962 lhs: t.synth_selector(transformed_lhs, '_tag', types.Type(types.int_))
1963 rhs: ast.BasicLiteral{
1964 kind: token.Token.number
1965 value: '${tag_value}'
1966 }
1967 pos: cond.pos
1968 }
1969 return ast.IfExpr{
1970 cond: tag_check
1971 stmts: transformed_stmts
1972 else_expr: t.transform_expr(expr.else_expr)
1973 pos: expr.pos
1974 }
1975 }
1976 }
1977 // Get the variant type name from RHS
1978 // Also extract module for qualified types like types.Type
1979 mut variant_name := ''
1980 mut variant_module := ''
1981 if cond.rhs is ast.Ident {
1982 variant_name = (cond.rhs as ast.Ident).name
1983 } else if cond.rhs is ast.SelectorExpr {
1984 // Handle module-qualified types like types.Type
1985 sel := cond.rhs as ast.SelectorExpr
1986 variant_name = sel.rhs.name
1987 // Extract module name (e.g., from types.Type, extract "types")
1988 if sel.lhs is ast.Ident {
1989 variant_module = (sel.lhs as ast.Ident).name
1990 }
1991 }
1992 if variant_name != '' {
1993 variant_lookup_name := sum_type_variant_name_with_module(variant_module,
1994 variant_name)
1995 // Get the sum type name from LHS type
1996 // First try normal lookup, then fall back to finding sumtype by variant
1997 mut sumtype_name := t.get_sumtype_name_for_expr(cond.lhs)
1998
1999 // If that failed or returned wrong sumtype, try finding by variant
2000 if sumtype_name == '' {
2001 sumtype_name = t.find_sumtype_for_variant(variant_lookup_name)
2002 }
2003
2004 // Cross-check: if the variant has an explicit module prefix (e.g. types.FnType)
2005 // but the resolved sumtype is from a different module (e.g. ast__Type),
2006 // the resolution picked the wrong homonymous sum type. Re-resolve using the
2007 // variant's module to find the correct sum type.
2008 if variant_module != '' && sumtype_name.contains('__') {
2009 st_mod := sumtype_name.all_before_last('__')
2010 if st_mod != variant_module {
2011 st_short := sumtype_name.all_after_last('__')
2012 candidate := '${variant_module}__${st_short}'
2013 if t.is_sum_type(candidate) {
2014 sumtype_name = candidate
2015 }
2016 }
2017 }
2018
2019 if sumtype_name != '' {
2020 // Find the tag value for this variant
2021 variants := t.get_sum_type_variants(sumtype_name)
2022 mut tag_value := -1
2023 for i, v in variants {
2024 if sum_type_variant_matches_for_sumtype(sumtype_name, v,
2025 variant_lookup_name)
2026 {
2027 tag_value = i
2028 break
2029 }
2030 }
2031
2032 if tag_value >= 0 {
2033 // Get the string representation of the LHS expression
2034 smartcast_expr := t.expr_to_string(cond.lhs)
2035
2036 // Create fully qualified variant name if module is specified
2037 // e.g., types.Type -> types__Type
2038 qualified_variant := if variant_module != '' {
2039 '${variant_module}__${variant_name}'
2040 } else {
2041 variant_name
2042 }
2043 // For full variant name (type casts), always include module prefix
2044 qualified_variant_full := if variant_module != '' {
2045 '${variant_module}__${variant_name}'
2046 } else if t.cur_module != '' && t.cur_module != 'main'
2047 && t.cur_module != 'builtin' {
2048 '${t.cur_module}__${variant_name}'
2049 } else {
2050 variant_name
2051 }
2052
2053 transformed_lhs := t.transform_tag_check_lhs_for_sumtype(cond.lhs,
2054 sumtype_name)
2055
2056 // Push smart cast context for transforming body (supports nested smartcasts)
2057 t.push_smartcast_full(smartcast_expr, qualified_variant,
2058 qualified_variant_full, sumtype_name)
2059
2060 // Transform body with smart cast context
2061 transformed_stmts := t.transform_stmts(expr.stmts)
2062
2063 // Pop context
2064 t.pop_smartcast()
2065
2066 // Transform condition: v is Type -> v._tag == TAG_VALUE
2067 // This prevents cleanc from also applying smart cast
2068 // Use transformed_lhs to apply outer smartcasts to the tag check
2069 tag_check := ast.InfixExpr{
2070 op: token.Token.eq
2071 lhs: t.synth_selector(transformed_lhs, '_tag', types.Type(types.int_))
2072 rhs: ast.BasicLiteral{
2073 kind: token.Token.number
2074 value: '${tag_value}'
2075 }
2076 pos: cond.pos
2077 }
2078
2079 // Transform else_expr (may have its own smart cast context)
2080 return ast.IfExpr{
2081 cond: tag_check
2082 stmts: transformed_stmts
2083 else_expr: t.transform_expr(expr.else_expr)
2084 pos: expr.pos
2085 }
2086 }
2087 }
2088 // Interface smartcast: if iface_var is ConcreteType { ... }
2089 // The condition is left as-is (cleanc handles _type_id comparison).
2090 // We only need to push the smartcast context so field accesses in
2091 // the body are lowered to ((ConcreteType*)(iface._object))->field.
2092 if iface_ctx := t.smartcast_context_from_is_check(cond) {
2093 t.push_smartcast_ctx(iface_ctx)
2094 transformed_stmts := t.transform_stmts(expr.stmts)
2095 t.pop_smartcast()
2096 return ast.IfExpr{
2097 cond: t.transform_expr(expr.cond)
2098 stmts: transformed_stmts
2099 else_expr: t.transform_expr(expr.else_expr)
2100 pos: expr.pos
2101 }
2102 }
2103 }
2104 }
2105 }
2106
2107 // Handle if-guard expression in condition (for nested/expression-level if-guards)
2108 // For Option-returning expressions in if-guards, we need to expand them with temp variables.
2109 // Transform: if r := opt_func() { body } else { else_body }
2110 // To: { _tmp := opt_func(); if _tmp.state == 0 { r := _tmp.data; body } else { else_body } }
2111 if expr.cond is ast.IfGuardExpr {
2112 guard := expr.cond as ast.IfGuardExpr
2113 if guard.stmt.rhs.len > 0 {
2114 synth_pos := t.next_synth_pos()
2115 rhs := guard.stmt.rhs[0]
2116
2117 // Check if RHS returns Result or Option type
2118 mut is_result := t.expr_returns_result(rhs)
2119 mut is_option := t.expr_returns_option(rhs)
2120 if !is_result && !is_option {
2121 fn_name := t.get_call_fn_name(rhs)
2122 is_result = fn_name != '' && t.fn_returns_result(fn_name)
2123 is_option = fn_name != '' && t.fn_returns_option(fn_name)
2124 }
2125 if !is_result && !is_option {
2126 if ret_type := t.fn_pointer_call_return_type(rhs) {
2127 is_result = ret_type is types.ResultType || ret_type.name().starts_with('!')
2128 is_option = ret_type is types.OptionType || ret_type.name().starts_with('?')
2129 }
2130 }
2131 if is_result {
2132 // Handle Result if-guard using temp variable pattern
2133 // Transform: if var := result_call() { body } else { else_body }
2134 // To: { _tmp := result_call(); if !_tmp.is_error { var := _tmp.data; body } else { else_body } }
2135 temp_name := t.gen_temp_name()
2136 temp_pos := synth_pos
2137 if_pos := t.next_synth_pos()
2138 temp_ident := ast.Ident{
2139 name: temp_name
2140 pos: temp_pos
2141 }
2142
2143 mut is_blank := false
2144 if guard.stmt.lhs.len == 1 {
2145 lhs0 := guard.stmt.lhs[0]
2146 if lhs0 is ast.Ident {
2147 if lhs0.name == '_' {
2148 is_blank = true
2149 }
2150 }
2151 }
2152 mut data_type := types.Type(types.voidptr_)
2153 if wrapper_type := t.expr_wrapper_type_for_or(rhs) {
2154 t.register_temp_var(temp_name, wrapper_type)
2155 t.register_synth_type(temp_pos, wrapper_type)
2156 if wrapper_type is types.ResultType {
2157 data_type = wrapper_type.base_type
2158 }
2159 } else if wrapper_type := t.get_expr_type(rhs) {
2160 t.register_temp_var(temp_name, wrapper_type)
2161 t.register_synth_type(temp_pos, wrapper_type)
2162 if wrapper_type is types.ResultType {
2163 data_type = wrapper_type.base_type
2164 }
2165 } else if wrapper_type := t.fn_pointer_call_return_type(rhs) {
2166 t.register_temp_var(temp_name, wrapper_type)
2167 t.register_synth_type(temp_pos, wrapper_type)
2168 if wrapper_type is types.ResultType {
2169 data_type = wrapper_type.base_type
2170 }
2171 }
2172
2173 // 1. _tmp := result_call()
2174 temp_assign := ast.AssignStmt{
2175 op: .decl_assign
2176 lhs: [ast.Expr(temp_ident)]
2177 rhs: [t.transform_expr(rhs)]
2178 pos: temp_pos
2179 }
2180
2181 // 2. Condition: !_tmp.is_error
2182 success_cond := ast.PrefixExpr{
2183 op: .not
2184 expr: t.synth_selector(temp_ident, 'is_error', types.Type(types.bool_))
2185 }
2186
2187 // 3. Body: var := _tmp.data; original_body
2188 mut body_stmts := []ast.Stmt{}
2189 if !is_blank {
2190 data_access := t.synth_selector(temp_ident, 'data', data_type)
2191 t.register_if_guard_lhs_payload_type(guard.stmt.lhs, data_type)
2192 body_stmts << ast.AssignStmt{
2193 op: .decl_assign
2194 lhs: guard.stmt.lhs
2195 rhs: [data_access]
2196 pos: guard.stmt.pos
2197 }
2198 }
2199 for s in expr.stmts {
2200 body_stmts << s
2201 }
2202
2203 modified_if := ast.IfExpr{
2204 cond: success_cond
2205 stmts: t.transform_stmts(body_stmts)
2206 else_expr: t.transform_expr(expr.else_expr)
2207 pos: if_pos
2208 }
2209 // Propagate the original IfExpr type to the synthesized node
2210 if orig_type := t.get_expr_type(ast.Expr(expr)) {
2211 t.register_synth_type(if_pos, orig_type)
2212 }
2213
2214 // Wrap temp assignment + if in UnsafeExpr (compound expression)
2215 return ast.UnsafeExpr{
2216 stmts: [ast.Stmt(temp_assign), ast.ExprStmt{
2217 expr: modified_if
2218 }]
2219 }
2220 }
2221
2222 if is_option {
2223 // Handle Option if-guard using a temp variable to avoid taking
2224 // addresses of rvalue wrappers when extracting `.data`.
2225 // Transform: if var := opt_call() { body } else { else_body }
2226 // To: { _tmp := opt_call(); if _tmp.state == 0 { var := _tmp.data; body } else { else_body } }
2227 temp_name := t.gen_temp_name()
2228 temp_pos := synth_pos
2229 if_pos := t.next_synth_pos()
2230 temp_ident := ast.Ident{
2231 name: temp_name
2232 pos: temp_pos
2233 }
2234 mut data_type := types.Type(types.voidptr_)
2235 if wrapper_type := t.expr_wrapper_type_for_or(rhs) {
2236 t.register_temp_var(temp_name, wrapper_type)
2237 t.register_synth_type(temp_pos, wrapper_type)
2238 if wrapper_type is types.OptionType {
2239 data_type = wrapper_type.base_type
2240 }
2241 } else if wrapper_type := t.get_expr_type(rhs) {
2242 t.register_temp_var(temp_name, wrapper_type)
2243 t.register_synth_type(temp_pos, wrapper_type)
2244 if wrapper_type is types.OptionType {
2245 data_type = wrapper_type.base_type
2246 }
2247 } else if wrapper_type := t.fn_pointer_call_return_type(rhs) {
2248 t.register_temp_var(temp_name, wrapper_type)
2249 t.register_synth_type(temp_pos, wrapper_type)
2250 if wrapper_type is types.OptionType {
2251 data_type = wrapper_type.base_type
2252 }
2253 }
2254 mut is_blank := false
2255 if guard.stmt.lhs.len == 1 {
2256 lhs0 := guard.stmt.lhs[0]
2257 if lhs0 is ast.Ident {
2258 if lhs0.name == '_' {
2259 is_blank = true
2260 }
2261 }
2262 }
2263
2264 temp_assign := ast.AssignStmt{
2265 op: .decl_assign
2266 lhs: [ast.Expr(temp_ident)]
2267 rhs: [t.transform_expr(rhs)]
2268 pos: temp_pos
2269 }
2270
2271 opt_success_cond := ast.InfixExpr{
2272 op: .eq
2273 lhs: t.synth_selector(temp_ident, 'state', types.Type(types.int_))
2274 rhs: ast.BasicLiteral{
2275 kind: .number
2276 value: '0'
2277 }
2278 }
2279
2280 mut body_stmts := []ast.Stmt{}
2281 if !is_blank {
2282 data_access := t.synth_selector(temp_ident, 'data', data_type)
2283 t.register_if_guard_lhs_payload_type(guard.stmt.lhs, data_type)
2284 body_stmts << ast.AssignStmt{
2285 op: .decl_assign
2286 lhs: guard.stmt.lhs
2287 rhs: [data_access]
2288 pos: guard.stmt.pos
2289 }
2290 }
2291 for s in expr.stmts {
2292 body_stmts << s
2293 }
2294
2295 modified_if := ast.IfExpr{
2296 cond: opt_success_cond
2297 stmts: t.transform_stmts(body_stmts)
2298 else_expr: t.transform_expr(expr.else_expr)
2299 pos: if_pos
2300 }
2301 // Propagate the original IfExpr type to the synthesized node
2302 if orig_type := t.get_expr_type(ast.Expr(expr)) {
2303 t.register_synth_type(if_pos, orig_type)
2304 }
2305
2306 return ast.UnsafeExpr{
2307 stmts: [ast.Stmt(temp_assign), ast.ExprStmt{
2308 expr: modified_if
2309 }]
2310 }
2311 }
2312
2313 // Non-option case: use simple transformation
2314 // For map if-guards: if r := map[key] { body } else { else_body }
2315 // Transform to: if (key in map) { r := map[key]; body } else { else_body }
2316 // For other cases: if (rhs) { r := rhs; body } else { else_body }
2317 mut is_blank := false
2318 if guard.stmt.lhs.len == 1 {
2319 lhs0 := guard.stmt.lhs[0]
2320 if lhs0 is ast.Ident {
2321 if lhs0.name == '_' {
2322 is_blank = true
2323 }
2324 }
2325 }
2326
2327 // Check if RHS is a map or array index expression
2328 mut cond_expr := ast.Expr(t.transform_expr(rhs))
2329 if rhs is ast.IndexExpr {
2330 // Try to see if this is a map index
2331 if _ := t.get_map_type_for_expr(rhs.lhs) {
2332 // This is a map access - generate "key in map" check
2333 cond_expr = ast.Expr(ast.InfixExpr{
2334 op: .key_in
2335 lhs: rhs.expr // the key expression
2336 rhs: rhs.lhs // the map expression
2337 pos: rhs.pos
2338 })
2339 } else {
2340 // This is an array access - generate bounds check: index < array.len
2341 cond_expr = ast.Expr(ast.InfixExpr{
2342 op: .lt
2343 lhs: t.transform_expr(rhs.expr) // the index expression
2344 rhs: t.synth_selector(t.transform_expr(rhs.lhs), 'len',
2345 types.Type(types.int_))
2346 pos: rhs.pos
2347 })
2348 }
2349 }
2350
2351 mut new_stmts := []ast.Stmt{cap: expr.stmts.len + 1}
2352 if !is_blank {
2353 guard_assign := ast.AssignStmt{
2354 op: .decl_assign
2355 lhs: guard.stmt.lhs
2356 rhs: guard.stmt.rhs
2357 pos: guard.stmt.pos
2358 }
2359 new_stmts << guard_assign
2360 }
2361 for s in expr.stmts {
2362 new_stmts << s
2363 }
2364 saved_p := t.pending_stmts
2365 t.pending_stmts = []ast.Stmt{}
2366 t_stmts := t.transform_stmts(new_stmts)
2367 inner_p := t.pending_stmts
2368 t.pending_stmts = saved_p
2369 for ip in inner_p {
2370 t.pending_stmts << ip
2371 }
2372 saved_p2 := t.pending_stmts
2373 t.pending_stmts = []ast.Stmt{}
2374 t_else := t.transform_expr(expr.else_expr)
2375 inner_p2 := t.pending_stmts
2376 t.pending_stmts = saved_p2
2377 for ip in inner_p2 {
2378 t.pending_stmts << ip
2379 }
2380 result_if := ast.IfExpr{
2381 cond: t.transform_expr(cond_expr)
2382 stmts: t_stmts
2383 else_expr: t_else
2384 pos: synth_pos
2385 }
2386 // Propagate the original IfExpr type to the synthesized node
2387 if orig_type := t.get_expr_type(ast.Expr(expr)) {
2388 t.register_synth_type(synth_pos, orig_type)
2389 }
2390 return result_if
2391 }
2392 }
2393
2394 // Default transformation
2395 mut body_smartcasts := []SmartcastContext{}
2396 mut seen_smartcasts := map[string]bool{}
2397 for term in t.flatten_and_terms_unwrapped(expr.cond) {
2398 if term is ast.InfixExpr {
2399 if ctx := t.smartcast_context_from_condition_term(term) {
2400 key := '${ctx.expr}|${ctx.variant}|${ctx.variant_full}'
2401 if key !in seen_smartcasts {
2402 seen_smartcasts[key] = true
2403 body_smartcasts << ctx
2404 }
2405 }
2406 }
2407 }
2408 outer_smartcast_stack := t.smartcast_stack.clone()
2409 outer_smartcast_counts := t.smartcast_expr_counts.clone()
2410 for ctx in body_smartcasts {
2411 t.push_smartcast_full(ctx.expr, ctx.variant, ctx.variant_full, ctx.sumtype)
2412 }
2413 // Save and restore pending_stmts around branch transformation to prevent
2414 // outer pending_stmts (e.g., from earlier RHS if-expr lowering) from being
2415 // flushed into the inner branch's transform_stmts.
2416 saved_pending := t.pending_stmts
2417 t.pending_stmts = []ast.Stmt{}
2418 transformed_stmts := t.transform_stmts(expr.stmts)
2419 // Merge any pending_stmts generated by the then-branch transform back into the outer context.
2420 inner_pending_then := t.pending_stmts
2421 t.pending_stmts = saved_pending
2422 for ip in inner_pending_then {
2423 t.pending_stmts << ip
2424 }
2425 t.smartcast_stack = outer_smartcast_stack.clone()
2426 t.smartcast_expr_counts = outer_smartcast_counts.clone()
2427 saved_pending2 := t.pending_stmts
2428 t.pending_stmts = []ast.Stmt{}
2429 transformed_else := t.transform_expr(expr.else_expr)
2430 // Merge any pending_stmts generated by the else-expr transform back into the outer context.
2431 inner_pending := t.pending_stmts
2432 t.pending_stmts = saved_pending2
2433 for ip in inner_pending {
2434 t.pending_stmts << ip
2435 }
2436 t.smartcast_stack = outer_smartcast_stack.clone()
2437 t.smartcast_expr_counts = outer_smartcast_counts.clone()
2438 transformed_if := ast.IfExpr{
2439 cond: t.transform_expr(expr.cond)
2440 stmts: transformed_stmts
2441 else_expr: transformed_else
2442 pos: expr.pos
2443 }
2444 t.smartcast_stack = outer_smartcast_stack.clone()
2445 t.smartcast_expr_counts = outer_smartcast_counts.clone()
2446 // Lower value-position IfExpr: hoist to a temp variable assignment.
2447 // This eliminates expression-valued IfExpr so backends don't need statement-expressions.
2448 // A value-position if has an else branch and its body ends with an ExprStmt (producing a value).
2449 // Skip lowering when:
2450 // - The IfExpr is directly inside an ExprStmt (statement position, not value)
2451 // - The IfExpr is already the RHS of a decl_assign (cleanc handles this efficiently)
2452 if !t.skip_if_value_lowering && transformed_if.else_expr !is ast.EmptyExpr
2453 && t.if_expr_is_value(transformed_if) {
2454 return t.lower_if_expr_value(transformed_if)
2455 }
2456 return transformed_if
2457}
2458
2459// get_sumtype_name_for_expr returns the sum type name for an expression, or empty string if not a sum type
2460// This function is smartcast-aware: if the expression is already smartcasted to a variant that is
2461// itself a sum type, it returns that sum type name.
2462fn (mut t Transformer) transform_infix_expr(expr ast.InfixExpr) ast.Expr {
2463 // Preserve short-circuit semantics while making smartcasts available to all
2464 // following terms in `&&` chains (including multiple `is` checks).
2465 if expr.op == .and {
2466 terms := t.flatten_and_terms_unwrapped(expr)
2467 if terms.len > 1 {
2468 mut transformed_terms := []ast.Expr{cap: terms.len}
2469 mut pushed := 0
2470 mut changed := false
2471 for term in terms {
2472 if term is ast.InfixExpr {
2473 if ctx := t.smartcast_context_from_condition_term(term) {
2474 transformed_terms << t.transform_expr(term)
2475 t.push_smartcast_full(ctx.expr, ctx.variant, ctx.variant_full, ctx.sumtype)
2476 pushed++
2477 changed = true
2478 continue
2479 }
2480 }
2481 transformed_terms << t.transform_expr(term)
2482 }
2483 for _ in 0 .. pushed {
2484 t.pop_smartcast()
2485 }
2486 if changed {
2487 return t.join_and_terms(transformed_terms)
2488 }
2489 }
2490 }
2491
2492 // Lower sum type checks/comparisons to tag comparisons.
2493 // This also handles parser/checker-lowered `!is` that appears as `!=` with type RHS.
2494 if expr.op in [.key_is, .not_is, .eq, .ne] {
2495 mut variant_name := ''
2496 mut variant_module := ''
2497 if expr.rhs is ast.Ident {
2498 variant_name = (expr.rhs as ast.Ident).name
2499 } else if expr.rhs is ast.SelectorExpr {
2500 sel := expr.rhs as ast.SelectorExpr
2501 variant_name = sel.rhs.name
2502 if sel.lhs is ast.Ident {
2503 variant_module = (sel.lhs as ast.Ident).name
2504 }
2505 } else if expr.rhs is ast.Type {
2506 // Handle complex type expressions like []Any, map[string]Any
2507 variant_name = t.type_expr_to_variant_name(expr.rhs)
2508 }
2509 if variant_name != '' {
2510 variant_lookup_name := sum_type_variant_name_with_module(variant_module, variant_name)
2511 mut sumtype_name := t.get_sumtype_name_for_expr(expr.lhs)
2512 mut lhs_is_enum := t.get_enum_type_name(expr.lhs) != ''
2513 if lhs_type := t.get_expr_type(expr.lhs) {
2514 lhs_base := t.unwrap_alias_and_pointer_type(lhs_type)
2515 lhs_is_enum = lhs_is_enum || lhs_base is types.Enum
2516 }
2517 rhs_is_enum_shorthand := expr.rhs is ast.SelectorExpr
2518 && (expr.rhs as ast.SelectorExpr).lhs is ast.EmptyExpr
2519 rhs_is_type_like := variant_name.len > 0 && variant_name[0] >= `A`
2520 && variant_name[0] <= `Z`
2521 can_find_sumtype_fallback := expr.op in [.key_is, .not_is] || rhs_is_type_like
2522 if sumtype_name == '' && !lhs_is_enum && !rhs_is_enum_shorthand
2523 && can_find_sumtype_fallback {
2524 sumtype_name = t.find_sumtype_for_variant(variant_lookup_name)
2525 }
2526 if sumtype_name != '' {
2527 variants := t.get_sum_type_variants(sumtype_name)
2528 mut tag_value := -1
2529 for i, v in variants {
2530 if sum_type_variant_matches_for_sumtype(sumtype_name, v, variant_lookup_name) {
2531 tag_value = i
2532 break
2533 }
2534 }
2535 if tag_value >= 0 {
2536 transformed_lhs := t.transform_tag_check_lhs_for_sumtype(expr.lhs, sumtype_name)
2537 cmp_op := if expr.op in [.key_is, .eq] {
2538 token.Token.eq
2539 } else {
2540 token.Token.ne
2541 }
2542 return t.make_infix_expr_at(cmp_op, t.synth_selector(transformed_lhs, '_tag',
2543 types.Type(types.int_)), ast.Expr(ast.BasicLiteral{
2544 kind: token.Token.number
2545 value: '${tag_value}'
2546 pos: expr.pos
2547 }), expr.pos)
2548 }
2549 }
2550 // Interface self-type check: `iface_var is InterfaceType` where the variable
2551 // is already of that interface type. This checks non-nil (type_id != 0).
2552 if sumtype_name == '' {
2553 if lhs_type := t.get_expr_type(expr.lhs) {
2554 lhs_base := t.unwrap_alias_and_pointer_type(lhs_type)
2555 if lhs_base is types.Interface {
2556 is_self_check := lhs_base.name.ends_with(variant_name)
2557 || lhs_base.name == variant_name
2558 if is_self_check {
2559 transformed_lhs := t.transform_expr(expr.lhs)
2560 cmp_op := if expr.op in [.key_is, .eq] {
2561 token.Token.ne
2562 } else {
2563 token.Token.eq
2564 }
2565 return t.make_infix_expr_at(cmp_op, t.synth_selector(transformed_lhs,
2566 '_type_id', types.Type(types.int_)), ast.Expr(ast.BasicLiteral{
2567 kind: token.Token.number
2568 value: '0'
2569 pos: expr.pos
2570 }), expr.pos)
2571 }
2572 }
2573 }
2574 }
2575 }
2576 }
2577
2578 // Check for string concatenation: string + string
2579 if expr.op == .plus {
2580 if literal := folded_string_literal_concat(expr) {
2581 return ast.Expr(ast.StringLiteral{
2582 kind: .v
2583 value: literal
2584 pos: expr.pos
2585 })
2586 }
2587 lhs_is_str := t.is_string_expr(expr.lhs)
2588 rhs_is_str := t.is_string_expr(expr.rhs)
2589
2590 // Check if either side is a string literal
2591 lhs_is_str_literal := if expr.lhs is ast.StringLiteral {
2592 true
2593 } else if expr.lhs is ast.BasicLiteral {
2594 expr.lhs.kind == .string
2595 } else {
2596 false
2597 }
2598 rhs_is_str_literal := if expr.rhs is ast.StringLiteral {
2599 true
2600 } else if expr.rhs is ast.BasicLiteral {
2601 expr.rhs.kind == .string
2602 } else {
2603 false
2604 }
2605
2606 // Also check if either side is a string__* call (already transformed)
2607 lhs_is_str_call := if expr.lhs is ast.CallExpr {
2608 if expr.lhs.lhs is ast.Ident {
2609 (expr.lhs.lhs as ast.Ident).name.starts_with('string__')
2610 } else {
2611 false
2612 }
2613 } else {
2614 false
2615 }
2616 rhs_is_str_call := if expr.rhs is ast.CallExpr {
2617 if expr.rhs.lhs is ast.Ident {
2618 (expr.rhs.lhs as ast.Ident).name.starts_with('string__')
2619 } else {
2620 false
2621 }
2622 } else {
2623 false
2624 }
2625
2626 // Also check for string InfixExpr (chained concatenation like s1 + s2 + s3)
2627 lhs_is_str_infix := expr.lhs is ast.InfixExpr
2628 && (expr.lhs as ast.InfixExpr).op == .plus && lhs_is_str
2629 rhs_is_str_infix := expr.rhs is ast.InfixExpr
2630 && (expr.rhs as ast.InfixExpr).op == .plus && rhs_is_str
2631 // Determine if this is a string concatenation using multiple signals
2632 should_transform := (lhs_is_str && rhs_is_str) || (lhs_is_str_literal && (rhs_is_str || expr.rhs is ast.Ident || rhs_is_str_call)) || (rhs_is_str_literal && (lhs_is_str || expr.lhs is ast.Ident || lhs_is_str_call)) || (lhs_is_str_call && (rhs_is_str || expr.rhs is ast.Ident)) || (rhs_is_str_call && (lhs_is_str || expr.lhs is ast.Ident)) || (lhs_is_str_infix && expr.rhs is ast.Ident) // Chained: (s1 + s2) + ident
2633 || (rhs_is_str_infix && expr.lhs is ast.Ident) // Chained: ident + (s1 + s2)
2634
2635 // Check for chained concatenation: (s1 + s2) + s3 -> string__plus_two(s1, s2, s3)
2636 if expr.lhs is ast.InfixExpr && should_transform {
2637 lhs_infix := expr.lhs as ast.InfixExpr
2638 if lhs_infix.op == .plus && t.is_string_expr(lhs_infix.lhs)
2639 && t.is_string_expr(lhs_infix.rhs) {
2640 // Transform to string__plus_two(s1, s2, s3)
2641 return ast.CallExpr{
2642 lhs: ast.Ident{
2643 name: 'string__plus_two'
2644 }
2645 args: [
2646 t.transform_expr(lhs_infix.lhs),
2647 t.transform_expr(lhs_infix.rhs),
2648 t.transform_expr(expr.rhs),
2649 ]
2650 pos: expr.pos
2651 }
2652 }
2653 }
2654 // Check for simple concatenation: s1 + s2 -> string__plus(s1, s2)
2655 if should_transform {
2656 return ast.CallExpr{
2657 lhs: ast.Ident{
2658 name: 'string__plus'
2659 }
2660 args: [t.transform_expr(expr.lhs), t.transform_expr(expr.rhs)]
2661 pos: expr.pos
2662 }
2663 }
2664 }
2665 // Check for 'in' operator with arrays: elem in arr => Array_T_contains(arr, elem)
2666 if expr.op in [.key_in, .not_in] {
2667 // Range membership: x in a..b -> x >= a && x < b
2668 if expr.rhs is ast.RangeExpr {
2669 range := expr.rhs as ast.RangeExpr
2670 lhs_trans := t.transform_expr(expr.lhs)
2671 lower_bound := ast.Expr(ast.InfixExpr{
2672 op: .ge
2673 lhs: lhs_trans
2674 rhs: t.transform_expr(range.start)
2675 pos: expr.pos
2676 })
2677 mut range_check := lower_bound
2678 if range.end !is ast.EmptyExpr {
2679 upper_op := if range.op == .dotdot {
2680 token.Token.lt
2681 } else {
2682 token.Token.le
2683 }
2684 upper_bound := ast.Expr(ast.InfixExpr{
2685 op: upper_op
2686 lhs: lhs_trans
2687 rhs: t.transform_expr(range.end)
2688 pos: expr.pos
2689 })
2690 range_check = ast.Expr(ast.InfixExpr{
2691 op: .and
2692 lhs: lower_bound
2693 rhs: upper_bound
2694 pos: expr.pos
2695 })
2696 }
2697 if expr.op == .not_in {
2698 return ast.PrefixExpr{
2699 op: .not
2700 expr: range_check
2701 pos: expr.pos
2702 }
2703 }
2704 return range_check
2705 }
2706 // Map membership: key in map -> map__exists(&map, &key)
2707 if rhs_type := t.map_index_lhs_type(expr.rhs) {
2708 if map_typ := t.unwrap_map_type(rhs_type) {
2709 if t.is_eval_backend() {
2710 return ast.InfixExpr{
2711 op: expr.op
2712 lhs: t.transform_expr(expr.lhs)
2713 rhs: t.transform_expr(expr.rhs)
2714 pos: expr.pos
2715 }
2716 }
2717 map_ptr := t.map_expr_to_runtime_ptr(expr.rhs, rhs_type, map_typ)
2718 // For the key, declare a temp variable in the same scope as the
2719 // map__exists call so the pointer stays valid during the call.
2720 // Using addr_of_expr_with_temp would nest the temp inside a
2721 // statement expression whose scope ends before map__exists reads it.
2722 lhs_trans := t.transform_expr(expr.lhs)
2723 mut key_ptr := ast.Expr(ast.empty_expr)
2724 mut key_stmts := []ast.Stmt{}
2725 if t.can_take_address_expr(lhs_trans)
2726 && !t.is_enum_rvalue(lhs_trans, map_typ.key_type) {
2727 key_ptr = ast.PrefixExpr{
2728 op: .amp
2729 expr: lhs_trans
2730 }
2731 } else {
2732 key_tmp := t.gen_temp_name()
2733 key_ident := t.typed_temp_ident(key_tmp, map_typ.key_type)
2734 key_stmts << ast.Stmt(ast.AssignStmt{
2735 op: .decl_assign
2736 lhs: [ast.Expr(key_ident)]
2737 rhs: [lhs_trans]
2738 pos: key_ident.pos
2739 })
2740 key_ptr = ast.PrefixExpr{
2741 op: .amp
2742 expr: key_ident
2743 }
2744 }
2745 exists_call := ast.Expr(ast.CallExpr{
2746 lhs: ast.Ident{
2747 name: 'map__exists'
2748 }
2749 args: [map_ptr, key_ptr]
2750 pos: expr.pos
2751 })
2752 mut result_expr := exists_call
2753 if expr.op == .not_in {
2754 result_expr = ast.PrefixExpr{
2755 op: .not
2756 expr: exists_call
2757 pos: expr.pos
2758 }
2759 }
2760 if key_stmts.len > 0 {
2761 // Wrap in UnsafeExpr so the temp key variable is in scope
2762 // for the entire map__exists call.
2763 key_stmts << ast.Stmt(ast.ExprStmt{
2764 expr: result_expr
2765 })
2766 return ast.UnsafeExpr{
2767 stmts: key_stmts
2768 }
2769 }
2770 return result_expr
2771 }
2772 }
2773 // For inline array literals, expand to a chain of equality checks
2774 // instead of a contains() call — simpler and works for all backends.
2775 if expr.rhs is ast.ArrayInitExpr {
2776 arr := expr.rhs as ast.ArrayInitExpr
2777 if arr.exprs.len > 0 {
2778 lhs_trans := t.transform_expr(expr.lhs)
2779 // Get enum type from LHS for resolving shorthand in array elements
2780 enum_type := t.get_enum_type_name(expr.lhs)
2781 // Build: lhs == arr[0] || lhs == arr[1] || ...
2782 first_elem := if enum_type != '' {
2783 t.resolve_enum_shorthand(t.transform_expr(arr.exprs[0]), enum_type)
2784 } else {
2785 t.transform_expr(arr.exprs[0])
2786 }
2787 mut chain := t.make_infix_expr_at(.eq, lhs_trans, first_elem, expr.pos)
2788 for i := 1; i < arr.exprs.len; i++ {
2789 elem := if enum_type != '' {
2790 t.resolve_enum_shorthand(t.transform_expr(arr.exprs[i]), enum_type)
2791 } else {
2792 t.transform_expr(arr.exprs[i])
2793 }
2794 elem_cmp := t.make_infix_expr_at(.eq, lhs_trans, elem, expr.pos)
2795 chain = t.make_infix_expr_at(.logical_or, chain, elem_cmp, expr.pos)
2796 }
2797 if expr.op == .not_in {
2798 return ast.PrefixExpr{
2799 op: .not
2800 expr: chain
2801 pos: expr.pos
2802 }
2803 }
2804 return chain
2805 }
2806 }
2807 // Array membership: elem in arr -> Array_T_contains(arr, elem)
2808 if arr_info := t.get_array_method_info(expr.rhs) {
2809 // Get enum type from LHS for resolving shorthand in array
2810 enum_type := t.get_enum_type_name(expr.lhs)
2811 // If array contains enum shorthand but we can't resolve the type,
2812 // skip transformation and let cleanc handle it
2813 mut has_unresolved_shorthand := false
2814 if enum_type == '' && expr.rhs is ast.ArrayInitExpr {
2815 arr := expr.rhs as ast.ArrayInitExpr
2816 if arr.exprs.len > 0 {
2817 first := arr.exprs[0]
2818 // Check for enum shorthand (.value with empty LHS)
2819 if first is ast.SelectorExpr {
2820 sel := first as ast.SelectorExpr
2821 // Check for enum shorthand: EmptyExpr or empty Ident as LHS
2822 is_shorthand := if sel.lhs is ast.EmptyExpr {
2823 true
2824 } else if sel.lhs is ast.Ident {
2825 // Also check for empty ident name
2826 (sel.lhs as ast.Ident).name == ''
2827 } else {
2828 false
2829 }
2830 if is_shorthand {
2831 has_unresolved_shorthand = true
2832 }
2833 }
2834 }
2835 }
2836 // Check if this is a sum type variant check: sumtype_var in [TypeA, TypeB]
2837 // If LHS is a sum type and RHS contains variant type names, let cleanc handle it
2838 mut is_sumtype_variant_check := false
2839 if expr.rhs is ast.ArrayInitExpr {
2840 // Get LHS sum type name (if it is a sum type)
2841 lhs_sumtype := t.get_sumtype_name_for_expr(expr.lhs)
2842 if lhs_sumtype != '' {
2843 // Check if the array elements are variant type names (Idents)
2844 arr := expr.rhs as ast.ArrayInitExpr
2845 if arr.exprs.len > 0 && arr.exprs[0] is ast.Ident {
2846 // This looks like a sum type variant check
2847 // Verify that the ident names are actually variants
2848 variants := t.get_sum_type_variants(lhs_sumtype)
2849 first_ident := arr.exprs[0] as ast.Ident
2850 // Extract module prefix from sum type name (e.g., "ast__Expr" -> "ast__")
2851 mod_prefix := if lhs_sumtype.contains('__') {
2852 lhs_sumtype.all_before_last('__') + '__'
2853 } else {
2854 ''
2855 }
2856 // Check both with and without module prefix
2857 variant_name := first_ident.name
2858 variant_mangled := mod_prefix + variant_name
2859 if variant_name in variants || variant_mangled in variants {
2860 is_sumtype_variant_check = true
2861 }
2862 }
2863 }
2864 }
2865 // If this is a sum type variant check, skip transformation entirely
2866 // and return the original expression (cleanc will handle tag checks)
2867 if is_sumtype_variant_check {
2868 // Return unchanged - cleanc will generate the tag check
2869 return t.make_infix_expr_at(expr.op, t.transform_expr(expr.lhs), expr.rhs, expr.pos)
2870 }
2871 if !has_unresolved_shorthand {
2872 mut method_info := arr_info
2873 if enum_type != '' && !arr_info.is_fixed {
2874 enum_c_name := t.v_type_name_to_c_name(enum_type)
2875 if enum_c_name != '' {
2876 method_info = ArrayMethodInfo{
2877 array_type: 'Array_${enum_c_name}'
2878 elem_type: enum_c_name
2879 is_fixed: false
2880 }
2881 }
2882 }
2883 contains_fn_name := t.register_needed_array_method(method_info, 'contains')
2884 // Transform array with enum context if needed
2885 transformed_rhs := if enum_type != '' && expr.rhs is ast.ArrayInitExpr {
2886 t.transform_array_with_enum_context(expr.rhs as ast.ArrayInitExpr, enum_type)
2887 } else {
2888 t.transform_expr(expr.rhs)
2889 }
2890 contains_call := ast.CallExpr{
2891 lhs: ast.Ident{
2892 name: contains_fn_name
2893 }
2894 args: [transformed_rhs, t.transform_expr(expr.lhs)]
2895 pos: expr.pos
2896 }
2897 if expr.op == .not_in {
2898 // !in => !Array_T_contains(arr, elem)
2899 return ast.PrefixExpr{
2900 op: .not
2901 expr: contains_call
2902 pos: expr.pos
2903 }
2904 }
2905 return contains_call
2906 } else {
2907 // Unresolved shorthand - return as-is for cleanc to handle expansion
2908 return t.make_infix_expr_at(expr.op, t.transform_expr(expr.lhs), expr.rhs, expr.pos)
2909 }
2910 }
2911 }
2912 // Check for array append: arr << elem => builtin__array_push_noscan((array*)&arr, _MOV((T[]){ elem }))
2913 // If RHS is also an array, use push_many instead
2914 // Note: map[key] << value is handled at the statement level
2915 // by try_transform_map_index_push in transform_stmts.
2916 if expr.op in [.amp, .pipe, .xor] && expr.lhs is ast.InfixExpr {
2917 left := expr.lhs as ast.InfixExpr
2918 if left.op == .left_shift {
2919 if _ := t.get_array_elem_type_str(left.lhs) {
2920 combined_rhs := ast.InfixExpr{
2921 lhs: left.rhs
2922 op: expr.op
2923 rhs: expr.rhs
2924 pos: expr.pos
2925 }
2926 return t.transform_expr(ast.InfixExpr{
2927 lhs: left.lhs
2928 op: .left_shift
2929 rhs: combined_rhs
2930 pos: left.pos
2931 })
2932 }
2933 }
2934 }
2935 if expr.op == .left_shift {
2936 if elem_type_name := t.get_array_elem_type_str(expr.lhs) {
2937 if t.is_eval_backend() {
2938 return ast.InfixExpr{
2939 op: expr.op
2940 lhs: t.transform_expr(expr.lhs)
2941 rhs: t.transform_expr(expr.rhs)
2942 pos: expr.pos
2943 }
2944 }
2945 // Use push_many only when RHS element type matches the LHS element type.
2946 // This avoids mis-lowering [][]T << []T, which must stay a single push.
2947 mut rhs_is_array := false
2948 // Enum shorthand (.member) is never an array value, even if the checker
2949 // annotated it with the LHS array type instead of the element enum type.
2950 rhs_is_enum_shorthand := expr.rhs is ast.SelectorExpr
2951 && (expr.rhs as ast.SelectorExpr).lhs is ast.EmptyExpr
2952 rhs_is_bitwise_expr := expr.rhs is ast.InfixExpr
2953 && (expr.rhs as ast.InfixExpr).op in [.amp, .pipe, .xor, .left_shift, .right_shift]
2954 if !rhs_is_enum_shorthand && !rhs_is_bitwise_expr {
2955 rhs_is_array = t.append_rhs_is_array_value_compatible(elem_type_name, expr.rhs)
2956 }
2957 // Or-data-extract: _or_tN.data where the temp var holds a Result/Option of array.
2958 // extract_or_expr replaces `call()!` with `_or_tN.data`, losing the PostfixExpr
2959 // that array_value_elem_type would have recognized. Check the temp var's type.
2960 if !rhs_is_array && expr.rhs is ast.SelectorExpr {
2961 rhs_sel := expr.rhs as ast.SelectorExpr
2962 if rhs_sel.rhs.name == 'data' && rhs_sel.lhs is ast.Ident {
2963 or_ident := rhs_sel.lhs as ast.Ident
2964 if or_ident.name.starts_with('_or_t') {
2965 if or_type := t.lookup_var_type(or_ident.name) {
2966 unwrapped := if or_type is types.ResultType {
2967 or_type.base_type
2968 } else if or_type is types.OptionType {
2969 or_type.base_type
2970 } else {
2971 or_type
2972 }
2973 if unwrapped is types.Array {
2974 rhs_is_array = true
2975 }
2976 }
2977 }
2978 }
2979 }
2980
2981 lhs_is_ptr := t.is_pointer_type_expr(expr.lhs)
2982 && !t.array_append_lhs_uses_local_array_storage(expr.lhs)
2983
2984 // Create (array*)&arr or (array*)arr expression depending on whether LHS is already a pointer
2985 arr_ptr_expr := if lhs_is_ptr {
2986 // Already a pointer, just cast
2987 ast.Expr(ast.CastExpr{
2988 typ: ast.Ident{
2989 name: 'array*'
2990 }
2991 expr: t.transform_expr(expr.lhs)
2992 })
2993 } else {
2994 // Take address then cast
2995 ast.Expr(ast.CastExpr{
2996 typ: ast.Ident{
2997 name: 'array*'
2998 }
2999 expr: ast.PrefixExpr{
3000 op: .amp
3001 expr: t.transform_expr(expr.lhs)
3002 pos: expr.pos
3003 }
3004 })
3005 }
3006
3007 if rhs_is_array {
3008 // RHS is an array - use array__push_many(array*, val.data, val.len)
3009 rhs_transformed := t.transform_expr(expr.rhs)
3010 // Materialize RHS values that should not be selected twice as rvalues.
3011 // The VarDecl is hoisted via pending_stmts before the current statement.
3012 if t.contains_call_expr(expr.rhs) || expr.rhs is ast.ArrayInitExpr {
3013 t.temp_counter++
3014 tmp_name := '_pm_t${t.temp_counter}'
3015 tmp_ident := ast.Ident{
3016 name: tmp_name
3017 }
3018 if rhs_type := t.get_expr_type(expr.rhs) {
3019 t.register_temp_var(tmp_name, rhs_type)
3020 }
3021 t.pending_stmts << ast.Stmt(ast.AssignStmt{
3022 op: .decl_assign
3023 lhs: [ast.Expr(tmp_ident)]
3024 rhs: [rhs_transformed]
3025 })
3026 return ast.CallExpr{
3027 lhs: ast.Ident{
3028 name: 'array__push_many'
3029 }
3030 args: [
3031 arr_ptr_expr,
3032 t.synth_selector(ast.Expr(tmp_ident), 'data',
3033 types.Type(types.voidptr_)),
3034 t.synth_selector(ast.Expr(tmp_ident), 'len', types.Type(types.int_)),
3035 ]
3036 pos: expr.pos
3037 }
3038 }
3039 // Wrap PrefixExpr in parens to fix operator precedence (*other.data -> (*other).data)
3040 rhs_for_selector := if expr.rhs is ast.PrefixExpr {
3041 ast.Expr(ast.ParenExpr{
3042 expr: rhs_transformed
3043 })
3044 } else {
3045 rhs_transformed
3046 }
3047 return ast.CallExpr{
3048 lhs: ast.Ident{
3049 name: 'array__push_many'
3050 }
3051 args: [
3052 arr_ptr_expr,
3053 t.synth_selector(rhs_for_selector, 'data', types.Type(types.voidptr_)),
3054 t.synth_selector(rhs_for_selector, 'len', types.Type(types.int_)),
3055 ]
3056 pos: expr.pos
3057 }
3058 }
3059
3060 // Create (T[]){ elem } expression for single element push
3061 // Note: cleanc will add _MOV wrapper when generating ArrayInitExpr
3062 push_rhs := t.single_nested_array_append_value(expr.rhs, elem_type_name) or { expr.rhs }
3063 mut transformed_rhs := t.transform_expr(push_rhs)
3064 rhs_is_nested_array_literal := push_rhs is ast.ArrayInitExpr
3065 && (elem_type_name.starts_with('Array_')
3066 || elem_type_name.starts_with('Array_fixed_'))
3067 if t.contains_call_expr(push_rhs) || rhs_is_nested_array_literal {
3068 t.temp_counter++
3069 tmp_name := '_ap_t${t.temp_counter}'
3070 tmp_ident := ast.Ident{
3071 name: tmp_name
3072 }
3073 if rhs_type := t.get_expr_type(push_rhs) {
3074 t.register_temp_var(tmp_name, rhs_type)
3075 }
3076 t.pending_stmts << ast.Stmt(ast.AssignStmt{
3077 op: .decl_assign
3078 lhs: [ast.Expr(tmp_ident)]
3079 rhs: [transformed_rhs]
3080 })
3081 transformed_rhs = ast.Expr(tmp_ident)
3082 }
3083 // Clone strings when pushing to arrays to prevent use-after-free
3084 // (V1 does: array_push(&arr, _MOV((string[]){ string_clone(s) })))
3085 cloned_rhs := if elem_type_name == 'string' {
3086 ast.Expr(ast.CallExpr{
3087 lhs: ast.Ident{
3088 name: 'string__clone'
3089 }
3090 args: [transformed_rhs]
3091 })
3092 } else {
3093 transformed_rhs
3094 }
3095 push_elem := t.wrap_array_push_elem_value(cloned_rhs, elem_type_name)
3096 arr_literal := ast.ArrayInitExpr{
3097 typ: ast.Expr(ast.Type(ast.ArrayType{
3098 elem_type: ast.Ident{
3099 name: elem_type_name
3100 }
3101 }))
3102 exprs: [push_elem]
3103 }
3104 return ast.CallExpr{
3105 lhs: ast.Ident{
3106 name: 'builtin__array_push_noscan'
3107 }
3108 args: [
3109 arr_ptr_expr,
3110 ast.Expr(arr_literal),
3111 ]
3112 pos: expr.pos
3113 }
3114 }
3115 }
3116 // Check for enum shorthand in comparisons: x.op == .amp -> x.op == Token.amp
3117 if expr.op in [.eq, .ne] {
3118 // Check if RHS is enum shorthand (.member with empty LHS)
3119 if expr.rhs is ast.SelectorExpr {
3120 rhs_sel := expr.rhs as ast.SelectorExpr
3121 if rhs_sel.lhs is ast.EmptyExpr {
3122 // RHS is enum shorthand - resolve using LHS type
3123 enum_type := t.get_enum_type_name(expr.lhs)
3124 if enum_type != '' {
3125 resolved_rhs := t.resolve_enum_shorthand(expr.rhs, enum_type)
3126 return t.make_infix_expr_at(expr.op, t.transform_expr(expr.lhs),
3127 t.transform_expr(resolved_rhs), expr.pos)
3128 }
3129 }
3130 }
3131 // Check if LHS is enum shorthand (.member with empty LHS)
3132 if expr.lhs is ast.SelectorExpr {
3133 lhs_sel := expr.lhs as ast.SelectorExpr
3134 if lhs_sel.lhs is ast.EmptyExpr {
3135 // LHS is enum shorthand - resolve using RHS type
3136 enum_type := t.get_enum_type_name(expr.rhs)
3137 if enum_type != '' {
3138 resolved_lhs := t.resolve_enum_shorthand(expr.lhs, enum_type)
3139 return t.make_infix_expr_at(expr.op, t.transform_expr(resolved_lhs),
3140 t.transform_expr(expr.rhs), expr.pos)
3141 }
3142 }
3143 }
3144 }
3145 // Check for string comparisons: s1 == s2, s1 < s2, etc.
3146 // Skip if either side is nil — that's a pointer comparison, not a string comparison.
3147 if expr.op in [.eq, .ne, .lt, .gt, .le, .ge] && !t.is_nil_expr(expr.lhs)
3148 && !t.is_nil_expr(expr.rhs) {
3149 mut lhs_has_active_type := false
3150 mut lhs_is_str := false
3151 if lhs_active_type := t.active_local_decl_type_for_expr(expr.lhs) {
3152 lhs_has_active_type = true
3153 lhs_is_str = t.type_is_string(lhs_active_type)
3154 } else {
3155 lhs_is_str = t.is_string_expr(expr.lhs)
3156 }
3157 mut rhs_has_active_type := false
3158 mut rhs_is_str := false
3159 if rhs_active_type := t.active_local_decl_type_for_expr(expr.rhs) {
3160 rhs_has_active_type = true
3161 rhs_is_str = t.type_is_string(rhs_active_type)
3162 } else {
3163 rhs_is_str = t.is_string_expr(expr.rhs)
3164 }
3165 // Also check type environment for expression types if is_string_expr didn't find it,
3166 // unless an active local declaration already resolved the operand type.
3167 if !lhs_is_str && !lhs_has_active_type {
3168 if expr_type := t.get_expr_type(expr.lhs) {
3169 lhs_is_str = t.type_is_string(expr_type)
3170 }
3171 }
3172 if !rhs_is_str && !rhs_has_active_type {
3173 if expr_type := t.get_expr_type(expr.rhs) {
3174 rhs_is_str = t.type_is_string(expr_type)
3175 }
3176 }
3177 // If one side is a string literal and the other is unknown (but likely a string),
3178 // treat as string comparison. Only do this for string literals, not other string expressions.
3179 lhs_is_str_literal := if expr.lhs is ast.StringLiteral {
3180 true
3181 } else if expr.lhs is ast.BasicLiteral {
3182 expr.lhs.kind == .string
3183 } else {
3184 false
3185 }
3186 rhs_is_str_literal := if expr.rhs is ast.StringLiteral {
3187 true
3188 } else if expr.rhs is ast.BasicLiteral {
3189 expr.rhs.kind == .string
3190 } else {
3191 false
3192 }
3193 // Only infer string comparison if at least one side is a string literal AND
3194 // the other is identified as string OR is an ident (could be loop variable)
3195 // Also transform if both are Ident and at least one is known to be string
3196 // (the other is likely also string in a comparison context)
3197 // Also transform if one side is a SelectorExpr and the other is a string literal
3198 // (field access compared with string literal is almost always string comparison)
3199 // If either side is known to be a string, the other must also be string
3200 // (V is type-checked). This handles cases where is_string_expr fails on
3201 // complex expressions like Result data access selectors.
3202 uses_generic_type := t.expr_uses_current_generic_type(expr.lhs)
3203 || t.expr_uses_current_generic_type(expr.rhs)
3204 active_non_string_type := (lhs_has_active_type && !lhs_is_str)
3205 || (rhs_has_active_type && !rhs_is_str)
3206 should_transform := !uses_generic_type && !active_non_string_type && (lhs_is_str
3207 || rhs_is_str || (lhs_is_str_literal && (expr.rhs is ast.Ident
3208 || expr.rhs is ast.SelectorExpr))
3209 || (rhs_is_str_literal && (expr.lhs is ast.Ident || expr.lhs is ast.SelectorExpr)))
3210 if should_transform {
3211 // Transform string comparisons to function calls
3212 match expr.op {
3213 .eq {
3214 // s1 == s2 -> string__eq(s1, s2)
3215 return ast.CallExpr{
3216 lhs: ast.Ident{
3217 name: 'string__eq'
3218 }
3219 args: [t.transform_expr(expr.lhs), t.transform_expr(expr.rhs)]
3220 pos: expr.pos
3221 }
3222 }
3223 .ne {
3224 // s1 != s2 -> !string__eq(s1, s2)
3225 return ast.PrefixExpr{
3226 op: .not
3227 expr: ast.CallExpr{
3228 lhs: ast.Ident{
3229 name: 'string__eq'
3230 }
3231 args: [t.transform_expr(expr.lhs),
3232 t.transform_expr(expr.rhs)]
3233 pos: expr.pos
3234 }
3235 pos: expr.pos
3236 }
3237 }
3238 .lt {
3239 // s1 < s2 -> string__lt(s1, s2)
3240 return ast.CallExpr{
3241 lhs: ast.Ident{
3242 name: 'string__lt'
3243 }
3244 args: [t.transform_expr(expr.lhs), t.transform_expr(expr.rhs)]
3245 pos: expr.pos
3246 }
3247 }
3248 .gt {
3249 // s1 > s2 -> string__lt(s2, s1)
3250 return ast.CallExpr{
3251 lhs: ast.Ident{
3252 name: 'string__lt'
3253 }
3254 args: [t.transform_expr(expr.rhs), t.transform_expr(expr.lhs)]
3255 pos: expr.pos
3256 }
3257 }
3258 .le {
3259 // s1 <= s2 -> !string__lt(s2, s1)
3260 return ast.PrefixExpr{
3261 op: .not
3262 expr: ast.CallExpr{
3263 lhs: ast.Ident{
3264 name: 'string__lt'
3265 }
3266 args: [t.transform_expr(expr.rhs),
3267 t.transform_expr(expr.lhs)]
3268 pos: expr.pos
3269 }
3270 pos: expr.pos
3271 }
3272 }
3273 .ge {
3274 // s1 >= s2 -> !string__lt(s1, s2)
3275 return ast.PrefixExpr{
3276 op: .not
3277 expr: ast.CallExpr{
3278 lhs: ast.Ident{
3279 name: 'string__lt'
3280 }
3281 args: [t.transform_expr(expr.lhs),
3282 t.transform_expr(expr.rhs)]
3283 pos: expr.pos
3284 }
3285 pos: expr.pos
3286 }
3287 }
3288 else {}
3289 }
3290 }
3291 // Check for array comparisons: arr1 == arr2 or arr1 != arr2
3292 lhs_arr_type := t.get_array_type_str(expr.lhs)
3293 rhs_arr_type := t.get_array_type_str(expr.rhs)
3294 // For native backends, fixed arrays use memcmp, not array__eq.
3295 // get_array_type_str returns 'Array_fixed_...' for fixed arrays.
3296 mut is_fixed_array := false
3297 if lhs_str := lhs_arr_type {
3298 if lhs_str.starts_with('Array_fixed_') {
3299 is_fixed_array = true
3300 }
3301 }
3302 if lhs_arr_type != none && rhs_arr_type != none && !is_fixed_array {
3303 // Use type-specific equality for arrays of structs/maps (memcmp won't work)
3304 mut eq_fn_name := 'array__eq'
3305 if t.pref.backend != .arm64 && t.pref.backend != .x64 {
3306 if t.array_elem_needs_deep_eq(expr.lhs) || t.array_elem_needs_deep_eq(expr.rhs) {
3307 eq_fn_name = '${lhs_arr_type}__eq'
3308 }
3309 }
3310 // Transform array comparisons to function calls
3311 eq_call := ast.CallExpr{
3312 lhs: ast.Ident{
3313 name: eq_fn_name
3314 }
3315 args: [t.transform_expr(expr.lhs), t.transform_expr(expr.rhs)]
3316 pos: expr.pos
3317 }
3318 if expr.op == .ne {
3319 // arr1 != arr2 -> !array__eq(arr1, arr2)
3320 return ast.PrefixExpr{
3321 op: .not
3322 expr: eq_call
3323 pos: expr.pos
3324 }
3325 }
3326 // arr1 == arr2 -> array__eq(arr1, arr2)
3327 return eq_call
3328 }
3329 // Check for fixed array comparisons: [N]T == [N]T
3330 // For native backends, lower to C.memcmp(&a, &b, N * sizeof(T)) == 0
3331 // Only for memcmp-safe element types (primitives, fixed arrays of primitives).
3332 // Dynamic arrays, strings, maps, and structs contain heap pointers.
3333 if t.is_native_be && expr.op in [.eq, .ne] {
3334 if lhs_type := t.get_expr_type(expr.lhs) {
3335 lhs_base := t.unwrap_alias_and_pointer_type(lhs_type)
3336 if lhs_base is types.ArrayFixed {
3337 if t.is_memcmp_safe_type(lhs_base.elem_type) {
3338 // Primitives/fixed arrays of primitives: use memcmp
3339 elem_size := t.type_sizeof(lhs_base.elem_type)
3340 total_size := lhs_base.len * elem_size
3341 memcmp_call := ast.CallExpr{
3342 lhs: ast.Ident{
3343 name: 'C__memcmp'
3344 }
3345 args: [
3346 ast.Expr(ast.PrefixExpr{
3347 op: .amp
3348 expr: t.transform_expr(expr.lhs)
3349 pos: expr.pos
3350 }),
3351 ast.Expr(ast.PrefixExpr{
3352 op: .amp
3353 expr: t.transform_expr(expr.rhs)
3354 pos: expr.pos
3355 }),
3356 ast.Expr(ast.BasicLiteral{
3357 kind: .number
3358 value: total_size.str()
3359 }),
3360 ]
3361 pos: expr.pos
3362 }
3363 zero_lit := ast.BasicLiteral{
3364 kind: .number
3365 value: '0'
3366 }
3367 return t.make_infix_expr_at(expr.op, memcmp_call, ast.Expr(zero_lit),
3368 expr.pos)
3369 }
3370 // Non-memcmp-safe elements (dynamic arrays, strings, maps):
3371 // Generate element-by-element comparison using the appropriate
3372 // equality function, since synthesized AST nodes lack type info.
3373 elem_base := t.unwrap_alias_and_pointer_type(lhs_base.elem_type)
3374 eq_fn := if elem_base is types.Array {
3375 'array__eq'
3376 } else if t.type_to_c_name(elem_base) == 'string' {
3377 'string__=='
3378 } else if elem_base is types.Map {
3379 'map_map_eq'
3380 } else {
3381 '' // unsupported element type
3382 }
3383 if eq_fn != '' {
3384 lhs_trans := t.transform_expr(expr.lhs)
3385 rhs_trans := t.transform_expr(expr.rhs)
3386 mut chain := ast.Expr(ast.CallExpr{
3387 lhs: ast.Ident{
3388 name: eq_fn
3389 }
3390 args: [
3391 ast.Expr(ast.IndexExpr{
3392 lhs: lhs_trans
3393 expr: ast.BasicLiteral{
3394 kind: .number
3395 value: '0'
3396 }
3397 }),
3398 ast.Expr(ast.IndexExpr{
3399 lhs: rhs_trans
3400 expr: ast.BasicLiteral{
3401 kind: .number
3402 value: '0'
3403 }
3404 }),
3405 ]
3406 pos: expr.pos
3407 })
3408 for i := 1; i < lhs_base.len; i++ {
3409 elem_cmp := ast.CallExpr{
3410 lhs: ast.Ident{
3411 name: eq_fn
3412 }
3413 args: [
3414 ast.Expr(ast.IndexExpr{
3415 lhs: lhs_trans
3416 expr: ast.BasicLiteral{
3417 kind: .number
3418 value: i.str()
3419 }
3420 }),
3421 ast.Expr(ast.IndexExpr{
3422 lhs: rhs_trans
3423 expr: ast.BasicLiteral{
3424 kind: .number
3425 value: i.str()
3426 }
3427 }),
3428 ]
3429 pos: expr.pos
3430 }
3431 chain = t.make_infix_expr_at(.and, chain, elem_cmp, expr.pos)
3432 }
3433 if expr.op == .ne {
3434 return ast.PrefixExpr{
3435 op: .not
3436 expr: chain
3437 pos: expr.pos
3438 }
3439 }
3440 return chain
3441 }
3442 }
3443 }
3444 }
3445 // Check for map comparisons: map1 == map2 or map1 != map2
3446 // Exclude pointer-to-map types (those should be pointer comparisons)
3447 if expr.op in [.eq, .ne] {
3448 mut is_lhs_ptr_map := false
3449 if lhs_type := t.get_expr_type(expr.lhs) {
3450 if lhs_type is types.Pointer {
3451 is_lhs_ptr_map = true
3452 }
3453 }
3454 mut is_rhs_ptr_map := false
3455 if rhs_type := t.get_expr_type(expr.rhs) {
3456 if rhs_type is types.Pointer {
3457 is_rhs_ptr_map = true
3458 }
3459 }
3460 if !is_lhs_ptr_map && !is_rhs_ptr_map {
3461 lhs_map_type := t.get_map_type_for_expr(expr.lhs)
3462 rhs_map_type := t.get_map_type_for_expr(expr.rhs)
3463 if lhs_map_type != none || rhs_map_type != none {
3464 if t.is_eval_backend() {
3465 return ast.InfixExpr{
3466 op: expr.op
3467 lhs: t.transform_expr(expr.lhs)
3468 rhs: t.transform_expr(expr.rhs)
3469 pos: expr.pos
3470 }
3471 }
3472 map_type_name := lhs_map_type or { rhs_map_type or { 'map' } }
3473 eq_fn := if map_type_name.starts_with('Map_') {
3474 '${map_type_name}_map_eq'
3475 } else {
3476 'map_map_eq'
3477 }
3478 map_eq_call := ast.CallExpr{
3479 lhs: ast.Ident{
3480 name: eq_fn
3481 }
3482 args: [t.transform_expr(expr.lhs), t.transform_expr(expr.rhs)]
3483 pos: expr.pos
3484 }
3485 if expr.op == .ne {
3486 return ast.PrefixExpr{
3487 op: .not
3488 expr: map_eq_call
3489 pos: expr.pos
3490 }
3491 }
3492 return map_eq_call
3493 }
3494 }
3495 }
3496 }
3497 // Note: struct == / != is handled by cleanc's memcmp/field-by-field comparison.
3498 // Check for struct operator overloading (e.g., time.Time - time.Time)
3499 // This transforms t1 - t2 into time__Time__op_minus(t1, t2) for structs with operator overloading
3500 // Only applies to specific known struct types that define operator methods
3501 if expr.op in [.plus, .minus, .mul, .div, .mod] {
3502 if lhs_type := t.get_expr_type(expr.lhs) {
3503 match lhs_type {
3504 types.Struct {
3505 type_name := t.type_to_c_name(lhs_type)
3506 // Only transform known struct types with operator overloading
3507 known_struct_ops := ['time__Time']
3508 if type_name in known_struct_ops {
3509 // Determine operator method name
3510 op_name := match expr.op {
3511 .plus { '__op_plus' }
3512 .minus { '__op_minus' }
3513 .mul { '__op_mul' }
3514 .div { '__op_div' }
3515 .mod { '__op_mod' }
3516 else { '' }
3517 }
3518
3519 if op_name != '' {
3520 // Generate function call: Type__op(lhs, rhs)
3521 fn_name := '${type_name}${op_name}'
3522 return ast.CallExpr{
3523 lhs: ast.Ident{
3524 name: fn_name
3525 }
3526 args: [t.transform_expr(expr.lhs),
3527 t.transform_expr(expr.rhs)]
3528 pos: expr.pos
3529 }
3530 }
3531 }
3532 }
3533 else {}
3534 }
3535 }
3536 }
3537 // Default: just transform children
3538 lhs_trans := t.transform_expr(expr.lhs)
3539 rhs_trans := t.transform_expr(expr.rhs)
3540 return t.make_infix_expr_at(expr.op, lhs_trans, rhs_trans, expr.pos)
3541}
3542
3543fn folded_string_literal_concat(expr ast.Expr) ?string {
3544 match expr {
3545 ast.StringLiteral {
3546 if expr.kind != .v {
3547 return none
3548 }
3549 return strip_string_literal_quotes(expr.value)
3550 }
3551 ast.InfixExpr {
3552 if expr.op != .plus {
3553 return none
3554 }
3555 left := folded_string_literal_concat(expr.lhs) or { return none }
3556 right := folded_string_literal_concat(expr.rhs) or { return none }
3557 return left + right
3558 }
3559 ast.ParenExpr {
3560 return folded_string_literal_concat(expr.expr)
3561 }
3562 else {
3563 return none
3564 }
3565 }
3566}
3567
3568fn strip_string_literal_quotes(raw string) string {
3569 if raw.len < 2 {
3570 return raw
3571 }
3572 first := raw[0]
3573 last := raw[raw.len - 1]
3574 if first == last && first in [`'`, `"`] {
3575 return raw[1..raw.len - 1]
3576 }
3577 return raw
3578}
3579
3580// resolve_field_type looks up the type of a field on a variable
3581// e.g., for a.flags where a is Array, returns 'ArrayFlags'
3582fn (mut t Transformer) transform_comptime_expr(expr ast.ComptimeExpr) ast.Expr {
3583 // The inner expression should be an IfExpr for $if
3584 inner := expr.expr
3585 if inner is ast.IfExpr {
3586 return t.eval_comptime_if(inner)
3587 }
3588 if inner is ast.CallExpr {
3589 if inner.lhs is ast.Ident && inner.lhs.name == 'res' {
3590 return ast.Expr(ast.BasicLiteral{
3591 kind: .key_false
3592 value: 'false'
3593 pos: expr.pos
3594 })
3595 }
3596 }
3597 if inner is ast.CallOrCastExpr {
3598 if inner.lhs is ast.Ident && inner.lhs.name == 'res' {
3599 return ast.Expr(ast.BasicLiteral{
3600 kind: .key_false
3601 value: 'false'
3602 pos: expr.pos
3603 })
3604 }
3605 }
3606 if inner is ast.CallExpr {
3607 if inner.lhs is ast.Ident && inner.lhs.name == 'embed_file' {
3608 return t.transform_embed_file_comptime_expr(expr, inner.args)
3609 }
3610 }
3611 if inner is ast.CallOrCastExpr {
3612 if inner.lhs is ast.Ident && inner.lhs.name == 'embed_file' {
3613 return t.transform_embed_file_comptime_expr(expr, [inner.expr])
3614 }
3615 }
3616 if inner is ast.Ident {
3617 if inner.name in ['VMODROOT', '@VMODROOT'] {
3618 return ast.Expr(t.vmodroot_string_literal(expr.pos))
3619 }
3620 }
3621 if transformed := t.transform_embed_file_comptime_chain(inner, expr.pos) {
3622 return transformed
3623 }
3624 // For other comptime expressions, just return them transformed
3625 return ast.ComptimeExpr{
3626 expr: t.transform_expr(inner)
3627 pos: expr.pos
3628 }
3629}
3630
3631fn (mut t Transformer) transform_embed_file_comptime_chain(expr ast.Expr, comptime_pos token.Pos) ?ast.Expr {
3632 if transformed := t.transform_embed_file_chain_lhs(expr, comptime_pos) {
3633 return transformed
3634 }
3635 match expr {
3636 ast.SelectorExpr {
3637 if transformed_lhs := t.transform_embed_file_chain_lhs(expr.lhs, comptime_pos) {
3638 return ast.Expr(ast.SelectorExpr{
3639 lhs: transformed_lhs
3640 rhs: expr.rhs
3641 pos: expr.pos
3642 })
3643 }
3644 }
3645 ast.CallExpr {
3646 mut transformed_lhs := ast.empty_expr
3647 if expr.lhs is ast.SelectorExpr {
3648 if transformed_base := t.transform_embed_file_chain_lhs(expr.lhs.lhs, comptime_pos) {
3649 transformed_lhs = ast.Expr(ast.SelectorExpr{
3650 lhs: transformed_base
3651 rhs: expr.lhs.rhs
3652 pos: expr.lhs.pos
3653 })
3654 }
3655 }
3656 if transformed_lhs !is ast.EmptyExpr {
3657 mut args := []ast.Expr{cap: expr.args.len}
3658 for arg in expr.args {
3659 args << t.transform_expr(arg)
3660 }
3661 return ast.Expr(ast.CallExpr{
3662 lhs: transformed_lhs
3663 args: args
3664 pos: expr.pos
3665 })
3666 }
3667 }
3668 else {}
3669 }
3670
3671 return none
3672}
3673
3674fn (mut t Transformer) transform_embed_file_chain_lhs(expr ast.Expr, comptime_pos token.Pos) ?ast.Expr {
3675 match expr {
3676 ast.CallExpr {
3677 if expr.lhs is ast.Ident && expr.lhs.name == 'embed_file' {
3678 return t.transform_embed_file_comptime_expr(ast.ComptimeExpr{
3679 expr: ast.Expr(expr)
3680 pos: comptime_pos
3681 }, expr.args)
3682 }
3683 if expr.lhs is ast.SelectorExpr {
3684 sel := expr.lhs as ast.SelectorExpr
3685 if transformed_base := t.transform_embed_file_chain_lhs(sel.lhs, comptime_pos) {
3686 mut args := []ast.Expr{cap: expr.args.len}
3687 for arg in expr.args {
3688 args << t.transform_expr(arg)
3689 }
3690 return ast.Expr(ast.CallExpr{
3691 lhs: ast.Expr(ast.SelectorExpr{
3692 lhs: transformed_base
3693 rhs: sel.rhs
3694 pos: sel.pos
3695 })
3696 args: args
3697 pos: expr.pos
3698 })
3699 }
3700 }
3701 }
3702 ast.CallOrCastExpr {
3703 if expr.lhs is ast.Ident && expr.lhs.name == 'embed_file' {
3704 return t.transform_embed_file_comptime_expr(ast.ComptimeExpr{
3705 expr: ast.Expr(expr)
3706 pos: comptime_pos
3707 }, [expr.expr])
3708 }
3709 }
3710 ast.SelectorExpr {
3711 if transformed_lhs := t.transform_embed_file_chain_lhs(expr.lhs, comptime_pos) {
3712 return ast.Expr(ast.SelectorExpr{
3713 lhs: transformed_lhs
3714 rhs: expr.rhs
3715 pos: expr.pos
3716 })
3717 }
3718 }
3719 ast.ComptimeExpr {
3720 if transformed_inner := t.transform_embed_file_comptime_chain(expr.expr, expr.pos) {
3721 return transformed_inner
3722 }
3723 }
3724 else {}
3725 }
3726
3727 return none
3728}
3729
3730// lower_go_call transforms `go foo(a, b)` into a regular call to
3731// goroutines__goroutine_create, avoiding backend-specific go handling.
3732// For zero-arg calls: goroutines__goroutine_create(voidptr(foo), voidptr(0), 0)
3733// For calls with args: generates a wrapper struct + trampoline function,
3734// then calls goroutines__goroutine_create(trampoline, packed_args, sizeof).
3735fn (mut t Transformer) lower_go_call(expr ast.KeywordOperator) ast.Expr {
3736 call := expr.exprs[0]
3737 mut call_lhs := ast.empty_expr
3738 mut call_args := []ast.Expr{}
3739 if call is ast.CallExpr {
3740 call_lhs = call.lhs
3741 call_args = call.args.clone()
3742 } else if call is ast.CallOrCastExpr {
3743 call_lhs = call.lhs
3744 call_args = [call.expr]
3745 } else {
3746 // Not a call expression, return as-is
3747 return expr
3748 }
3749 // Resolve the V-level function name for the wrapper
3750 fn_name := t.resolve_go_fn_name(call_lhs)
3751 if fn_name == '' {
3752 // Cannot resolve — fall back to keeping the original expression
3753 return expr
3754 }
3755 // Transform all arguments
3756 mut transformed_args := []ast.Expr{cap: call_args.len}
3757 for arg in call_args {
3758 transformed_args << t.transform_expr(arg)
3759 }
3760 // Resolve argument type names for struct generation
3761 mut arg_type_names := []string{cap: call_args.len}
3762 for arg in call_args {
3763 if typ := t.get_expr_type(arg) {
3764 arg_type_names << t.type_to_c_name(typ)
3765 } else {
3766 arg_type_names << 'int'
3767 }
3768 }
3769 // Check for method call (receiver needs to be first arg in wrapper)
3770 mut is_method := false
3771 mut receiver_expr := ast.empty_expr
3772 mut receiver_type_name := ''
3773 if call_lhs is ast.SelectorExpr {
3774 sel := call_lhs as ast.SelectorExpr
3775 sel_lhs := sel.lhs
3776 if !(sel_lhs is ast.Ident && t.is_module_name((sel_lhs as ast.Ident).name)) {
3777 is_method = true
3778 receiver_expr = t.transform_expr(sel_lhs)
3779 if recv_type := t.get_expr_type(sel_lhs) {
3780 receiver_type_name = t.type_to_c_name(recv_type)
3781 } else {
3782 receiver_type_name = 'voidptr'
3783 }
3784 }
3785 }
3786 wrapper_name := '__go_wrap_${fn_name}'
3787 if wrapper_name !in t.needed_go_wrappers {
3788 mut param_names := []string{}
3789 mut param_types := []string{}
3790 if is_method {
3791 param_names << '_recv'
3792 param_types << receiver_type_name
3793 }
3794 for i, type_name in arg_type_names {
3795 param_names << '_a${i}'
3796 param_types << type_name
3797 }
3798 t.needed_go_wrappers[wrapper_name] = GoWrapperInfo{
3799 fn_name: fn_name
3800 wrapper_name: wrapper_name
3801 param_names: param_names
3802 param_types: param_types
3803 }
3804 }
3805 // Build call args: [receiver, args...] for methods, [args...] for functions
3806 mut wrapper_args := []ast.Expr{}
3807 if is_method {
3808 wrapper_args << receiver_expr
3809 }
3810 for arg in transformed_args {
3811 wrapper_args << arg
3812 }
3813 return ast.CallExpr{
3814 lhs: ast.Ident{
3815 name: wrapper_name
3816 }
3817 args: wrapper_args
3818 pos: expr.pos
3819 }
3820}
3821
3822// resolve_go_fn_name extracts the C-mangled function name from a call LHS expression.
3823fn (t &Transformer) resolve_go_fn_name(lhs ast.Expr) string {
3824 if lhs is ast.Ident {
3825 if t.cur_module != '' {
3826 return '${t.cur_module}__${lhs.name}'
3827 }
3828 return lhs.name
3829 }
3830 if lhs is ast.SelectorExpr {
3831 if lhs.lhs is ast.Ident {
3832 mod_name := lhs.lhs.name
3833 return '${mod_name}__${lhs.rhs.name}'
3834 }
3835 }
3836 return ''
3837}
3838
3839// is_module_name checks if a name refers to a known module.
3840fn (t &Transformer) is_module_name(name string) bool {
3841 // Check if the name is a known module by looking up its scope
3842 return name in t.cached_scopes
3843}
3844
3845// eval_comptime_if evaluates a compile-time $if and returns the selected branch expression
3846