v / vlib / v2 / transformer / for.v
1213 lines · 1135 sloc · 32.24 KB · b831b0eec9b5b2756784b5dabf3808d47d6a39ae
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.token
9import v2.types
10
11// gen_map_iter_temp_name generates unique temporary variable names for map iteration
12fn (mut t Transformer) gen_map_iter_temp_name(suffix string) string {
13 t.temp_counter++
14 return '_map_${suffix}_${t.temp_counter}'
15}
16
17fn (t &Transformer) iter_expr_needs_deref(expr ast.Expr) bool {
18 if t.is_pointer_type_expr(expr) {
19 return true
20 }
21 if expr is ast.Ident {
22 if obj := t.scope.lookup_parent(expr.name, 0) {
23 mut base := obj.typ()
24 for _ in 0 .. 64 {
25 if base is types.Pointer {
26 return true
27 }
28 if base is types.Alias {
29 alias_t := base as types.Alias
30 base = alias_t.base_type
31 continue
32 }
33 break
34 }
35 }
36 }
37 if iter_type := t.get_expr_type(expr) {
38 mut base := iter_type
39 for _ in 0 .. 64 {
40 if base is types.Pointer {
41 return true
42 }
43 if base is types.Alias {
44 alias_t := base as types.Alias
45 base = alias_t.base_type
46 continue
47 }
48 break
49 }
50 }
51 return false
52}
53
54fn string_iter_value_type() types.Type {
55 return types.Type(types.Primitive{
56 props: .integer | .unsigned
57 size: 8
58 })
59}
60
61fn runes_iter_value_type() types.Type {
62 return types.Type(types.Alias{
63 name: 'rune'
64 base_type: types.Type(types.Primitive{
65 props: .integer | .unsigned
66 size: 32
67 })
68 })
69}
70
71fn (mut t Transformer) register_for_in_lhs_type(lhs ast.Expr, typ types.Type) {
72 if lhs is ast.Ident {
73 if lhs.pos.id != 0 {
74 t.register_synth_type(lhs.pos, typ)
75 }
76 } else if lhs is ast.ModifierExpr {
77 t.register_for_in_lhs_type(lhs.expr, typ)
78 }
79}
80
81fn (mut t Transformer) register_for_in_var_type(name string, typ types.Type) {
82 if name == '' || name == '_' {
83 return
84 }
85 t.remember_local_decl_type(name, typ)
86 obj := value_object_from_type(typ)
87 if t.scope != unsafe { nil } {
88 t.scope.insert_or_update(name, obj)
89 }
90 if t.fn_root_scope != unsafe { nil } {
91 t.fn_root_scope.insert_or_update(name, obj)
92 }
93}
94
95fn (t &Transformer) is_string_iterable_type(iter_type types.Type) bool {
96 mut cur := iter_type
97 for _ in 0 .. 64 {
98 if cur is types.Pointer {
99 cur = (cur as types.Pointer).base_type
100 continue
101 }
102 if cur is types.Alias {
103 cur = (cur as types.Alias).base_type
104 continue
105 }
106 break
107 }
108 if cur is types.String {
109 return true
110 }
111 return cur is types.Struct && (cur as types.Struct).name == 'string'
112}
113
114fn (t &Transformer) for_in_value_type(iter_type types.Type) types.Type {
115 if t.is_string_iterable_type(iter_type) {
116 return string_iter_value_type()
117 }
118 return iter_type.value_type()
119}
120
121fn (t &Transformer) for_in_value_uses_array_index(value_type types.Type) bool {
122 mut cur := value_type
123 for _ in 0 .. 64 {
124 if cur is types.Alias {
125 cur = (cur as types.Alias).base_type
126 continue
127 }
128 break
129 }
130 return cur is types.Array || cur is types.ArrayFixed || cur is types.Map || cur is types.Struct
131}
132
133fn (t &Transformer) for_in_iter_expr_type(expr ast.Expr) ?types.Type {
134 mut base_expr := expr
135 for _ in 0 .. 8 {
136 if base_expr is ast.ModifierExpr {
137 base_expr = base_expr.expr
138 continue
139 }
140 if base_expr is ast.ParenExpr {
141 base_expr = base_expr.expr
142 continue
143 }
144 break
145 }
146 if base_expr is ast.Ident {
147 if typ := t.lookup_var_type(base_expr.name) {
148 return typ
149 }
150 }
151 return t.get_expr_type(expr)
152}
153
154fn (t &Transformer) generic_iter_value_placeholder(expr ast.Expr) ?string {
155 match expr {
156 ast.Ident {
157 return t.generic_var_type_params[expr.name] or { none }
158 }
159 ast.ParenExpr {
160 return t.generic_iter_value_placeholder(expr.expr)
161 }
162 ast.ModifierExpr {
163 return t.generic_iter_value_placeholder(expr.expr)
164 }
165 else {}
166 }
167
168 return none
169}
170
171fn (mut t Transformer) iter_value_expr(orig ast.Expr, transformed ast.Expr, pos token.Pos, value_type types.Type) ast.Expr {
172 t.register_synth_type(pos, value_type)
173 if t.iter_expr_needs_deref(orig) {
174 base_expr := ast.Expr(ast.ParenExpr{
175 expr: transformed
176 pos: pos
177 })
178 deref_expr := ast.Expr(ast.PrefixExpr{
179 op: .mul
180 expr: base_expr
181 pos: pos
182 })
183 return ast.Expr(ast.ParenExpr{
184 expr: deref_expr
185 pos: pos
186 })
187 }
188 return transformed
189}
190
191fn (mut t Transformer) array_data_index_expr(array_expr ast.Expr, index_expr ast.Expr, value_type types.Type, pos token.Pos) ast.Expr {
192 t.register_synth_type(pos, value_type)
193 data_expr := t.synth_selector(array_expr, 'data', types.Type(types.voidptr_))
194 ptr_pos := t.next_synth_pos()
195 t.register_synth_type(ptr_pos, types.Type(types.Pointer{
196 base_type: value_type
197 }))
198 elem_type_name := t.type_to_c_decl_name(value_type)
199 typed_data := ast.Expr(ast.CastExpr{
200 typ: ast.Ident{
201 name: '${elem_type_name}*'
202 }
203 expr: data_expr
204 pos: ptr_pos
205 })
206 return ast.Expr(ast.IndexExpr{
207 lhs: typed_data
208 expr: index_expr
209 pos: pos
210 })
211}
212
213fn (mut t Transformer) smartcast_map_iter_value_expr(iter_expr ast.Expr, map_type types.Map) ast.Expr {
214 map_c_name := t.type_to_c_name(types.Type(map_type))
215 if map_c_name == '' {
216 return iter_expr
217 }
218 data_access := t.synth_selector(iter_expr, '_data', types.Type(types.voidptr_))
219 is_native_backend := t.pref != unsafe { nil } && t.is_native_be
220 variant_access := if is_native_backend {
221 data_access
222 } else {
223 t.synth_selector(data_access, '_${map_c_name}', types.Type(types.voidptr_))
224 }
225 cast_expr := ast.CastExpr{
226 typ: ast.Ident{
227 name: '${map_c_name}*'
228 }
229 expr: variant_access
230 }
231 deref_expr := ast.PrefixExpr{
232 op: .mul
233 expr: cast_expr
234 }
235 return ast.Expr(ast.ParenExpr{
236 expr: deref_expr
237 })
238}
239
240// try_expand_for_in_map expands map iteration to lower-level constructs.
241// Transforms: for k, v in map_expr { body }
242// Into:
243// {
244// mut _map_len := map_expr.key_values.len
245// for _map_idx := 0; _map_idx < _map_len; _map_idx++ {
246// _map_delta := map_expr.key_values.len - _map_len
247// _map_len = map_expr.key_values.len
248// if _map_delta < 0 { _map_idx = -1; continue }
249// if !DenseArray__has_index(&map_expr.key_values, _map_idx) { continue }
250// k := *(KeyType*)DenseArray__key(&map_expr.key_values, _map_idx)
251// v := *(ValueType*)DenseArray__value(&map_expr.key_values, _map_idx)
252// body
253// }
254// }
255fn (mut t Transformer) try_expand_for_in_map(stmt ast.ForStmt) ?[]ast.Stmt {
256 if t.is_eval_backend() {
257 return none
258 }
259 // Check if this is a for-in statement
260 if stmt.init !is ast.ForInStmt {
261 return none
262 }
263 for_in := stmt.init as ast.ForInStmt
264
265 // Get the type of the iterable expression
266 iter_type := t.get_expr_type(for_in.expr) or { return none }
267
268 // Check if it's a map type (allow alias/pointer wrappers).
269 map_type := t.unwrap_map_type(iter_type) or { return none }
270
271 // Get key variable name
272 mut key_name := ''
273 mut key_is_blank := false
274 if for_in.key !is ast.EmptyExpr {
275 if for_in.key is ast.Ident {
276 key_name = for_in.key.name
277 key_is_blank = key_name == '_'
278 } else if for_in.key is ast.ModifierExpr {
279 if for_in.key.expr is ast.Ident {
280 key_name = for_in.key.expr.name
281 key_is_blank = key_name == '_'
282 }
283 }
284 }
285
286 // Get value variable name
287 mut value_name := ''
288 if for_in.value is ast.Ident {
289 value_name = for_in.value.name
290 } else if for_in.value is ast.ModifierExpr {
291 if for_in.value.expr is ast.Ident {
292 value_name = for_in.value.expr.name
293 }
294 }
295
296 // Get C-compatible type names for key and value (using C declaration syntax with *)
297 key_type_name := t.type_to_c_decl_name(map_type.key_type)
298 value_type_name := t.type_to_c_decl_name(map_type.value_type)
299
300 // Generate unique temp variable names
301 idx_name := t.gen_map_iter_temp_name('idx')
302 len_name := t.gen_map_iter_temp_name('len')
303 delta_name := t.gen_map_iter_temp_name('delta')
304
305 idx_ident := ast.Ident{
306 name: idx_name
307 }
308 len_ident := ast.Ident{
309 name: len_name
310 }
311 delta_ident := ast.Ident{
312 name: delta_name
313 }
314
315 // For lvalue expressions (simple Ident or SelectorExpr), transform and use directly
316 // so mutations during iteration (delete/set) are visible.
317 // For rvalue expressions (function calls, map literals), store in a temp variable.
318 // NOTE: rvalue expressions must NOT be pre-transformed here, because the expansion
319 // result goes through transform_stmt again. Pre-transforming would cause double
320 // transformation (e.g., ArrayInitExpr args in new_map_init become full array
321 // construction calls instead of raw data arrays).
322 smartcast_iter_expr := t.expr_to_string(for_in.expr)
323 is_smartcast_iter := smartcast_iter_expr != ''
324 && t.find_smartcast_for_expr(smartcast_iter_expr) != none
325 is_lvalue := !is_smartcast_iter && (for_in.expr is ast.Ident || for_in.expr is ast.SelectorExpr)
326 mut map_ref := ast.Expr(ast.Ident{})
327 mut stmts := []ast.Stmt{}
328 if is_lvalue {
329 map_ref = for_in.expr
330 } else {
331 map_tmp_name := t.gen_map_iter_temp_name('map')
332 map_tmp_ident := ast.Ident{
333 name: map_tmp_name
334 }
335 map_source := if is_smartcast_iter {
336 t.smartcast_map_iter_value_expr(for_in.expr, map_type)
337 } else {
338 for_in.expr
339 }
340 stmts << ast.AssignStmt{
341 op: .decl_assign
342 lhs: [ast.Expr(map_tmp_ident)]
343 rhs: [map_source]
344 }
345 t.register_temp_var(map_tmp_name, iter_type)
346 map_ref = ast.Expr(map_tmp_ident)
347 }
348
349 // key_values selector: map_ref.key_values
350 key_values_expr := t.synth_selector(map_ref, 'key_values', types.Type(types.Struct{
351 name: 'DenseArray'
352 }))
353
354 // key_values.len selector: map_ref.key_values.len
355 key_values_len_expr := t.synth_selector(ast.Expr(key_values_expr), 'len',
356 types.Type(types.int_))
357
358 // 1. mut _map_len := map_ref.key_values.len
359 stmts << ast.AssignStmt{
360 op: .decl_assign
361 lhs: [ast.Expr(ast.ModifierExpr{
362 kind: .key_mut
363 expr: len_ident
364 })]
365 rhs: [ast.Expr(key_values_len_expr)]
366 }
367
368 // Build the inner loop body
369 mut loop_body := []ast.Stmt{}
370
371 // _map_delta := map_expr.key_values.len - _map_len
372 loop_body << ast.AssignStmt{
373 op: .decl_assign
374 lhs: [ast.Expr(delta_ident)]
375 rhs: [t.make_infix_expr(.minus, key_values_len_expr, ast.Expr(len_ident))]
376 }
377
378 // _map_len = map_expr.key_values.len
379 loop_body << ast.AssignStmt{
380 op: .assign
381 lhs: [ast.Expr(len_ident)]
382 rhs: [key_values_len_expr]
383 }
384
385 // if _map_delta < 0 { _map_idx = -1; continue }
386 delta_lt_zero := t.make_infix_expr(.lt, ast.Expr(delta_ident), t.make_number_expr('0'))
387 loop_body << ast.ExprStmt{
388 expr: ast.IfExpr{
389 cond: delta_lt_zero
390 stmts: [
391 ast.Stmt(ast.AssignStmt{
392 op: .assign
393 lhs: [ast.Expr(idx_ident)]
394 rhs: [
395 ast.Expr(ast.PrefixExpr{
396 op: .minus
397 expr: ast.BasicLiteral{
398 kind: .number
399 value: '1'
400 }
401 }),
402 ]
403 }),
404 ast.Stmt(ast.FlowControlStmt{
405 op: .key_continue
406 }),
407 ]
408 }
409 }
410
411 // if !DenseArray__has_index(&map_expr.key_values, _map_idx) { continue }
412 has_index_call := ast.CallExpr{
413 lhs: ast.Ident{
414 name: 'DenseArray__has_index'
415 }
416 args: [
417 ast.Expr(ast.PrefixExpr{
418 op: .amp
419 expr: key_values_expr
420 }),
421 ast.Expr(idx_ident),
422 ]
423 }
424 loop_body << ast.ExprStmt{
425 expr: ast.IfExpr{
426 cond: ast.PrefixExpr{
427 op: .not
428 expr: has_index_call
429 }
430 stmts: [ast.Stmt(ast.FlowControlStmt{
431 op: .key_continue
432 })]
433 }
434 }
435
436 // k := *(KeyType*)DenseArray__key(&map_expr.key_values, _map_idx)
437 // This is represented as a cast expression wrapping the call
438 if !key_is_blank && key_name != '' {
439 key_call := ast.CallExpr{
440 lhs: ast.Ident{
441 name: 'DenseArray__key'
442 }
443 args: [
444 ast.Expr(ast.PrefixExpr{
445 op: .amp
446 expr: key_values_expr
447 }),
448 ast.Expr(idx_ident),
449 ]
450 }
451 // Cast to KeyType* then dereference: *(KeyType*)call
452 key_cast := ast.CastExpr{
453 typ: ast.Ident{
454 name: '${key_type_name}*'
455 }
456 expr: key_call
457 }
458 key_deref := ast.PrefixExpr{
459 op: .mul
460 expr: key_cast
461 }
462 loop_body << ast.AssignStmt{
463 op: .decl_assign
464 lhs: [ast.Expr(ast.Ident{
465 name: key_name
466 })]
467 rhs: [ast.Expr(key_deref)]
468 }
469 // Clone string keys to avoid use-after-free when map mutations
470 // (delete/set) free the underlying string data during iteration.
471 if map_type.key_type is types.String {
472 loop_body << ast.AssignStmt{
473 op: .assign
474 lhs: [ast.Expr(ast.Ident{
475 name: key_name
476 })]
477 rhs: [
478 ast.Expr(ast.CallExpr{
479 lhs: ast.Ident{
480 name: 'string__clone'
481 }
482 args: [ast.Expr(ast.Ident{
483 name: key_name
484 })]
485 }),
486 ]
487 }
488 }
489 // Register key variable type in scope for later string detection
490 t.register_for_in_var_type(key_name, map_type.key_type)
491 }
492
493 // v := *(ValueType*)DenseArray__value(&map_expr.key_values, _map_idx)
494 if value_name != '' && value_name != '_' {
495 value_call := ast.CallExpr{
496 lhs: ast.Ident{
497 name: 'DenseArray__value'
498 }
499 args: [
500 ast.Expr(ast.PrefixExpr{
501 op: .amp
502 expr: key_values_expr
503 }),
504 ast.Expr(idx_ident),
505 ]
506 }
507 // Cast to ValueType* then dereference: *(ValueType*)call
508 value_cast := ast.CastExpr{
509 typ: ast.Ident{
510 name: '${value_type_name}*'
511 }
512 expr: value_call
513 }
514 value_deref := ast.PrefixExpr{
515 op: .mul
516 expr: value_cast
517 }
518 loop_body << ast.AssignStmt{
519 op: .decl_assign
520 lhs: [ast.Expr(ast.Ident{
521 name: value_name
522 })]
523 rhs: [ast.Expr(value_deref)]
524 }
525 // Register value variable type in scope for later type detection
526 t.register_for_in_var_type(value_name, map_type.value_type)
527 }
528
529 // Add the original body statements (NOT transformed here - transform_stmts will do it)
530 for body_stmt in stmt.stmts {
531 loop_body << body_stmt
532 }
533
534 // 2. Build the for loop:
535 // for _map_idx := 0; _map_idx < _map_len; _map_idx++ { ... }
536 loop_cond := t.make_infix_expr(.lt, ast.Expr(idx_ident), ast.Expr(len_ident))
537 next_idx := t.make_infix_expr(.plus, ast.Expr(idx_ident), t.make_number_expr('1'))
538 for_stmt := ast.ForStmt{
539 init: ast.AssignStmt{
540 op: .decl_assign
541 lhs: [ast.Expr(idx_ident)]
542 rhs: [ast.Expr(ast.BasicLiteral{
543 kind: .number
544 value: '0'
545 })]
546 }
547 cond: loop_cond
548 post: ast.AssignStmt{
549 op: .assign
550 lhs: [ast.Expr(idx_ident)]
551 rhs: [next_idx]
552 }
553 stmts: loop_body
554 }
555 stmts << for_stmt
556
557 return stmts
558}
559
560fn (mut t Transformer) transform_for_stmt(stmt ast.ForStmt) ast.ForStmt {
561 // Open a child scope for loop variables
562 t.open_scope()
563
564 // Check if this is a for-in loop (init is ForInStmt)
565 if stmt.init is ast.ForInStmt {
566 for_in := stmt.init as ast.ForInStmt
567 // Check for range expression: for i in 0..n
568 if for_in.expr is ast.RangeExpr {
569 result := t.transform_range_for_in(stmt, for_in, for_in.expr)
570 t.close_scope()
571 return result
572 }
573 // `for r in s.runes_iterator()` - lower as indexed string iteration.
574 if iter_base := t.runes_iterator_base_expr(for_in.expr) {
575 if base_type := t.for_in_iter_expr_type(iter_base) {
576 result := t.transform_array_for_in_with_value_type(stmt, ast.ForInStmt{
577 key: for_in.key
578 value: for_in.value
579 expr: iter_base
580 }, base_type, runes_iter_value_type())
581 t.close_scope()
582 return result
583 }
584 }
585 // Check if the for-in expression is smartcast to a specific type
586 // (e.g., `match size { []f64 { for v in size { ... } } }`)
587 if sc := t.find_smartcast_for_expr(t.expr_to_string(for_in.expr)) {
588 if orig_type := t.for_in_iter_expr_type(for_in.expr) {
589 if orig_type is types.SumType {
590 for variant in orig_type.variants {
591 variant_name := t.type_to_c_name(variant)
592 if variant_name == sc.variant_full || variant_name == sc.variant {
593 if variant is types.Array || variant is types.String {
594 result := t.transform_array_for_in(stmt, for_in, variant)
595 t.close_scope()
596 return result
597 }
598 break
599 }
600 }
601 }
602 }
603 }
604 if iter_type := t.for_in_iter_expr_type(for_in.expr) {
605 // Normalize pointer/alias wrappers so for-in lowering works for
606 // method receivers like `mut a []T` and aliased array types.
607 mut iter_base_type := iter_type
608 for {
609 if iter_base_type is types.Pointer {
610 ptr := iter_base_type as types.Pointer
611 iter_base_type = ptr.base_type
612 continue
613 }
614 if iter_base_type is types.Alias {
615 alias_t := iter_base_type as types.Alias
616 iter_base_type = alias_t.base_type
617 continue
618 }
619 break
620 }
621 // Fixed array - transform to indexed for loop with literal size
622 if iter_base_type is types.ArrayFixed {
623 arr_fixed := iter_base_type as types.ArrayFixed
624 result := t.transform_fixed_array_for_in(stmt, for_in, arr_fixed)
625 t.close_scope()
626 return result
627 }
628 // Dynamic array or string - transform to indexed for loop with .len.
629 // Keep these as separate type checks because `is A || is B` currently
630 // lowers incorrectly in cleanc self-host output.
631 if iter_base_type is types.Array {
632 result := t.transform_array_for_in(stmt, for_in, iter_base_type)
633 t.close_scope()
634 return result
635 }
636 if iter_base_type is types.String || t.is_string_iterable_type(iter_base_type) {
637 result := t.transform_array_for_in(stmt, for_in, iter_base_type)
638 t.close_scope()
639 return result
640 }
641 if t.is_native_be {
642 result := t.transform_untyped_for_in(stmt, for_in)
643 t.close_scope()
644 return result
645 }
646 // Other iterable types (maps, channels, etc): keep the ForInStmt form.
647 // The untyped indexed lowering below is only valid for array-like iterables.
648 value_type := t.for_in_value_type(iter_type)
649 if for_in.value is ast.Ident {
650 value_name := (for_in.value as ast.Ident).name
651 if value_name != '' && value_name != '_' {
652 t.register_for_in_var_type(value_name, value_type)
653 }
654 }
655 key_type := iter_type.key_type()
656 if for_in.key is ast.Ident {
657 key_name := (for_in.key as ast.Ident).name
658 if key_name != '' && key_name != '_' {
659 t.register_for_in_var_type(key_name, key_type)
660 }
661 }
662 transformed_stmts := t.transform_stmts(stmt.stmts)
663 result := ast.ForStmt{
664 init: ast.Stmt(ast.ForInStmt{
665 key: for_in.key
666 value: for_in.value
667 expr: t.transform_expr(for_in.expr)
668 })
669 cond: t.transform_expr(stmt.cond)
670 post: t.transform_stmt(stmt.post)
671 stmts: transformed_stmts
672 }
673 t.close_scope()
674 return result
675 }
676 // Keep lowering deterministic even when type info lookup fails for the
677 // iterable expression. This avoids leaking raw ForInStmt nodes to cleanc.
678 result := t.transform_untyped_for_in(stmt, for_in)
679 t.close_scope()
680 return result
681 }
682
683 // Check if the for-loop condition is an `is` check (e.g., `for x is Type { ... }`)
684 // and push smartcast for the loop body
685 mut loop_smartcasts := []SmartcastContext{}
686 for term in t.flatten_and_terms_unwrapped(stmt.cond) {
687 if term is ast.InfixExpr {
688 if ctx := t.smartcast_context_from_condition_term(term) {
689 loop_smartcasts << ctx
690 }
691 }
692 }
693 for ctx in loop_smartcasts {
694 t.push_smartcast_full(ctx.expr, ctx.variant, ctx.variant_full, ctx.sumtype)
695 }
696 transformed_stmts := t.transform_stmts(stmt.stmts)
697 for _ in loop_smartcasts {
698 t.pop_smartcast()
699 }
700
701 result := ast.ForStmt{
702 init: t.transform_stmt(stmt.init)
703 cond: t.transform_expr(stmt.cond)
704 post: t.transform_stmt(stmt.post)
705 stmts: transformed_stmts
706 }
707 t.close_scope()
708 return result
709}
710
711// transform_untyped_for_in lowers for-in loops when iterable type lookup fails.
712// It generates an indexed loop and leaves element type inference to later stages.
713fn (mut t Transformer) transform_untyped_for_in(stmt ast.ForStmt, for_in ast.ForInStmt) ast.ForStmt {
714 mut value_name := '_elem'
715 mut value_lhs := ast.Expr(ast.Ident{
716 name: value_name
717 })
718 mut is_mut_value := false
719 if for_in.value is ast.Ident {
720 value_name = for_in.value.name
721 value_lhs = ast.Expr(for_in.value)
722 } else if for_in.value is ast.ModifierExpr {
723 if for_in.value.expr is ast.Ident {
724 value_name = for_in.value.expr.name
725 value_lhs = ast.Expr(for_in.value.expr)
726 if for_in.value.kind == .key_mut {
727 is_mut_value = true
728 }
729 }
730 }
731
732 mut key_name := '_idx'
733 mut has_explicit_key := false
734 if for_in.key is ast.Ident {
735 if for_in.key.name != '_' {
736 key_name = for_in.key.name
737 has_explicit_key = true
738 }
739 } else if for_in.key is ast.ModifierExpr {
740 if for_in.key.expr is ast.Ident {
741 if for_in.key.expr.name != '_' {
742 key_name = for_in.key.expr.name
743 has_explicit_key = true
744 }
745 }
746 }
747 if !has_explicit_key {
748 key_name = '_idx_${value_name}'
749 }
750
751 idx_pos := t.next_synth_pos()
752 key_ident := ast.Ident{
753 name: key_name
754 pos: idx_pos
755 }
756 if int_obj := t.scope.lookup_parent('int', 0) {
757 int_type := int_obj.typ()
758 t.register_for_in_var_type(key_name, int_type)
759 t.register_synth_type(idx_pos, int_type)
760 }
761 iter_typ := t.get_expr_type(for_in.expr)
762 iter_pos := t.next_synth_pos()
763 mut transformed_expr := ast.Expr(ast.ParenExpr{
764 expr: t.transform_expr(for_in.expr)
765 pos: iter_pos
766 })
767 if typ := iter_typ {
768 t.register_synth_type(iter_pos, typ)
769 }
770 iter_pending := t.pending_stmts
771 t.pending_stmts = []ast.Stmt{}
772
773 index_pos := t.next_synth_pos()
774 if typ := t.get_expr_type(for_in.value) {
775 t.register_synth_type(index_pos, typ)
776 }
777
778 index_expr := ast.Expr(ast.IndexExpr{
779 lhs: transformed_expr
780 expr: key_ident
781 pos: index_pos
782 })
783 value_rhs := if is_mut_value {
784 ptr_pos := t.next_synth_pos()
785 if typ := t.get_expr_type(for_in.value) {
786 t.register_synth_type(ptr_pos, types.Type(types.Pointer{
787 base_type: typ
788 }))
789 }
790 ast.Expr(ast.PrefixExpr{
791 op: .amp
792 expr: index_expr
793 pos: ptr_pos
794 })
795 } else {
796 index_expr
797 }
798 value_assign := ast.AssignStmt{
799 op: .decl_assign
800 lhs: [value_lhs]
801 rhs: [value_rhs]
802 }
803
804 mut new_stmts := []ast.Stmt{cap: stmt.stmts.len + 1}
805 new_stmts << value_assign
806 transformed_body := t.transform_stmts(stmt.stmts)
807 new_stmts << transformed_body
808 body_pending := t.pending_stmts
809 t.pending_stmts = []ast.Stmt{}
810 t.pending_stmts << iter_pending
811 t.pending_stmts << body_pending
812
813 loop_cond := t.make_infix_expr(.lt, ast.Expr(key_ident), t.synth_selector(transformed_expr,
814 'len', types.Type(types.int_)))
815 return ast.ForStmt{
816 init: ast.AssignStmt{
817 op: .decl_assign
818 lhs: [ast.Expr(key_ident)]
819 rhs: [ast.Expr(ast.BasicLiteral{
820 value: '0'
821 kind: .number
822 })]
823 }
824 cond: loop_cond
825 post: ast.AssignStmt{
826 op: .plus_assign
827 lhs: [ast.Expr(key_ident)]
828 rhs: [ast.Expr(ast.BasicLiteral{
829 value: '1'
830 kind: .number
831 })]
832 }
833 stmts: new_stmts
834 }
835}
836
837fn (t &Transformer) runes_iterator_base_expr(expr ast.Expr) ?ast.Expr {
838 if expr is ast.CallExpr {
839 if expr.args.len == 0 && expr.lhs is ast.SelectorExpr
840 && expr.lhs.rhs.name == 'runes_iterator' {
841 return expr.lhs.lhs
842 }
843 }
844 if expr is ast.CallOrCastExpr {
845 if expr.lhs is ast.SelectorExpr && expr.lhs.rhs.name == 'runes_iterator'
846 && expr.expr is ast.EmptyExpr {
847 return expr.lhs.lhs
848 }
849 }
850 return none
851}
852
853// transform_array_for_in transforms `for x in arr` / `for i, x in arr` / `for c in str`
854// into: for (int _idx = 0; _idx < arr.len; _idx++) { T x = arr[_idx]; ... }
855fn (mut t Transformer) transform_array_for_in(stmt ast.ForStmt, for_in ast.ForInStmt, iter_type types.Type) ast.ForStmt {
856 return t.transform_array_for_in_with_value_type(stmt, for_in, iter_type,
857 t.for_in_value_type(iter_type))
858}
859
860fn (mut t Transformer) transform_array_for_in_with_value_type(stmt ast.ForStmt, for_in ast.ForInStmt, iter_type types.Type, value_type types.Type) ast.ForStmt {
861 mut value_name := '_elem'
862 mut value_lhs := ast.Expr(ast.Ident{
863 name: value_name
864 })
865 mut is_mut_value := false
866 if for_in.value is ast.Ident {
867 value_name = for_in.value.name
868 value_lhs = ast.Expr(for_in.value)
869 } else if for_in.value is ast.ModifierExpr {
870 if for_in.value.expr is ast.Ident {
871 value_name = for_in.value.expr.name
872 value_lhs = ast.Expr(for_in.value.expr)
873 if for_in.value.kind == .key_mut {
874 is_mut_value = true
875 }
876 }
877 }
878
879 mut key_name := '_idx'
880 mut has_explicit_key := false
881 if for_in.key is ast.Ident {
882 if for_in.key.name != '_' {
883 key_name = for_in.key.name
884 has_explicit_key = true
885 }
886 } else if for_in.key is ast.ModifierExpr {
887 if for_in.key.expr is ast.Ident {
888 if for_in.key.expr.name != '_' {
889 key_name = for_in.key.expr.name
890 has_explicit_key = true
891 }
892 }
893 }
894 if !has_explicit_key {
895 key_name = '_idx_${value_name}'
896 }
897
898 idx_pos := t.next_synth_pos()
899 key_ident := ast.Ident{
900 name: key_name
901 pos: idx_pos
902 }
903
904 // Register loop variables in scope
905 key_type := iter_type.key_type()
906 t.register_for_in_var_type(key_name, key_type)
907 t.register_for_in_var_type(value_name, value_type)
908 had_generic_value := value_name in t.generic_var_type_params
909 old_generic_value := t.generic_var_type_params[value_name] or { '' }
910 if placeholder := t.generic_iter_value_placeholder(for_in.expr) {
911 t.generic_var_type_params[value_name] = placeholder
912 }
913 t.register_synth_type(idx_pos, key_type)
914
915 iter_pos := t.next_synth_pos()
916 transformed_expr := t.iter_value_expr(for_in.expr, t.transform_expr(for_in.expr), iter_pos,
917 iter_type)
918 iter_pending := t.pending_stmts
919 t.pending_stmts = []ast.Stmt{}
920
921 index_pos := t.next_synth_pos()
922 t.register_synth_type(index_pos, value_type)
923
924 // Build: elem := ((T*)arr.data)[_idx] (or elem := &((T*)arr.data)[_idx] for mut).
925 // String indexing is still handled as `s[_idx]`, because the string payload field is `.str`.
926 index_expr := if t.is_string_iterable_type(iter_type)
927 || t.for_in_value_uses_array_index(value_type) {
928 ast.Expr(ast.IndexExpr{
929 lhs: transformed_expr
930 expr: key_ident
931 pos: index_pos
932 })
933 } else {
934 t.array_data_index_expr(transformed_expr, ast.Expr(key_ident), value_type, index_pos)
935 }
936 // For mut loop variables: take address for in-place mutation.
937 // But when the element type is already a pointer (e.g., []&MenuItem),
938 // the pointer itself allows mutation — don't add another & level.
939 value_is_ptr := value_type is types.Pointer
940 value_lhs_type := if is_mut_value && !value_is_ptr {
941 types.Type(types.Pointer{
942 base_type: value_type
943 })
944 } else {
945 value_type
946 }
947 value_decl_pos := t.next_synth_pos()
948 value_decl_lhs := ast.Expr(ast.Ident{
949 name: value_name
950 pos: value_decl_pos
951 })
952 t.register_synth_type(value_decl_pos, value_lhs_type)
953 t.register_for_in_lhs_type(value_lhs, value_lhs_type)
954 value_rhs := if is_mut_value && !value_is_ptr {
955 ptr_pos := t.next_synth_pos()
956 t.register_synth_type(ptr_pos, value_lhs_type)
957 // mut loop variable: take address for in-place mutation
958 ast.Expr(ast.PrefixExpr{
959 op: .amp
960 expr: index_expr
961 pos: ptr_pos
962 })
963 } else {
964 index_expr
965 }
966 value_assign := ast.AssignStmt{
967 op: .decl_assign
968 lhs: [value_decl_lhs]
969 rhs: [value_rhs]
970 }
971
972 mut new_stmts := []ast.Stmt{cap: stmt.stmts.len + 1}
973 new_stmts << value_assign
974 transformed_body := t.transform_stmts(stmt.stmts)
975 if had_generic_value {
976 t.generic_var_type_params[value_name] = old_generic_value
977 } else {
978 t.generic_var_type_params.delete(value_name)
979 }
980 new_stmts << transformed_body
981 body_pending := t.pending_stmts
982 t.pending_stmts = []ast.Stmt{}
983 t.pending_stmts << iter_pending
984 t.pending_stmts << body_pending
985
986 // Build: for (_idx := 0; _idx < arr.len; _idx++) { ... }
987 loop_cond := t.make_infix_expr(.lt, ast.Expr(key_ident), t.synth_selector(transformed_expr,
988 'len', types.Type(types.int_)))
989 return ast.ForStmt{
990 init: ast.AssignStmt{
991 op: .decl_assign
992 lhs: [ast.Expr(key_ident)]
993 rhs: [ast.Expr(ast.BasicLiteral{
994 value: '0'
995 kind: .number
996 })]
997 }
998 cond: loop_cond
999 post: ast.AssignStmt{
1000 op: .plus_assign
1001 lhs: [ast.Expr(key_ident)]
1002 rhs: [ast.Expr(ast.BasicLiteral{
1003 value: '1'
1004 kind: .number
1005 })]
1006 }
1007 stmts: new_stmts
1008 }
1009}
1010
1011// transform_range_for_in transforms `for i in start..end` into
1012// for (int i = start; i < end; i++) { ... }
1013fn (mut t Transformer) transform_range_for_in(stmt ast.ForStmt, for_in ast.ForInStmt, range ast.RangeExpr) ast.ForStmt {
1014 mut value_name := '_i'
1015 if for_in.value is ast.Ident {
1016 value_name = for_in.value.name
1017 } else if for_in.value is ast.ModifierExpr {
1018 if for_in.value.expr is ast.Ident {
1019 value_name = for_in.value.expr.name
1020 }
1021 }
1022
1023 if int_obj := t.scope.lookup_parent('int', 0) {
1024 t.register_for_in_var_type(value_name, int_obj.typ())
1025 }
1026
1027 cmp_op := if range.op == .ellipsis { token.Token.le } else { token.Token.lt } // `...` inclusive, `..` exclusive
1028
1029 mut new_stmts := []ast.Stmt{cap: stmt.stmts.len}
1030 transformed_body := t.transform_stmts(stmt.stmts)
1031 new_stmts << transformed_body
1032
1033 // Use the start/end expressions but strip original positions to avoid
1034 // env type misattribution (checker may register iterable type at start pos)
1035 start_expr := t.strip_pos(t.transform_expr(range.start))
1036 end_expr := t.strip_pos(t.transform_expr(range.end))
1037
1038 range_cond := t.make_infix_expr(cmp_op, ast.Expr(ast.Ident{
1039 name: value_name
1040 }), end_expr)
1041 return ast.ForStmt{
1042 init: ast.AssignStmt{
1043 op: .decl_assign
1044 lhs: [ast.Expr(ast.Ident{
1045 name: value_name
1046 })]
1047 rhs: [start_expr]
1048 }
1049 cond: range_cond
1050 post: ast.AssignStmt{
1051 op: .plus_assign
1052 lhs: [ast.Expr(ast.Ident{
1053 name: value_name
1054 })]
1055 rhs: [ast.Expr(ast.BasicLiteral{
1056 value: '1'
1057 kind: .number
1058 })]
1059 }
1060 stmts: new_stmts
1061 }
1062}
1063
1064// transform_fixed_array_for_in transforms `for elem in fixed_arr` to indexed for loop
1065// for i := 0; i < SIZE; i++ { elem := fixed_arr[i]; ... }
1066fn (mut t Transformer) transform_fixed_array_for_in(stmt ast.ForStmt, for_in ast.ForInStmt, arr_type types.ArrayFixed) ast.ForStmt {
1067 // Get value variable name
1068 mut value_name := '_elem'
1069 mut value_lhs := ast.Expr(ast.Ident{
1070 name: value_name
1071 })
1072 if for_in.value is ast.Ident {
1073 value_name = for_in.value.name
1074 value_lhs = ast.Expr(for_in.value)
1075 } else if for_in.value is ast.ModifierExpr {
1076 if for_in.value.expr is ast.Ident {
1077 value_name = for_in.value.expr.name
1078 value_lhs = ast.Expr(for_in.value.expr)
1079 }
1080 }
1081
1082 // Get key variable name (index)
1083 mut key_name := '_idx'
1084 mut has_explicit_key := false
1085 if for_in.key is ast.Ident {
1086 if for_in.key.name != '_' {
1087 key_name = for_in.key.name
1088 has_explicit_key = true
1089 }
1090 } else if for_in.key is ast.ModifierExpr {
1091 if for_in.key.expr is ast.Ident {
1092 if for_in.key.expr.name != '_' {
1093 key_name = for_in.key.expr.name
1094 has_explicit_key = true
1095 }
1096 }
1097 }
1098
1099 // Use unique hidden index if no key specified
1100 if !has_explicit_key {
1101 key_name = '_idx_${value_name}'
1102 }
1103 idx_pos := t.next_synth_pos()
1104 key_ident := ast.Ident{
1105 name: key_name
1106 pos: idx_pos
1107 }
1108
1109 // Register loop variables in scope
1110 key_type := types.Type(arr_type).key_type()
1111 value_type := types.Type(arr_type).value_type()
1112 t.register_for_in_var_type(key_name, key_type)
1113 t.register_for_in_var_type(value_name, value_type)
1114 t.register_synth_type(idx_pos, key_type)
1115
1116 // Transform the iterable expression
1117 iter_pos := t.next_synth_pos()
1118 transformed_expr := t.iter_value_expr(for_in.expr, t.transform_expr(for_in.expr), iter_pos,
1119 types.Type(arr_type))
1120
1121 index_pos := t.next_synth_pos()
1122 t.register_synth_type(index_pos, value_type)
1123
1124 // Build: elem := fixed_arr[i]
1125 value_decl_pos := t.next_synth_pos()
1126 value_decl_lhs := ast.Expr(ast.Ident{
1127 name: value_name
1128 pos: value_decl_pos
1129 })
1130 t.register_synth_type(value_decl_pos, value_type)
1131 t.register_for_in_lhs_type(value_lhs, value_type)
1132 value_assign := ast.AssignStmt{
1133 op: .decl_assign
1134 lhs: [value_decl_lhs]
1135 rhs: [
1136 ast.Expr(ast.IndexExpr{
1137 lhs: transformed_expr
1138 expr: key_ident
1139 pos: index_pos
1140 }),
1141 ]
1142 }
1143
1144 // Prepend value assignment to loop body
1145 mut new_stmts := []ast.Stmt{cap: stmt.stmts.len + 1}
1146 new_stmts << value_assign
1147 transformed_body := t.transform_stmts(stmt.stmts)
1148 new_stmts << transformed_body
1149
1150 // Build: for i := 0; i < SIZE; i++ { ... }
1151 fixed_cond := t.make_infix_expr(.lt, ast.Expr(key_ident), t.make_number_expr('${arr_type.len}'))
1152 return ast.ForStmt{
1153 init: ast.AssignStmt{
1154 op: .decl_assign
1155 lhs: [ast.Expr(key_ident)]
1156 rhs: [ast.Expr(ast.BasicLiteral{
1157 value: '0'
1158 kind: .number
1159 })]
1160 }
1161 cond: fixed_cond
1162 post: ast.AssignStmt{
1163 op: .plus_assign
1164 lhs: [ast.Expr(key_ident)]
1165 rhs: [ast.Expr(ast.BasicLiteral{
1166 value: '1'
1167 kind: .number
1168 })]
1169 }
1170 stmts: new_stmts
1171 }
1172}
1173
1174// strip_pos creates a copy of a simple expression with pos=0 so that
1175// the cleanc env type lookup won't misattribute the original checker type.
1176fn (t &Transformer) strip_pos(e ast.Expr) ast.Expr {
1177 match e {
1178 ast.BasicLiteral {
1179 return ast.BasicLiteral{
1180 value: e.value
1181 kind: e.kind
1182 }
1183 }
1184 ast.Ident {
1185 return ast.Ident{
1186 name: e.name
1187 }
1188 }
1189 ast.PrefixExpr {
1190 return ast.PrefixExpr{
1191 op: e.op
1192 expr: t.strip_pos(e.expr)
1193 }
1194 }
1195 ast.CastExpr {
1196 return ast.CastExpr{
1197 typ: e.typ
1198 expr: t.strip_pos(e.expr)
1199 }
1200 }
1201 else {
1202 return e
1203 }
1204 }
1205}
1206
1207fn (mut t Transformer) transform_for_in_stmt(stmt ast.ForInStmt) ast.ForStmt {
1208 // ForInStmt is only a ForStmt initializer in v2 AST; lower stray ForInStmt
1209 // nodes through the regular for-loop transformer path.
1210 return t.transform_for_stmt(ast.ForStmt{
1211 init: ast.Stmt(stmt)
1212 })
1213}
1214