vq / vlib / v2_toberemoved / transformer / flat_write.v
13734 lines · 13301 sloc · 551.92 KB
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module transformer
5
6import v2.ast
7import v2.token
8import v2.types
9
10// flat_write.v scaffolds the "transformer writes flat directly" multi-session
11// port. The wedge `transform_files_to_flat` (transformer.v ~line 1855) today
12// composes the legacy per-file transform with a boundary `ast.flatten_files()`
13// so callers can adopt the flat-output API now. The peak-memory win documented
14// in project_v2_flat_migration.md only materialises when the per-file rewrite
15// sites stop materialising legacy `ast.File` / `ast.Stmt` / `ast.Expr` values
16// and emit `FlatNode`s into the supplied `ast.FlatBuilder` directly.
17//
18// The entry point is `transform_file_index_to_flat` below. Its body is the
19// staging ground for the port: every session in the plan replaces one rewrite
20// site inside `transform_file` with a flat-emitting equivalent, leaving the
21// outer signature stable. The differential harness in
22// transformer_flat_diff_test.v pins both the wedge invariant (4th row,
23// `to_flat_*`) and the per-file invariant (5th row, `per_file_*`) so each
24// session's change must keep both signatures bit-equal.
25//
26// ----- Migration phases -----
27//
28// The 55-site audit from session 1 is the inventory of work, but the port
29// itself decomposes into roughly six phases. Each phase ships one
30// architectural seam; the per-rewrite-site ports of phase 4 are then
31// mechanical follow-ups that plug into the seams.
32//
33// Phase 1 (DONE, session 1): scaffolding.
34// `transform_file_index_to_flat` exists. Harness 5th row pins per-file
35// parity against a manual rehydrate+transform+append loop. Body is the
36// identity decomposition: rehydrate + transform_file + append_file.
37//
38// Phase 2 (DONE, session 2): file-level emission decomposition.
39// `ast.FlatBuilder.append_file_with_stmt_ids` accepts pre-emitted stmt
40// FlatNodeIds for the file's stmt list. `transform_file_index_to_flat`
41// now mirrors `transform_file`'s prologue itself, runs `transform_stmts`
42// for the body, and emits each transformed stmt via the new per-stmt
43// seam (`transform_stmt_to_flat` → `out.emit_stmt(...)`). The post-stmt
44// file root is assembled via `append_file_with_stmt_ids`. The per-stmt
45// seam is where phases 3..5 plug per-variant direct-emit logic.
46//
47// Phase 3 (DONE, session 3): per-stmt direct-emit dispatch.
48// `transform_file_index_to_flat` now bypasses `transform_stmts` at the
49// file level (top-level stmts never trigger its multi-stmt expansions —
50// all those paths live in function bodies). `transform_stmt_to_flat`
51// carries an 11-variant leaf-arm match for stmt kinds that are identity
52// in `transform_stmt`'s `else { stmt }` case (AsmStmt, Directive,
53// EmptyStmt, EnumDecl, FlowControlStmt, ImportStmt, InterfaceDecl,
54// ModuleStmt, StructDecl, TypeDecl, []Attribute); these direct-emit and
55// skip `transform_stmt` entirely. Non-leaf arms take the legacy
56// round-trip `out.emit_stmt(t.transform_stmt(stmt))`. The dispatch
57// surface is now ready for phase 4 ports.
58//
59// Phase 4 (IN PROGRESS — per-rewrite-site ports).
60// For each non-leaf Stmt variant (AssignStmt, BlockStmt, ConstDecl,
61// DeferStmt, ExprStmt, FnDecl, ForStmt, ForInStmt, GlobalDecl,
62// ComptimeStmt, LabelStmt, ReturnStmt, AssertStmt) rewrite the rewrite
63// logic inside `transform_X` to emit `FlatNode`s via `out.emit(...)`
64// directly instead of constructing legacy `ast.Stmt` / `ast.Expr`
65// values. The inventory below is the checklist.
66//
67// Session 1 (2026-05-26): expression dispatch surface + GlobalDecl port.
68// `transform_expr_to_flat` mirrors `transform_stmt_to_flat`'s shape with
69// 9 leaf-arm direct-emit variants (BasicLiteral, EmptyExpr, Keyword,
70// LifetimeExpr, RangeExpr, SelectExpr, StringLiteral, Tuple, Type — all
71// identity in `transform_expr`'s `else { expr }` case); non-leaf arms
72// take the legacy round-trip `out.emit_expr(t.transform_expr(expr))`.
73// The GlobalDecl arm in `transform_stmt_to_flat` direct-emits via
74// `transform_expr_to_flat` for each field's typ/value plus the new
75// `emit_field_decl_by_ids` / `emit_global_decl_by_ids` builder helpers,
76// skipping the `transform_global_decl` round-trip entirely. The
77// `__global init` fixture in the 5th harness row pins it.
78//
79// Session 2 (2026-05-26): ConstDecl port + harness fixture.
80// The ConstDecl arm in `transform_stmt_to_flat` direct-emits via
81// `transform_expr_to_flat` for each field's value plus the new
82// `emit_field_init_by_id` / `emit_const_decl_by_ids` builder helpers,
83// skipping the `transform_const_decl` round-trip entirely. Mirrors the
84// GlobalDecl shape but uses FieldInit (name + value only, no typ/attrs)
85// and carries the `is_public` flag. A new `fixture_const_decl` covers
86// leaf (BasicLiteral, StringLiteral) and non-leaf (InfixExpr) const
87// values across all 5 harness rows.
88//
89// Session 3 (2026-05-26): FnDecl dispatch arm (shape port).
90// The FnDecl arm in `transform_stmt_to_flat` calls `transform_fn_decl`
91// for the body work (prologue / scope setup / `transform_stmts` /
92// defer lowering — the next port target) but decomposes the resulting
93// `ast.FnDecl` and emits via the new `emit_parameter` / `emit_type` /
94// `emit_fn_decl_by_ids` builder helpers. Body stmts go through
95// `out.emit_stmt(body_stmt)` per stmt and the list is assembled via
96// `emit_aux_list_from_ids`. Bit-equal to the legacy
97// `out.emit_stmt(t.transform_fn_decl(stmt))` round-trip; the value is
98// that the dispatch arm now exists, so the next session can refactor
99// `transform_fn_decl` to emit body stmts straight into the builder
100// without touching this dispatch site. Every existing fixture
101// exercises this arm — all 46 harness tests pin it.
102//
103// Session 4 (2026-05-26): FnDecl wrapper-struct elision.
104// Extracts `transform_fn_decl_parts(decl) (attrs, stmts)` in fn.v as
105// the body-work driver — returns the two variable parts of the lowered
106// FnDecl (final attribute list and final transformed + defer-lowered
107// stmts) and leaves the immutable name / typ / receiver / language /
108// is_public / is_method / is_static / pos fields to be re-attached by
109// the caller. Existing `transform_fn_decl` becomes a thin wrapper that
110// calls the helper and assembles the `ast.FnDecl` (no behavioural
111// change for legacy callers). The FnDecl flat-write arm now calls
112// `transform_fn_decl_parts` directly, so the `ast.FnDecl` wrapper
113// struct that session 3 built and then decomposed is never allocated.
114// First real (if modest) memory saving in the flat-write port: one
115// FnDecl struct per fn under flat-output paths.
116//
117// Session 5 (2026-05-26): ParenExpr expr direct-emit + harness fixture.
118// The ParenExpr arm in `transform_expr_to_flat` direct-emits via a
119// recursive `transform_expr_to_flat` for the inner expression plus the
120// new `emit_paren_expr_by_id` builder helper, skipping the
121// `ast.ParenExpr` struct allocation per occurrence (the `transform_expr`
122// ParenExpr arm just recurses + rebuilds the wrapper — identity in
123// shape). First non-leaf *expression* port — paves the way for porting
124// larger expression rewrites (PrefixExpr, CastExpr, InfixExpr ...) into
125// the dispatch surface. New `fixture_paren_expr` (file-scope consts
126// wrapping leaf and nested-paren values) covers the arm across all 5
127// harness rows — 46 → 51 transformer-diff tests.
128//
129// Session 6 (2026-05-26): PrefixExpr expr direct-emit + harness fixture.
130// The PrefixExpr arm in `transform_expr_to_flat` direct-emits via a
131// recursive `transform_expr_to_flat` for the inner expression plus the
132// new `emit_prefix_expr_by_id` builder helper, skipping the
133// `ast.PrefixExpr` struct allocation for the common case (unary `-`,
134// `~`, `!`). Two legacy fallback guards keep semantics intact:
135// `expr.op == .amp` (transform_prefix_expr removes `&` redundancy with
136// attribute-aware lowering — not bit-equal to a structural rebuild) and
137// `expr.op == .arrow && expr.expr is ast.OrExpr` (channel-recv with
138// or-block is lowered by `expand_chan_recv_or_expr` — full
139// statement-list synthesis). Both stay on the round-trip
140// `out.emit_expr(t.transform_expr(expr))` path. New
141// `fixture_prefix_expr` (file-scope consts with `-`, `~`, `!`) covers
142// the arm across all 5 harness rows — 51 → 56 transformer-diff tests.
143//
144// Session 7 (2026-05-26): ModifierExpr expr direct-emit + harness fixture.
145// The ModifierExpr arm in `transform_expr_to_flat` direct-emits via a
146// recursive `transform_expr_to_flat` for the inner expression plus the
147// new `emit_modifier_expr_by_id` builder helper, skipping the
148// `ast.ModifierExpr` struct allocation per occurrence (`mut x`,
149// `shared x`, `atomic x`, `static x`, `volatile x` — all pure wrappers
150// around the inner expression in `transform_expr`'s arm; kind + pos
151// copied verbatim). Structurally identical to the ParenExpr session
152// except the kind token packs into the meta u16 (mirror of
153// `add_expr(ModifierExpr)`'s encoding). New `fixture_modifier_expr`
154// (a `mut`-parameter fn + a caller that passes `mut a`) covers the arm
155// across all 5 harness rows — 56 → 61 transformer-diff tests.
156//
157// Session 8 (2026-05-26): LambdaExpr expr direct-emit + harness fixture.
158// The LambdaExpr arm in `transform_expr_to_flat` direct-emits via a
159// recursive `transform_expr_to_flat` for the body expression plus a
160// leaf `out.emit_expr(ast.Expr(arg))` per arg Ident and the new
161// `emit_lambda_expr_by_ids` builder helper. The `transform_expr` arm
162// for LambdaExpr is a pure wrapper — args are an `[]Ident` copied
163// verbatim, only the body `expr.expr` is transformed — so direct-emit
164// mirrors that exactly: leaf args go through the legacy `emit_expr`
165// leaf path (Idents are identity), body goes through the recursive
166// direct-emit. Mirrors `add_expr(LambdaExpr)` encoding exactly:
167// edge[0] = body expr, edge[1..] = args. New `fixture_lambda_expr`
168// (an `apply(f fn (int) int, x int)` higher-order fn + a caller that
169// passes `|y| y + 1`) covers the arm across all 5 harness rows —
170// 61 → 66 transformer-diff tests.
171//
172// Session 9 (2026-05-26): FnLiteral expr direct-emit + harness fixture.
173// The FnLiteral arm in `transform_expr_to_flat` direct-emits via:
174// `out.emit_type(ast.Type(expr.typ))` for the FnType (verbatim, leaf),
175// `out.emit_expr(cv)` per captured_var (verbatim, leaf), and
176// `transform_stmts(expr.stmts)` + `out.emit_stmt(...)` per result for
177// the body stmts (still allocates a `[]Stmt` — the stmt-list expansion
178// seam is the next big port target), then the new
179// `emit_fn_literal_by_ids` builder helper. Skips the
180// `ast.FnLiteral` wrapper struct allocation per occurrence. Mirrors
181// `add_expr(FnLiteral)` encoding exactly: edge[0] = type,
182// edge[1..1+captured.len] = captured_vars, edge[1+captured.len..] =
183// stmts; `captured_vars.len` is packed into `extra`. New
184// `fixture_fn_literal` (a `make_doubler()` returning `fn (x int) int`
185// + a `use_fn_literal()` that binds a `fn (n int) int` to a local and
186// calls it) covers the arm across all 5 harness rows — 66 → 71
187// transformer-diff tests.
188//
189// Session 10 (2026-05-26): PostfixExpr expr direct-emit + harness fixture.
190// The PostfixExpr arm in `transform_expr_to_flat` direct-emits via a
191// recursive `transform_expr_to_flat` for the inner expression plus the
192// new `emit_postfix_expr_by_id` builder helper. Skips the
193// `ast.PostfixExpr` struct allocation for the plain postfix ops (`++`
194// / `--`, i.e. `.inc` / `.dec`). One legacy fallback guard:
195// `expr.op == .not || expr.op == .question` — these ops trigger
196// non-trivial lowering in `transform_expr` (native backends keep
197// postfix; non-native rewrite `expr!` / `expr?` into a CastExpr over
198// the inner Result/Option base type plus string-range rename
199// `substr` → `substr_checked` plus unwrap fallback). These produce
200// different expression shapes, so they stay on the legacy round-trip.
201// New `fixture_postfix_expr` (a fn that mut-binds a local then runs
202// `a++` / `a--`) covers the arm across all 5 harness rows — 71 → 76
203// transformer-diff tests.
204//
205// Session 11 (2026-05-26): CastExpr expr direct-emit + harness fixture.
206// The CastExpr arm in `transform_expr_to_flat` direct-emits via the
207// leaf `out.emit_expr(expr.typ)` (type is NOT transformed by
208// `transform_expr` — copied verbatim) plus a recursive
209// `transform_expr_to_flat` for the inner expression, then assembles
210// via the new `emit_cast_expr_by_ids` builder helper. Skips the
211// `ast.CastExpr` struct allocation per occurrence in the common case
212// (primitive casts: `int(x)`, `f64(y)`, `u8(z)`, etc.).
213// One legacy fallback guard: `t.type_expr_name_full(expr.typ)`
214// resolves to a name that `t.is_sum_type(name)` accepts. In that case
215// `transform_expr` may lower the cast into an explicit sum-type
216// initialisation (CallExpr to `<SumType>__from_<Variant>` / InitExpr),
217// which is a *different* expression shape. Both helpers are read-only
218// so the guard is cheap to evaluate. The existing
219// `fixture_sumtype_is_as` already exercises the fallback (it contains
220// `c := Shape(Circle{r: 2.0})`); new `fixture_cast_expr` (primitive
221// casts only) pins the fast path across all 5 harness rows — 76 → 81
222// transformer-diff tests.
223//
224// Session 12 (2026-05-26): FieldInit expr direct-emit + harness fixture.
225// The FieldInit arm in `transform_expr_to_flat` direct-emits via a
226// recursive `transform_expr_to_flat` for the inner `value` plus the
227// existing `emit_field_init_by_id` builder helper (the same helper
228// ConstDecl uses for its aux_field_init child node — `add_expr(FieldInit)`
229// and `add_field_init(field)` produce bit-equal encodings so a single
230// emit helper covers both call sites). Skips the `ast.FieldInit` struct
231// allocation per occurrence — `transform_expr`'s FieldInit arm just
232// transforms `value` and copies `name` verbatim. FieldInit reaches the
233// expr dispatch when it appears as a standalone call arg in struct-
234// shorthand / named-arg syntax (`foo(name: value)`). New
235// `fixture_field_init` (struct + a `make(name: 'a', n: 1)` call) covers
236// the arm across all 5 harness rows — 81 → 86 transformer-diff tests.
237//
238// Session 13 (2026-05-26): AsCastExpr expr direct-emit + harness fixture.
239// The AsCastExpr arm in `transform_expr_to_flat` direct-emits via the
240// leaf `out.emit_expr(expr.typ)` (type is NOT transformed by
241// `transform_expr` — copied verbatim) plus a recursive
242// `transform_expr_to_flat` for the inner expression, then assembles via
243// the new `emit_as_cast_expr_by_ids` builder helper. First port that
244// mutates Transformer state: to keep the `as` cast intact (a same-
245// expression smartcast would rewrite the operand into a direct sum
246// payload access and hide the original storage type from the backend),
247// the arm clones `smartcast_stack` + `smartcast_expr_counts`, drains
248// matching smartcast entries via a bounded `remove_smartcast_for_expr`
249// loop, transforms the inner expression with the drained state, then
250// restores the snapshot. Direct-emit mirrors the legacy prologue/restore
251// exactly so the snapshot semantics stay identical — the only thing
252// that changes is the inner recursion target (flat vs legacy). Skips
253// the `ast.AsCastExpr` struct allocation per occurrence. New
254// `fixture_as_cast_expr` (sum type Shape + `(s as Circle).r` / `(s as
255// Square).side` arms in a `pick(s Shape, k int) f64` fn) covers the
256// arm across all 5 harness rows — 86 → 91 transformer-diff tests.
257//
258// Session 14 (2026-05-26): UnsafeExpr expr direct-emit + harness fixture.
259// The UnsafeExpr arm in `transform_expr_to_flat` has two paths matching
260// `transform_expr`'s arm:
261// 1. `unsafe { nil }` normalises to a plain `nil` Ident (detected via
262// `t.is_unsafe_nil_expr(expr)`) — emitted as a leaf Ident through
263// `out.emit_expr(...)`. Identity in `transform_expr`'s else case.
264// 2. otherwise transform `expr.stmts` via `transform_stmts` and emit
265// each result via `out.emit_stmt(...)`, then assemble the flat node
266// via the new `emit_unsafe_expr_by_ids` builder helper. Mirrors
267// `add_expr(UnsafeExpr)` encoding exactly (edges are body stmts in
268// order). Skips the `ast.UnsafeExpr` struct allocation per non-nil
269// occurrence.
270// The body stmt-list is still materialised because `transform_stmts`
271// returns one (the stmt-list expansion seam is the next big port
272// target). New `fixture_unsafe_expr` (a `mut p := unsafe { &int(0) }`
273// with a non-trivial body + plain `unsafe { nil }` for the
274// normalisation arm) covers both paths across all 5 harness rows —
275// 91 → 96 transformer-diff tests.
276//
277// Session 15 (2026-05-26): LockExpr expr direct-emit + harness fixture.
278// The LockExpr arm in `transform_expr_to_flat` rewrites
279// `lock x { body }` / `rlock x { body }` into a sequence of mutex
280// lock/unlock calls wrapped in an UnsafeExpr (compound expression).
281// `expand_lock_expr` produces `[lock_calls..., body..., unlock_calls...]`;
282// the legacy arm duplicates the last body stmt after the unlock calls so
283// that GCC compound-expression value semantics return the body's tail
284// value instead of `void`. Direct-emit mirrors the rewrite exactly —
285// same `expand_lock_expr` call, same duplicate-last fix-up, then emits
286// each stmt via `out.emit_stmt(...)` and assembles via the existing
287// `emit_unsafe_expr_by_ids` builder helper (the same helper UnsafeExpr
288// session 14 uses, since the legacy LockExpr arm produces an UnsafeExpr).
289// Crucial encoding detail: the legacy `ast.UnsafeExpr{stmts: stmts}` is
290// constructed with no `pos` field (defaults to zero), so direct-emit must
291// pass `token.Pos{}` (not `expr.pos`). First port to import `v2.token`
292// in `flat_write.v` — needed for the zero-pos literal. New
293// `fixture_lock_expr` (a `Counter` struct with `mut value int` + a
294// `bump(mut c Counter) int` that uses `lock c { c.value += 1; c.value }`)
295// covers the arm across all 5 harness rows — 96 → 101 transformer-diff
296// tests.
297//
298// Session 16 (2026-05-26): OrExpr expr direct-emit + harness fixture.
299// The OrExpr arm in `transform_expr_to_flat` direct-emits the expression-
300// context or-block (the statement-level sites have dedicated handlers and
301// never reach here). `expand_single_or_expr` returns the data-access
302// expression that stands in for the or-block value and appends prefix
303// stmts (typed temp init, optional-state branch). When `prefix_stmts` is
304// non-empty, the legacy arm wraps everything in an `UnsafeExpr` (GCC
305// compound expression) — direct-emit mirrors that exactly via
306// `emit_unsafe_expr_by_ids` (same helper LockExpr session 15 uses),
307// passing `token.Pos{}` since the legacy wrapper has no explicit pos.
308// When empty, the result expression goes through `out.emit_expr(...)`
309// (leaf). Skips the `ast.UnsafeExpr` wrapper allocation per occurrence
310// in the compound-expression case. New `fixture_or_expr` (a fn
311// returning `pair(maybe(n) or { -1 }, 2)` — or-block nested in call
312// args) covers the arm across all 5 harness rows — 101 → 106
313// transformer-diff tests.
314//
315// Session 17 (2026-05-26): IfGuardExpr expr direct-emit (degenerate arm).
316// IfGuardExpr only appears in normal usage as an IfExpr condition, where
317// `transform_if_expr` handles it (statement form via
318// `try_expand_if_guard_stmt`, expression form via the expr.v ~line 1371
319// site). The arm in `transform_expr_to_flat` is the degenerate standalone-
320// reach case: legacy `transform_expr` returns
321// `t.transform_expr(expr.stmt.rhs[0])` when `rhs` is non-empty, otherwise
322// returns `expr` unchanged. Direct-emit mirrors that — recursive
323// `transform_expr_to_flat` for the rhs[0] case, leaf `out.emit_expr(...)`
324// for the fallback. No new fixture: IfGuardExpr is unreachable in
325// standalone position in practice, so no diff-harness fixture exercises
326// it; the port is a completeness sweep that keeps the dispatch surface
327// coherent with the legacy `transform_expr` shape. All 106 existing
328// transformer-diff tests continue to pass.
329//
330// Session 18 (2026-05-26): SelectorExpr deep helper port + fixture.
331// First "deep helper rewrite" port (per the planning conversation that
332// followed session 17). The full body of `transform_selector_expr`
333// (~130 lines in expr.v) is mirrored as `transform_selector_expr_to_flat`
334// in this file. Special-case branches that rewrite the selector into a
335// non-selector shape — typeof.name/idx (3 sub-branches: KeywordOperator,
336// CallExpr, CallOrCastExpr lhs), sumtype representation fields
337// (`_tag` / `_data` / `_*` chained under `_data`), smartcast match
338// (direct + field-access), `os.args` → `arguments()`, module-qualified
339// enum (`mod.Type.value` → `mod__Type__value` Ident or nested module
340// refs), and same-module enum (`Op.value` → `mod__Op__value` Ident) —
341// all build the legacy `ast.Expr` and emit via `out.emit_expr(...)`
342// (their results are Idents, BasicLiterals, or CallExprs that route
343// through `add_expr`'s normal encoding). Only the default `x.field`
344// path is direct-emit: recurse on lhs via `transform_expr_to_flat`,
345// emit rhs Ident via the leaf `out.emit_expr(...)`, and assemble via
346// the new `emit_selector_expr_by_ids` builder helper, skipping the
347// `ast.SelectorExpr` struct allocation per default-path occurrence.
348//
349// Adds `import v2.types` to this file (needed for the `typ is types.Enum`
350// checks in the module-qualified / same-module enum branches). New
351// `fixture_selector_expr` exercises the default path with chained
352// selectors (`b.p.x`, `b.p.y`), non-Ident lhs (`arr[0].y` — IndexExpr),
353// and a selector-LHS assignment (`b.p.y = 42`) — 106 → 111 transformer-
354// diff tests.
355//
356// Pattern note: this is the first port where the dispatch arm delegates
357// to a dedicated `transform_*_to_flat` helper instead of inlining the
358// logic in the match arm itself. Established because the legacy helper
359// has too many branches to inline (~130 lines of special-case
360// dispatch + lookup). Subsequent deep-helper ports (CallExpr, IfExpr,
361// InfixExpr, ...) will follow the same shape: one helper per legacy
362// `transform_*` with the same structure, special-case branches routed
363// through `emit_expr` for their non-default Expr results, default-path
364// direct-emit via new `emit_*_by_ids` builder helpers.
365//
366// Session 19 (2026-05-26): IndexExpr deep helper port + fixture.
367// Second deep-helper rewrite, same shape as session 18. The body of
368// `transform_index_expr` (~140 lines in expr.v) is mirrored as
369// `transform_index_expr_to_flat`. Two branches produce non-IndexExpr
370// shapes and route through the legacy round-trip:
371// (a) Slice lowering (`expr.expr is ast.RangeExpr`) →
372// `transform_slice_index_expr` produces a CallExpr to
373// `array_slice` / `string_substr`.
374// (b) Map index on a non-eval backend → `map__get` lowering produces
375// an `ast.UnsafeExpr` wrapping prefix temps + cast + deref.
376// Map detection mirrors the legacy sequence exactly: `map_index_lhs_type`
377// → fallback to `lookup_var_type` for Ident lhs → `unwrap_map_type`.
378// All three lookups are immutable `&Transformer` calls so running them
379// up-front to decide dispatch is side-effect-free. Eval-backend map
380// case rebuilds an IndexExpr verbatim — falls through to direct-emit.
381//
382// The gated path (`arr#[i]`), eval-backend map path, and default
383// `arr[i]` path all rebuild an IndexExpr with `lhs` and `expr`
384// recursively transformed and `is_gated` copied verbatim — direct-emit
385// via the new `emit_index_expr_by_ids` builder helper (`is_gated` flag
386// packed into the flags byte to match `add_expr(IndexExpr)` encoding
387// exactly). Skips the `ast.IndexExpr` struct allocation on the common
388// path.
389//
390// New `fixture_index_expr` exercises all four paths in one fn: default
391// `arr[0] + arr[1]`, gated `arr#[0]`, map index `m['key']`, and slice
392// `arr[1..3].len` — 111 → 116 transformer-diff tests.
393//
394// Session 20 (2026-05-26): ComptimeExpr deep helper port + fixture.
395// Third deep-helper rewrite, same shape as sessions 18/19. The body of
396// `transform_comptime_expr` (~50 lines in expr.v) is mirrored as
397// `transform_comptime_expr_to_flat`. Most branches lower the comptime
398// form into a non-ComptimeExpr shape — eval_comptime_if produces an
399// IfExpr result; `$res(...)` (both CallExpr and CallOrCastExpr lhs)
400// becomes a BasicLiteral `false`; `$embed_file(...)` (both forms)
401// becomes an InitExpr or chained CallExpr via
402// `transform_embed_file_comptime_expr`; `@VMODROOT`/`VMODROOT` Ident
403// becomes a StringLiteral via `vmodroot_string_literal`; the
404// `transform_embed_file_comptime_chain` rescue produces a non-comptime
405// shape — all six branches route through `out.emit_expr(...)`. Only
406// the default `ast.ComptimeExpr{expr: transform(inner), pos}` path is
407// direct-emit via the new `emit_comptime_expr_by_id` builder helper.
408// Skips the `ast.ComptimeExpr` struct allocation on the rare
409// default-path occurrence.
410//
411// New `fixture_comptime_expr` exercises a `$if linux { return 1 }`
412// stmt body (the IfExpr branch — legacy fallback) — 116 → 121
413// transformer-diff tests.
414//
415// Session 21 (2026-05-26): InitExpr deep helper port + fixture.
416// Fourth deep-helper rewrite, same shape as sessions 18-20. The body of
417// `transform_init_expr` (~220 lines in struct.v) is large and complex —
418// the per-field transformation involves sumtype wrapping,
419// ArrayInitExpr special-casing, expected-type resolution, and
420// missing-default fill-in via `add_missing_struct_field_defaults`. Only
421// one branch produces a non-InitExpr shape: the typed empty map
422// `map[K]V{}` lowers to a CallExpr to `new_map` (or `MapInitExpr` on the
423// eval backend) — that branch routes through `out.emit_expr(...)`. The
424// default path delegates the field work to legacy
425// `transform_init_expr` (its result is an `ast.InitExpr` with the same
426// `typ` and a fully-transformed fields list) and then direct-emits via
427// the new `emit_init_expr_by_ids` builder helper together with
428// `emit_field_init_by_id` per field. This skips the outer
429// `ast.InitExpr` wrapper allocation per occurrence; the per-field
430// `ast.FieldInit` wrappers still materialise on the default path (the
431// legacy function builds them). A later session can inline the
432// per-field loop directly to also skip those — that's a much bigger
433// port and not required to close this dispatch arm.
434//
435// New `fixture_init_expr` exercises two struct types and a nested
436// struct literal with both fully-specified fields and a field with a
437// declared default (`tag string = "default"`) — the missing-default
438// fill-in path. 121 → 126 transformer-diff tests.
439//
440// Session 22 (2026-05-26): ReturnStmt port + fixture.
441// First stmt-level port since session 3 (FnDecl). `transform_return_stmt`
442// (~110 lines in transformer.v) always returns an `ast.ReturnStmt`
443// (never lowers to a different shape). Its body does per-expression
444// sumtype wrapping (`should_wrap_return_sumtype`), enum-shorthand
445// resolution (`resolve_enum_shorthand`), smartcast handling (Ident
446// re-wrap, var-type sumtype shortcut), and native-backend pre-wrap
447// (`wrap_sumtype_value` for sumtype-returning fns). Too much logic to
448// inline for a single session. Wrap-only port: call legacy
449// `transform_return_stmt`, decompose the resulting ReturnStmt, emit
450// each transformed expr via `out.emit_expr(...)` (already transformed)
451// and assemble via the new `emit_return_stmt_by_ids` helper. Skips the
452// outer `ast.ReturnStmt` wrapper allocation per occurrence; the
453// `[]ast.Expr` list still materialises (legacy builds it).
454//
455// New `fixture_return_stmt` covers three return shapes: multi-value
456// return (`return 1, 2`), sumtype-wrapping return
457// (`return Circle2{r: r}` in a `Shape2`-returning fn), and smartcast
458// return (`if s is Circle2 { return s }` — Ident re-wrap path).
459// 126 → 131 transformer-diff tests.
460//
461// Session 23 (2026-05-26): Ident expr direct-emit + fixture.
462// The Ident arm in `transform_expr_to_flat` direct-emits via the leaf
463// `out.emit_expr(ast.Expr(expr))` for the default path (Ident is
464// identity in `transform_expr`'s arm — same shape returned). Two
465// state-dependent rewrite branches fall back to legacy:
466// 1. `@VMODROOT` → `vmodroot_string_literal(expr.pos)` produces a
467// StringLiteral (different shape).
468// 2. `find_smartcast_for_expr(expr.name)` hit → smartcast lowering
469// via `apply_smartcast_direct_ctx` produces a direct sum payload
470// access (different shape).
471// Both gate checks use the immutable `&Transformer` receiver, safe to
472// probe upfront. Skips the `transform_expr` dispatch call per Ident
473// occurrence reached through a ported ancestor (ParenExpr, PrefixExpr,
474// ModifierExpr, CastExpr, AsCastExpr, FieldInit, LambdaExpr, IndexExpr,
475// SelectorExpr, ComptimeExpr, InitExpr default fields, etc.). No
476// struct allocation savings since the legacy default-path arm also
477// returns the input Ident verbatim — the win is dispatch-skip on the
478// hottest expression variant.
479//
480// New `fixture_ident` covers file-scope Ident references nested inside
481// already-ported expr arms: `const neg_base = -base` (Ident inside
482// PrefixExpr arm), `const wrapped_base = (base)` (Ident inside
483// ParenExpr arm), `const inv_base = ~base` (Ident inside PrefixExpr
484// `~` arm), plus mut-param + multiple Ident references in a fn body
485// (Idents in InfixExpr operands — covered by the legacy InfixExpr
486// fallback path). 131 → 136 transformer-diff tests.
487//
488// Pattern note: first port where the saving is dispatch-skip rather
489// than struct-allocation skip (since Ident's default `transform_expr`
490// arm is pure identity, no allocation). Future leaf-identity arms
491// (KeywordOperator default, etc.) follow the same pattern: gate
492// state-dependent special branches via immutable lookups, direct-emit
493// the identity path.
494//
495// Session 24 (2026-05-26): KeywordOperator expr direct-emit + fixture.
496// KeywordOperator covers the comptime/macro-style call shapes
497// (`typeof(x)`, `sizeof(T)`, `isreftype(T)`, `dump(x)`, `likely(x)` /
498// `unlikely(x)`, `offsetof(T, f)`, `go ...` / `spawn ...`). The arm
499// in `transform_expr_to_flat` follows the session 23 (Ident) pattern:
500// a leaf-identity default path direct-emits via
501// `out.emit_expr(ast.Expr(expr))` and two gated state-dependent
502// special branches fall back to the legacy round-trip:
503// 1. `key_typeof` with `exprs.len > 0` → `resolve_typeof_expr`
504// produces a StringLiteral when the name is non-empty.
505// 2. `key_go` with `exprs.len > 0` → `lower_go_call(expr)` lowers
506// the spawn into a CallExpr to the generated goroutine wrapper.
507// All other ops (`sizeof`, `isreftype`, `dump`, `likely`, `unlikely`,
508// `offsetof`, `spawn`, also `typeof` when `resolve_typeof_expr`
509// returns empty) fall through to identity. Gate is the cheap
510// immutable `expr.op == ...` check — no side effects, no Transformer
511// state access. Same dispatch-skip win as session 23: skip the
512// `transform_expr` function call + match dispatch per occurrence
513// reached through a ported ancestor or a file-scope const value
514// (e.g. `const sz = sizeof(int)`).
515//
516// New `fixture_keyword_operator` covers file-scope KeywordOperators
517// in ConstDecl values: `const sz_int = sizeof(int)`,
518// `const sz_bool = sizeof(bool)`, `const ref_int = isreftype(int)`
519// — all hit the default identity branch since the legacy arm only
520// specially handles `key_typeof` and `key_go`. 136 → 141
521// transformer-diff tests.
522//
523// Session 25 (2026-05-26): GenericArgs expr direct-emit (completeness sweep).
524// GenericArgs covers the type-parameter shape `lhs[T]` (`typeof[int]`,
525// `MyGenericFn[string]`, ...). The `transform_expr` arm has three
526// branches: (1) `is_typeof_generic_args(expr)` → identity; (2)
527// `args.len == 1` + non-callable lhs type → `transform_index_expr`
528// lowers into an IndexExpr (different shape); (3) default →
529// `specialize_generic_callable_expr` lowers into an Ident or CallExpr
530// (different shape). The arm in `transform_expr_to_flat` follows the
531// session 23/24 pattern — gate the identity branch via the immutable
532// `is_typeof_generic_args` lookup (a `&Transformer` name check, no
533// state mutation) and direct-emit via leaf
534// `out.emit_expr(ast.Expr(expr))`; all other paths fall back to the
535// legacy round-trip.
536//
537// Reachability is limited in practice: GenericArgs typically appears
538// as a CallExpr lhs (`typeof[int]()`, `myfn[T](x)`), and CallExpr is
539// unported — so the arm fires only through ported-ancestor recursion
540// (ParenExpr / SelectorExpr deep helper, etc.). Same shape as the
541// session 17 IfGuardExpr completeness sweep: no new fixture, keeps the
542// dispatch surface coherent with the legacy `transform_expr` shape.
543// All 141 existing transformer-diff tests continue to pass.
544//
545// Session 26 (2026-05-26): StringInterLiteral deep helper port + fixture.
546// StringInterLiteral covers V's `'... ${x:d} ...'` interpolation syntax.
547// The legacy `transform_string_inter_literal` always returns a
548// StringInterLiteral (no branch produces a different shape) — it walks
549// each `ast.StringInter`, (a) optionally hoists a smartcasted payload via
550// `transform_expr` + `hoist_expr_to_temp` (mutates `t.pending_stmts`),
551// (b) wraps the inter expression via `transform_sprintf_arg` (type-aware
552// pointer-with-str / string-keeping logic), then (c) fills in the
553// resolved sprintf format via `resolve_sprintf_format`. The legacy body
554// allocates: one `ast.StringInterLiteral` wrapper, one `[]ast.StringInter`
555// list, and one `ast.StringInter` struct per inter.
556//
557// `transform_string_inter_literal_to_flat` mirrors the per-inter loop
558// and emits each StringInter via `emit_string_inter_by_ids(format, width,
559// precision, expr_id, format_expr_id, resolved_fmt)` and the outer
560// literal via `emit_string_inter_literal_by_ids(kind, values, inter_ids,
561// token.Pos{})`. The smartcast/hoist mutations still happen identically
562// (we call the same legacy helpers); only the post-build allocations are
563// skipped. Note: legacy omits `pos` on the rebuilt literal (defaults to
564// `token.Pos{}`) — the port passes `token.Pos{}` explicitly for parity.
565// Same deep-port pattern as sessions 18 (SelectorExpr), 19 (IndexExpr),
566// 20 (ComptimeExpr), 21 (InitExpr).
567//
568// No new fixture in this session — existing fixtures already cover
569// StringInterLiteral nodes reached via ported ancestors (e.g.,
570// `'hi ${name}'` inside a ParenExpr / SelectorExpr / ConstDecl value).
571// All 141 transformer-diff tests continue to pass.
572//
573// Session 27 (2026-05-26): ExprStmt port (stmt-level dispatch-skip).
574// First stmt-level identity-shape port. `transform_stmt`'s `ast.ExprStmt`
575// arm is identity-shape: it wraps the transformed inner expression in a
576// new `ast.ExprStmt`, with one state mutation — when `stmt.expr is
577// ast.IfExpr`, the arm flips `t.skip_if_value_lowering = true` around the
578// recursive `transform_expr` call so `transform_if_expr` knows the IfExpr
579// is in statement position (not value position) and skips temp-variable
580// lowering, then restores the prior flag.
581//
582// The flat-write port mirrors the flag flip around a recursive
583// `transform_expr_to_flat` call (so any deep-helper-ported expr arm
584// reached inside also benefits from the flat path — Ident, SelectorExpr,
585// IndexExpr, InitExpr, ComptimeExpr, StringInterLiteral, ...), then
586// assembles the stmt_expr node via the new `emit_expr_stmt_by_id` helper.
587// Skips the `ast.ExprStmt` wrapper struct allocation and the legacy
588// `out.emit_stmt(t.transform_stmt(stmt))` round-trip's match dispatch
589// (both on `transform_stmt` and on `add_stmt`) on every expression
590// statement.
591//
592// Reachability is excellent: ExprStmt is the most common statement form
593// after AssignStmt — every function body has them, every block has them,
594// every call as a top-level statement is an ExprStmt. No new fixture
595// this session — every existing fixture exercises ExprStmt via fn bodies
596// and asserts. All 141 transformer-diff tests continue to pass.
597//
598// Pattern note: first port that recursively enters `transform_expr_to_flat`
599// from the stmt-level dispatch, propagating the flat-port savings into
600// all statement-position expressions (not just nested expressions inside
601// already-ported ConstDecl/GlobalDecl/ReturnStmt values).
602//
603// Session 28 (2026-05-26): LabelStmt port (stmt-level recursive descent).
604// Second stmt-level identity-shape port after session 27 (ExprStmt).
605// `transform_stmt`'s LabelStmt arm always returns `LabelStmt{name:
606// stmt.name, stmt: transform_stmt(stmt.stmt)}` — `name` is verbatim,
607// single inner stmt recursively transformed. Direct-emit recurses via
608// `transform_stmt_to_flat` for the inner stmt (so any ported stmt arm
609// reached inside the label — ExprStmt, ReturnStmt, ForStmt, etc. — also
610// stays on the flat path) and assembles via the new
611// `emit_label_stmt_by_id(name, stmt_id)` helper. Mirrors
612// `add_stmt(LabelStmt)` encoding exactly (pos = `token.Pos{}`, interned
613// name, single edge to inner stmt). Skips the `ast.LabelStmt` wrapper
614// struct allocation and the `transform_stmt`/`add_stmt` match-dispatch
615// round-trip per occurrence.
616//
617// Reachability is limited to source-level `label: stmt` syntax
618// (typically `outer:` before loops for labelled break/continue), but the
619// port is clean — single child stmt and a verbatim name. No new fixture
620// this session — existing fixtures don't exercise LabelStmt directly,
621// but the port keeps the stmt-dispatch surface coherent. All 141
622// transformer-diff tests continue to pass.
623//
624// Pattern note: first stmt port that recursively enters
625// `transform_stmt_to_flat` itself (sibling of session 27's recursion into
626// `transform_expr_to_flat`), establishing the stmt-level recursive descent
627// pattern. Future stmt-level identity-shape ports (BlockStmt, DeferStmt,
628// ComptimeStmt $for branch) follow the same template — recurse via
629// `transform_stmt_to_flat` for child stmts, then emit via a new
630// `emit_*_by_ids` helper mirroring the matching `add_stmt` arm encoding.
631//
632// Session 29 (2026-05-26): BlockStmt port (wrap-only stmt port).
633// Third stmt-level identity-shape port after sessions 27 (ExprStmt) and
634// 28 (LabelStmt). `transform_stmt`'s BlockStmt arm always returns
635// `BlockStmt{stmts: transform_stmts(stmt.stmts)}` — identity-shape with
636// the body driver `transform_stmts` handling sibling-stmt smartcast
637// state snapshot/restore, pending-stmt hoisting, and one-to-many
638// expansion. Because the driver does extra inter-stmt work (and may
639// expand one input stmt into several outputs), we can't recurse via
640// `transform_stmt_to_flat` per-element without porting the driver
641// itself — that's a separate (bigger) refactor.
642//
643// Wrap-only port: call legacy `transform_stmts(stmt.stmts)` to
644// materialise the transformed `[]ast.Stmt`, emit each via the legacy
645// `out.emit_stmt(...)` (the stmts are already transformed), and
646// assemble via the new `emit_block_stmt_by_ids` helper. Mirrors
647// `add_stmt(BlockStmt)` encoding exactly (pos = `token.Pos{}`, edges =
648// inner stmts in order). Skips the outer `ast.BlockStmt` wrapper struct
649// allocation per occurrence; the inner `[]ast.Stmt` from
650// `transform_stmts` still materialises (until the body driver is
651// ported).
652//
653// Reachability is good — BlockStmt appears as a free-standing `{ ... }`
654// block in source. Most fn bodies don't directly use them (the FnDecl
655// arm calls `transform_stmts` itself), but `unsafe { ... }` and lambda
656// bodies in some shapes wrap their contents in BlockStmt. No new
657// fixture this session. All 141 transformer-diff tests continue to
658// pass.
659//
660// Pattern note: first stmt port that takes the "wrap-only" shape
661// (sessions 9 FnLiteral, 14 UnsafeExpr, 22 ReturnStmt established this
662// pattern at the expr level). The body driver port (which would let
663// BlockStmt / DeferStmt / FnLiteral.stmts / UnsafeExpr.stmts all stream
664// directly into the builder instead of materialising `[]ast.Stmt`) is
665// the next significant target.
666//
667// Session 55 (2026-05-27): InitExpr typed-empty-map direct-emit
668// refactor (seventh rewrite-site port — extends the post-routing-
669// pass mini-sweep to the InitExpr arm). `transform_init_expr_to_flat`
670// previously leaf-encoded the typed empty map branch
671// (`map[K]V{}`, `expr.fields.len == 0` + `MapType` typ) via
672// `out.emit_expr(t.transform_expr(ast.Expr(expr)))`. Session 55
673// direct-calls `transform_init_expr` (skipping the outer
674// `transform_expr` dispatch) and dispatches on result shape —
675// `ast.MapInitExpr` (eval backend) direct-emits via session 40's
676// `emit_map_init_expr_by_ids`; `ast.CallExpr` (non-eval, returns
677// a `new_map(sizeof(K), sizeof(V), &hash, &eq, &clone, &free)`
678// call) direct-emits via session 44's `emit_call_expr_by_ids`;
679// anything else falls through to leaf-emit.
680//
681// Safety: `transform_init_expr` is the helper `transform_expr`'s
682// InitExpr arm calls. The MapInitExpr branch's children
683// (`typ`/`keys`/`vals`) are leaves built locally by the helper
684// (`MapInitExpr{typ: ast.Expr(ast.Type(expr.typ)), keys: [], vals:
685// [], pos}`); the CallExpr branch's args are also leaves built
686// locally (`KeywordOperator{op: .key_sizeof}` + Ident-wrapped
687// `&hash`/`&eq`/... fns) — no re-transform needed, leaf
688// `out.emit_expr` is correct.
689//
690// Reachability: `map[K]V{}` is the canonical empty-map literal
691// and appears in every map-using V module. The dispatch-skip
692// win applies on every typed empty map occurrence; the
693// wrapper-allocation skip applies on every direct-emit path
694// (no longer build an `ast.InitExpr` wrapper before dispatch).
695// No new helper, no new fixture. All 168 transformer-diff tests
696// continue to pass.
697//
698// Session 54 (2026-05-27): CastExpr sum-type lowering direct-emit
699// refactor (sixth rewrite-site port — extends the post-routing-
700// pass mini-sweep to the CastExpr arm). `transform_expr_to_flat`'s
701// CastExpr arm previously gated the sum-type lowering branch and
702// leaf-encoded via `out.emit_expr(t.transform_expr(expr))`,
703// re-entering the legacy dispatch only to reach
704// `wrap_sumtype_value`. Session 54 direct-calls
705// `wrap_sumtype_value(expr.expr, sumtype_name)` and leaf-emits
706// the wrapped result when it returns Some; when it returns none
707// (value already is the sum type, declared receiver matches,
708// generic-param guard, etc.), falls through to the existing
709// default direct-emit path (CastExpr rebuild via
710// `emit_cast_expr_by_ids`).
711//
712// Safety: `wrap_sumtype_value` is the same helper
713// `transform_expr`'s CastExpr arm calls; calling it directly
714// skips one dispatch hop. The wrapped result (CastExpr/InitExpr/
715// CallExpr depending on variant shape — see `build_sumtype_init`)
716// is leaf-encoded via `out.emit_expr`: the result is already
717// fully-formed by the helper (it transforms the inner value
718// before wrapping at types.v:2145), so re-entering
719// `transform_expr_to_flat` would re-apply transforms — same
720// "do not route through dispatch" hazard as sessions 47-51.
721//
722// Reachability: sum-type casts (`MySum(variant_value)`) appear
723// in V code wherever a sum-type variant is materialised from an
724// untyped expression — common in pattern-match arms, ast
725// construction, etc. The dispatch-skip win applies on every
726// sum-type cast occurrence. No new helper, no new fixture. All
727// 168 transformer-diff tests continue to pass.
728//
729// Session 53 (2026-05-27): GenericArgs branch-2/3 direct-emit
730// refactor (fifth rewrite-site port — extends the
731// post-routing-pass mini-sweep to the GenericArgs arm).
732// `transform_expr_to_flat`'s GenericArgs arm previously gated
733// the identity branch (typeof[T]) and leaf-encoded the other two
734// branches via `out.emit_expr(t.transform_expr(expr))`. Session
735// 53 direct-handles each branch: branch 2 (single-arg,
736// non-callable lhs) cross-arm routes to
737// `transform_index_expr_to_flat` (same template as
738// GenericArgOrIndexExpr in session 33), branch 3 direct-calls
739// `specialize_generic_callable_expr` (returns Ident or
740// SelectorExpr — both identity in `transform_expr`) and
741// leaf-emits the result.
742//
743// Safety: `is_typeof_generic_args` / `get_expr_type` /
744// `is_callable_type` are immutable lookups. The IndexExpr
745// re-entry mirrors the legacy disambiguation exactly. The
746// branch-3 result (Ident/SelectorExpr) is leaf-encoded — both
747// shapes are identity in `transform_expr`, so re-entering
748// `transform_expr_to_flat` would only add a dispatch hop with
749// no rewrite.
750//
751// Reachability is limited (GenericArgs typically appears as a
752// CallExpr lhs which is unported — the arm fires through
753// ported-ancestor recursion: ParenExpr, SelectorExpr deep
754// helper, etc.), but the port removes the last legacy round-trip
755// from the arm. No new helper, no new fixture. All 168
756// transformer-diff tests continue to pass.
757//
758// Session 52 (2026-05-27): Ident @VMODROOT / smartcast branch
759// direct-emit refactor (fourth rewrite-site port — extends the
760// post-routing-pass mini-sweep to the Ident arm).
761// `transform_expr_to_flat`'s Ident arm previously leaf-encoded
762// both rewrite branches via `out.emit_expr(t.transform_expr(expr))`,
763// re-entering the legacy dispatch only to reach the two distinct
764// rewrites. Session 52 direct-calls each helper instead:
765// `@VMODROOT` → `vmodroot_string_literal(expr.pos)` (immutable,
766// returns StringLiteral) → leaf-emit; smartcast hit →
767// `apply_smartcast_direct_ctx(expr, ctx)` (the same helper
768// `transform_expr` would call) → leaf-emit.
769//
770// Safety: `vmodroot_string_literal` is a pure value-resolution
771// lookup (no expression rewriting). `apply_smartcast_direct_ctx`
772// returns an already-fully-formed expression (selector chain into
773// sum payload, or smartcast-rewritten expr) — leaf-encoding via
774// `out.emit_expr` is the safe escape hatch (routing through
775// `transform_expr_to_flat` would re-enter the helper-result's arm
776// and could re-apply smartcast rewrites).
777//
778// Reachability: Ident is by far the most-frequent expr variant
779// in V code (every variable reference is an Ident). The default
780// identity path was already direct-emit since session 23; this
781// session removes the last legacy-round-trip path from the arm.
782// `@VMODROOT` is rare (build-config code only); smartcast hits
783// are common inside `if x is Type { ... }` / `match`-narrowed
784// blocks. No new helper, no new fixture. All 168 transformer-diff
785// tests continue to pass.
786//
787// Session 51 (2026-05-27): KeywordOperator typeof/go branch split
788// direct-emit refactor (third rewrite-site port — extends the
789// post-routing-pass mini-sweep to the KeywordOperator arm).
790// `transform_expr_to_flat`'s KeywordOperator arm previously gated
791// both rewrite branches behind a single `(typeof || go) && len > 0`
792// check and leaf-encoded via `out.emit_expr(t.transform_expr(expr))`,
793// re-entering the legacy dispatch only to route to the two distinct
794// rewrites. Session 51 splits the branches: the typeof branch
795// direct-calls `resolve_typeof_expr` (an immutable `&Transformer`
796// lookup) and direct-emits the resulting StringLiteral via leaf
797// `out.emit_expr` (StringLiteral is identity in `transform_expr`);
798// the go branch direct-calls `lower_go_call(expr)` and dispatches
799// on the result shape — `ast.CallExpr` direct-emits via session
800// 44's `emit_call_expr_by_ids`, the `KeywordOperator` identity
801// fallback (unresolved fn_name / non-Call inner) falls through to
802// leaf `out.emit_expr(result)`.
803//
804// Safety: `resolve_typeof_expr` is a pure name-resolution lookup
805// (no mutation, no expression rewriting). `lower_go_call`
806// internally calls `t.transform_expr` on each arg and on the
807// selector lhs (for method calls) — args/receiver in the returned
808// CallExpr are fully-transformed, so direct-emit encodes them via
809// leaf `out.emit_expr` per arg. Routing through
810// `transform_expr_to_flat`'s CallExpr arm would re-run
811// `transform_call_expr` (fn.v:112 "Do NOT route" hazard) —
812// skipped, same as sessions 47/48/49/50.
813//
814// Reachability: `typeof(expr).name` patterns are common in V
815// comptime code (introspection, error messages, generic dispatch
816// hints). `go ...` / `spawn ...` are less common but appear in
817// concurrent code. The dispatch-skip win applies on every
818// KeywordOperator occurrence with these ops; the StringLiteral
819// direct-emit (no wrapper allocation) is a small but pure win on
820// typeof hits. No new helper, no new fixture. All 168
821// transformer-diff tests continue to pass.
822//
823// Session 50 (2026-05-27): IndexExpr map-index-lowering direct-emit
824// refactor (second rewrite-site port — completes the IndexExpr
825// non-identity-shape routing sweep started in session 49).
826// `transform_index_expr_to_flat`'s map-index branch (non-eval
827// backend, lhs unwraps to a Map type) was a leaf-encode shape:
828// `out.emit_expr(t.transform_expr(ast.Expr(expr)))` re-entered
829// `transform_expr`'s IndexExpr arm → `transform_index_expr` →
830// produced an UnsafeExpr containing temp-assign stmts plus a
831// deref-of-cast-of-`map__get` call. Session 50 refactor:
832// direct-call `transform_index_expr` (skipping the outer
833// `transform_expr` match dispatch) and direct-emit the resulting
834// UnsafeExpr via `emit_unsafe_expr_by_ids` (already present in
835// `vlib/v2_toberemoved/ast/flat.v`; no new helper needed).
836//
837// Safety: `transform_index_expr` returns an `ast.UnsafeExpr` with
838// fully-transformed stmts (the helper calls `t.transform_expr` on
839// every child expr internally — lhs, key, default-zero, etc.).
840// Direct-emit encodes each stmt via leaf `out.emit_stmt` — no
841// re-transform. Routing through `transform_stmt_to_flat` per stmt
842// would re-run the legacy dispatch (some arms call into helpers
843// that re-transform); leaf-encoding is the safe escape hatch.
844// The detection chain (`map_index_lhs_type` / `unwrap_map_type`
845// / `is_eval_backend`) stays upfront so we only direct-call
846// `transform_index_expr` when we already know it will produce the
847// UnsafeExpr lowering — `if result is ast.UnsafeExpr` is a
848// defensive type-narrow that catches an upstream shape drift.
849//
850// Reachability: map index reads `m[key]` on non-eval backends are
851// common in compiler-internal code (registries, lookup tables,
852// name resolution). The dispatch-skip win applies on every
853// map-index occurrence on the non-eval path. Same routing
854// template as sessions 47/48/49. No new helper, no new fixture.
855// All 168 transformer-diff tests continue to pass.
856//
857// Session 49 (2026-05-27): IndexExpr slice-lowering direct-emit
858// refactor (first rewrite-site port — sibling of routing-pass
859// sessions 46-48). `transform_index_expr_to_flat`'s slice branch
860// (`expr.expr is ast.RangeExpr`) was a leaf-encode shape: it called
861// `out.emit_expr(t.transform_expr(ast.Expr(expr)))` which re-entered
862// `transform_expr`'s IndexExpr arm → `transform_index_expr` →
863// `transform_slice_index_expr`, materialising a CallExpr to
864// `string__substr` / `array__slice` / `array__slice_ni`. With session
865// 44's `emit_call_expr_by_ids` in place, the slice branch can
866// direct-call `transform_slice_index_expr` (skipping the dispatch +
867// re-entry round-trip) and direct-emit the resulting CallExpr.
868//
869// Safety: `transform_slice_index_expr` returns an `ast.CallExpr`
870// with fully-transformed lhs/args (it calls `t.transform_expr` on
871// range start/end internally and on the lhs upstream of the
872// direct-call). Direct-emit encodes via leaf `out.emit_expr` per
873// arg — no re-transform. Routing through `transform_expr_to_flat`'s
874// CallExpr arm would re-run `transform_call_expr` (the fn.v:112
875// "Do NOT route" hazard) — skipped. Same template as sessions 47
876// (CallOrCastExpr) and 48 (SqlExpr lowered).
877//
878// Reachability is good — slice syntax `a[i..j]`, `s[..n]`, `arr[k..]`
879// is common in V code. The map-index lowering branch (lower in the
880// same function, returns UnsafeExpr) stays leaf-encoded for now;
881// porting it would need a new `emit_unsafe_expr_by_id` helper. This
882// is the first rewrite-site port after the routing-pass mini-sweep
883// finished — every dispatch arm in `transform_expr_to_flat` either
884// direct-emits or routes through a helper that does. No new helper,
885// no new fixture. All 168 transformer-diff tests continue to pass.
886//
887// Session 48 (2026-05-27): SqlExpr lowered-CallExpr direct-emit
888// refactor (routing pass — completes the routing-pass mini-sweep).
889// Session 36 ported SqlExpr with two outcome shapes:
890// identity-SqlExpr direct-emits via `emit_sql_expr_by_id` (added in
891// same session); lowered branch (is_create + table lookup hit) goes
892// through `lower_sql_create_expr` → `t.transform_expr(call)` on a
893// synthesized CallExpr, leaf-encoded via `out.emit_expr(result)`.
894// With session 44's `emit_call_expr_by_ids` in place, the lowered
895// branch's CallExpr result can be direct-emitted via the helper —
896// same template as session 47.
897//
898// Safety: `lower_sql_create_expr`'s `t.transform_expr(call)` enters
899// the CallExpr arm of `transform_expr` which calls
900// `transform_call_expr` once; its result has fully-transformed args.
901// Direct-emit here encodes lhs/args via leaf `out.emit_expr` — no
902// re-transform. Routing through `transform_expr_to_flat` would
903// re-run `transform_call_expr` (the fn.v:112 "Do NOT route" hazard
904// again) — skipped.
905//
906// Reachability is limited (sql ... { create ... } blocks with a
907// looked-up table struct), but the routing keeps the SqlExpr arm
908// coherent with the CallOrCastExpr arm (session 47) and finishes
909// the routing-pass mini-sweep that sessions 45-48 ran: every
910// "always-lowers via helper" arm whose downstream shape has a
911// direct-emit helper now uses it (MatchExpr → IfExpr in session 46,
912// CallOrCastExpr → CallExpr in session 47, SqlExpr lowered →
913// CallExpr in session 48). No new helper, no new fixture. All 168
914// transformer-diff tests continue to pass.
915//
916// Session 47 (2026-05-27): CallOrCastExpr direct-emit refactor
917// (routing pass — sibling of session 46's MatchExpr refactor).
918// Session 42 ported CallOrCastExpr to call `transform_call_or_cast_expr`
919// once and leaf-encode the result via `out.emit_expr(...)`. Session
920// 44 landed `emit_call_expr_by_ids`, unlocking the downstream win:
921// the most common shape `transform_call_or_cast_expr` lowers to is
922// `ast.CallExpr` (every method-resolved call, generic-math inline,
923// filter/map expansion, flag-enum method, smartcast method, all
924// module-prefixed calls, and the unresolved fallback). The CallExpr
925// branch can now be direct-emitted via the helper instead of
926// leaf-encoded.
927//
928// Safety: every CallExpr return path in `transform_call_or_cast_expr`
929// populates `lhs` and `args` from already-transformed values
930// (`transform_call_arg_with_sumtype_check`, `transform_expr` calls
931// upstream of each return). Direct-emit encodes lhs/args via leaf
932// `out.emit_expr` — no re-transform. Going through
933// `transform_expr_to_flat`'s CallExpr arm (which would re-run
934// `transform_call_expr` on the already-lowered result) IS unsafe
935// and explicitly flagged by an "// Do NOT route through
936// transform_call_expr because it would..." comment at fn.v:112; the
937// routing skips the dispatch arm and direct-emits instead.
938//
939// The non-CallExpr shapes (CastExpr, BasicLiteral '0', and rarer
940// `lower_call_or_cast_expr` results) still route through leaf
941// `out.emit_expr(result)`. Reachability of the direct-emit win is
942// high — CallExpr is the dominant downstream shape for the
943// parser's ambiguous `T(x)`/`f(x)` form. No new helper, no new
944// fixture. All 168 transformer-diff tests continue to pass.
945//
946// Session 46 (2026-05-27): MatchExpr direct-emit refactor (routing
947// pass — sibling of session 45's IfExpr port).
948// Session 35 ported MatchExpr to call `transform_match_expr` once
949// and leaf-encode the result via `out.emit_expr(...)`. Session 45
950// landed `emit_if_expr_by_ids` for the IfExpr arm, unlocking
951// MatchExpr's downstream win: `transform_match_expr` always lowers
952// to an `ast.IfExpr` (via `lower_match_expr_to_if`), so the IfExpr
953// can be direct-emitted via the new helper instead of routed
954// through leaf `out.emit_expr` (which still allocates one extra
955// wrapper layer inside `add_expr(IfExpr)`'s `push_expr` /
956// `push_stmt` walk).
957//
958// Safety: `transform_match_expr` runs `transform_match_branch_stmts`
959// per branch and `transform_expr(expr.expr)` upstream of
960// `build_match_branch_cond`, so the IfExpr's `cond` / `stmts` /
961// `else_expr` are already transformed. Direct-emit encodes them via
962// leaf `out.emit_expr` / `out.emit_stmt` (no re-transform). Going
963// through `transform_expr_to_flat`'s IfExpr arm would re-run
964// `transform_if_expr` on the already-transformed result — would
965// double-transform body stmts, which is why this routing skips the
966// IfExpr dispatch arm entirely.
967//
968// Edge case: `lower_match_expr_to_if` returns `ast.empty_expr` when
969// `branches.len == 0` (no branches → no if). The `is ast.IfExpr`
970// guard keeps that path on the leaf `out.emit_expr(result)` exit.
971//
972// Pattern note: routing-pass template — when an "always-lowers via
973// helper" arm produces a result shape that now has a direct-emit
974// helper, switch from leaf-encoding to direct-emit (without
975// re-routing through the dispatch arm, which would double-
976// transform). Same trick applies to CallOrCastExpr (session 42 →
977// `emit_call_expr_by_ids`) and SqlExpr's lowered-CallExpr branch
978// (session 36) — both follow-up sessions. No new helper, no new
979// fixture. All 168 transformer-diff tests continue to pass.
980//
981// Session 45 (2026-05-27): IfExpr port (always-lowers via helper, two
982// outcome shapes) — completes phase 4 expression-arm port sweep.
983// `transform_expr`'s IfExpr arm is a thin wrapper around
984// `transform_if_expr` (expr.v:1268), the largest control-flow helper
985// in the transformer. Many outcome shapes:
986// - &&-chain normalisation with embedded `is` checks → recursive
987// `transform_if_expr(outer_if / inner_if)` rebuild (IfExpr).
988// - smartcast hoisting (`if x is T && rest { ... }`) → IfExpr
989// with smartcast applied to the nested then-branch.
990// - sumtype tag/_type_id rewrite for `is`/`!is` cond → IfExpr.
991// - matched smartcast / type-narrowed branches → IfExpr with
992// hoisted decl-assigns + transformed stmts/else.
993// - direct option/result unwrap (`if x is none`) → IfExpr or
994// UnsafeExpr lowering with deref + tag check.
995// - default → `IfExpr{cond: transform(cond), stmts:
996// transform_stmts(stmts), else_expr: transform(else), pos}`
997// identity-shape rebuild.
998// - value-position lowering: `if_expr_is_value(transformed_if)`
999// AND not skipped → `lower_if_expr_value(transformed_if)` hoists
1000// a temp decl-assign per branch, returning an Ident (NOT an
1001// IfExpr).
1002//
1003// Direct-emit calls `transform_if_expr` once and dispatches on result
1004// type. Identity branch → encode `cond` + `else_expr` via leaf
1005// `out.emit_expr`, each `stmt` via leaf `out.emit_stmt` (helper
1006// already transformed body via `transform_stmts` —
1007// `transform_stmt_to_flat` would double-transform and break parity
1008// for hoisted smartcast / pending_stmts ordering) + assemble via the
1009// new `emit_if_expr_by_ids(cond_id, else_id, stmt_ids, pos)` helper.
1010// Lowered branch → route through leaf `out.emit_expr(result)`.
1011//
1012// Pattern note: extends "always-lowers via helper, two outcome
1013// shapes" (sessions 36 SqlExpr, 40 MapInitExpr, 41 ArrayInitExpr,
1014// 43 InfixExpr, 44 CallExpr). Reachability is high — `if` is one of
1015// the two main control-flow constructs in V; every `if cond { ... }
1016// else { ... }` passes through this arm. The wrapper-allocation skip
1017// applies on the identity branch (statement-position ifs and ifs
1018// without value lowering — most of them in compiler-style code).
1019//
1020// Completes phase 4's expression-arm port sweep — with this landing,
1021// every non-leaf arm in `transform_expr_to_flat` has a dedicated
1022// port. The `else { return out.emit_expr(t.transform_expr(expr)) }`
1023// fallback at the bottom of the dispatch is dead code (next
1024// follow-up: remove it + the `KeywordOperator` / `GenericArgs`
1025// intra-arm fallbacks that still route through the legacy round-trip
1026// for state-dependent rewrites). MatchExpr's arm (session 35) can
1027// now chain through `transform_expr_to_flat` to pick up IfExpr's
1028// direct-emit win on the lowered result, instead of routing through
1029// leaf `out.emit_expr` — another follow-up routing pass.
1030//
1031// New pub helper: `emit_if_expr_by_ids(cond_id, else_id, stmt_ids,
1032// pos)` in `vlib/v2_toberemoved/ast/flat.v` mirrors `add_expr(IfExpr)` encoding
1033// (`emit_simple(.expr_if, pos, [cond, else, stmts...])`). No new
1034// fixture this session. All 168 transformer-diff tests continue to
1035// pass.
1036//
1037// Session 44 (2026-05-27): CallExpr port (always-lowers via helper,
1038// two outcome shapes).
1039// `transform_expr`'s CallExpr arm is a thin wrapper around
1040// `transform_call_expr` (fn.v:1881) — by far the largest single
1041// helper in the transformer. It lowers many call shapes into
1042// non-CallExpr results: generic-math inline, embed_file lowering,
1043// $d resolution, .filter()/.map()/.sort() expansion, flag-enum
1044// methods, array contains/index/last_index, smartcast hoisting,
1045// BasicLiteral '0' elision (for elided fns), CastExpr for bare-type
1046// calls, IfExpr / InitExpr for some intrinsics, etc. The default
1047// tail (fn.v:2603-2610) is identity-shape:
1048// `CallExpr{lhs: transform(lhs),
1049// args: [transform_call_arg_with_sumtype_check(...)],
1050// pos}` —
1051// wrapped by `lower_missing_call_args` (fills omitted optional
1052// args) and `lower_variadic_args` (collapses trailing args into a
1053// `[]T` ArrayInitExpr).
1054//
1055// Direct-emit calls `transform_call_expr` once and dispatches on
1056// result type. Identity branch → encode `lhs` + each `arg` via leaf
1057// `out.emit_expr` (helper already transformed — re-routing through
1058// `transform_expr_to_flat` would double-transform and break parity
1059// for hoisted smartcast temps + variadic lowering) + assemble via
1060// the new `emit_call_expr_by_ids(lhs_id, arg_ids, pos)` helper.
1061// Lowered branch → route through leaf `out.emit_expr(result)` so
1062// the many lowering branches stay single-sourced.
1063//
1064// Pattern note: extends "always-lowers via helper, two outcome
1065// shapes" (sessions 36 SqlExpr, 40 MapInitExpr, 41 ArrayInitExpr,
1066// 43 InfixExpr). CallExpr is the highest-traffic arm of any port in
1067// phase 4 — every function call, method call, intrinsic, and
1068// lowered-array/map operation passes through it. The dispatch-skip
1069// win applies on every occurrence; the wrapper-allocation skip
1070// applies on the default-path identity branch (the most common
1071// case for ordinary user-code calls).
1072//
1073// With CallExpr ported, the CallOrCastExpr arm (session 42) and
1074// the cascade of lowering helpers throughout the transformer that
1075// produce CallExpr results could be retargeted to chain through
1076// `transform_expr_to_flat` instead of leaf `out.emit_expr` — but
1077// that's a follow-up routing pass, not part of this session. No
1078// new fixture this session. All 168 transformer-diff tests continue
1079// to pass.
1080//
1081// New pub helper: `emit_call_expr_by_ids(lhs_id, arg_ids, pos)` in
1082// `vlib/v2_toberemoved/ast/flat.v` mirrors `add_expr(CallExpr)` encoding
1083// (emit_simple `.expr_call` with edges = [lhs, args...]).
1084//
1085// Session 43 (2026-05-27): InfixExpr port (always-lowers via helper,
1086// two outcome shapes).
1087// `transform_expr`'s InfixExpr arm is a thin wrapper around
1088// `transform_infix_expr` (expr.v:2124), which has many outcome shapes
1089// that collapse into "identity InfixExpr" vs "lowered different
1090// shape":
1091// - `&&`-chain smartcast: `join_and_terms(terms)` rebuilds an
1092// InfixExpr chain — identity shape.
1093// - sum-type tag comparison (`is`/`!is`/`==`/`!=` against a
1094// variant) → `make_infix_expr_at(...)` over `_tag` / `_type_id`
1095// — identity shape (InfixExpr).
1096// - string literal concat folding → StringLiteral.
1097// - string + string → CallExpr to `string__plus`.
1098// - operator overload → CallExpr to the user-defined `<op>`
1099// method.
1100// - alias arithmetic, smartcast-of-lhs/rhs, fixed-array concat,
1101// etc. → various CallExpr / IndexExpr / etc.
1102// - default → `InfixExpr{op, lhs: transform(lhs), rhs:
1103// transform(rhs), pos}` — identity shape.
1104//
1105// Direct-emit calls `transform_infix_expr` once and dispatches on
1106// result type. Identity branches → encode lhs/rhs via leaf
1107// `out.emit_expr` (helper already transformed — `transform_expr_to_flat`
1108// would double-transform) + assemble via the new
1109// `emit_infix_expr_by_ids(op, lhs_id, rhs_id, pos)` helper. Lowered
1110// branches → route through leaf `out.emit_expr(result)`.
1111//
1112// Pattern note: extends "always-lowers via helper, two outcome
1113// shapes" (sessions 36 SqlExpr, 40 MapInitExpr, 41 ArrayInitExpr).
1114// The identity branch is reached by the vast majority of arithmetic
1115// / boolean / comparison infix operations in V code — every `a + b`,
1116// `i < n`, `flag && x`, `xs == ys`, ... — so the direct-emit win is
1117// broad.
1118//
1119// Reachability is extremely high — infix is the workhorse of every
1120// function body. The dispatch-skip win applies on every occurrence;
1121// the wrapper-allocation skip applies on every identity-branch
1122// occurrence. No new fixture this session. All 168 transformer-diff
1123// tests continue to pass.
1124//
1125// New pub helper: `emit_infix_expr_by_ids(op, lhs_id, rhs_id, pos)`
1126// in `vlib/v2_toberemoved/ast/flat.v` mirrors `add_expr(InfixExpr)` encoding
1127// (aux1=-1, aux2=-1, meta=u16(op), edges = [lhs, rhs]).
1128//
1129// Session 42 (2026-05-27): CallOrCastExpr port (always-lowers via helper,
1130// never-identity).
1131// `transform_expr`'s CallOrCastExpr arm is a thin wrapper around
1132// `transform_call_or_cast_expr` (fn.v:3955), which virtually always
1133// lowers a CallOrCastExpr (the parser's ambiguity node for `Type(arg)`
1134// vs `func(arg)`) into a different shape: CallExpr (most paths —
1135// generic-math inline, filter/map expansion, sort lambda, flag-enum
1136// methods, array contains/index, smartcast methods, all module-
1137// prefixed and method-resolved calls, the "unresolved fallback"
1138// CallExpr at the very end), CastExpr (when lhs is a known type
1139// expression), BasicLiteral '0' (elided fn or `enum.zero()` for flag
1140// enums), or whatever shape `lower_call_or_cast_expr` /
1141// `t.transform_expr(t.transform_call_or_cast_expr(...))` recursion
1142// produces.
1143//
1144// There is no identity-shape rebuild — every code path produces a
1145// non-CallOrCastExpr. The direct-emit win is purely the dispatch
1146// skip on the outer `transform_expr` arm; the lowered result still
1147// routes through leaf `out.emit_expr(...)` (no re-transform —
1148// `transform_call_or_cast_expr` already transformed receivers and
1149// args; double-transforming would break parity for smartcast
1150// hoisting and similar mutation).
1151//
1152// Pattern note: sibling of AssocExpr (session 34), MatchExpr (35) —
1153// "always-lowers via single helper to a DIFFERENT shape".
1154// Distinct from SqlExpr / MapInitExpr / ArrayInitExpr (sessions
1155// 36/40/41) which have an identity branch worth direct-emitting.
1156// When CallExpr and InfixExpr get their own flat helpers (sessions
1157// 43-44), this arm could switch to
1158// `t.transform_expr_to_flat(result, mut out)` to chain into those
1159// helpers' direct-emit paths — but today they still hit the `else`
1160// fallback, so routing through them adds no win.
1161//
1162// Reachability: CallOrCastExpr is the parser's ambiguous
1163// `T(x)`/`f(x)` form, common for single-arg calls and casts. By
1164// transformer-exit time every CallOrCastExpr becomes one of the
1165// shapes above, so the dispatch-skip win applies broadly. No new
1166// helper, no new fixture. All 168 transformer-diff tests continue to
1167// pass.
1168//
1169// Session 41 (2026-05-27): ArrayInitExpr port (always-lowers via helper,
1170// two outcome shapes).
1171// `transform_expr`'s ArrayInitExpr arm is a thin wrapper around
1172// `transform_array_init_expr` (struct.v:291), which has many outcome
1173// shapes that collapse into "identity ArrayInitExpr" vs "lowered
1174// CallExpr/UnsafeExpr":
1175// (a) invalid data → identity (`return ast.Expr(expr)`).
1176// (b) fixed-size array (`[1, 2, 3]!`, `[5]int{}`) → identity-shape
1177// rebuild with transformed `exprs`/`init`/`cap`/`len`.
1178// (c) eval backend → identity-shape rebuild with transformed
1179// typ/init/cap/len/update_expr fields.
1180// (d) spread `[...base, e1, e2]` → CallExpr to
1181// `new_array_from_array_and_c_array(...)`.
1182// (e) empty dynamic array `[]int{}` →
1183// `__new_array_with_default_noscan(len, cap, sizeof(elem),
1184// init)` CallExpr; or a ForStmt expansion when `init`
1185// references `index`.
1186// (f) keyed dynamic array `[1, 2, 3]` → `new_array_from_c_array(...)`
1187// CallExpr.
1188//
1189// Direct-emit calls `transform_array_init_expr` once and dispatches
1190// on result type. Identity branches (a)/(b)/(c) all return an
1191// `ast.ArrayInitExpr` — direct-emit via the new
1192// `emit_array_init_expr_by_ids` helper, skipping the wrapper
1193// allocation. Children are encoded via leaf `out.emit_expr` (the
1194// helper already transformed them — `transform_expr_to_flat` would
1195// double-transform). Lowering branches (d)/(e)/(f) return a
1196// non-ArrayInitExpr shape — route through leaf
1197// `out.emit_expr(result)` so the lowering logic stays
1198// single-sourced.
1199//
1200// Pattern note: extends "always-lowers via helper, two outcome
1201// shapes" (sessions 36 SqlExpr, 40 MapInitExpr). The two-shape
1202// template handles multiple identity branches uniformly via
1203// post-helper type dispatch. Unlike MapInitExpr (only eval backend
1204// keeps the identity shape), ArrayInitExpr keeps identity on every
1205// backend for fixed-size literals — so the direct-emit win reaches
1206// every backend for that branch, not just eval.
1207//
1208// Reachability is very high — array literals (`[]`, `[1, 2, 3]`,
1209// `[]int{}`, `[5]int{init: -1}`, `[1, 2, 3]!`) are ubiquitous in V
1210// code. Most call/return-arg array literals on non-eval backends hit
1211// branches (d)/(e)/(f), but fixed-size array literals (common in
1212// lookup tables and configuration) hit (b) identity-rebuild on every
1213// backend. No new fixture this session. All 168 transformer-diff
1214// tests continue to pass.
1215//
1216// New pub helper: `emit_array_init_expr_by_ids(typ_id, init_id,
1217// cap_id, len_id, update_expr_id, expr_ids, pos)` in
1218// `vlib/v2_toberemoved/ast/flat.v` mirrors `add_expr(ArrayInitExpr)` encoding
1219// (`emit_simple(.expr_array_init, pos, [typ, init, cap, len,
1220// update_expr, exprs...])`).
1221//
1222// Session 40 (2026-05-27): MapInitExpr port (always-lowers via helper,
1223// two outcome shapes).
1224// `transform_expr`'s MapInitExpr arm is a thin wrapper around
1225// `transform_map_init_expr` (struct.v:1010), which has two outcome
1226// shapes:
1227// (a) eval backend → identity-shape rebuild
1228// `MapInitExpr{typ: transform(typ), keys: [transform(k)...],
1229// vals: [transform(v)...], pos}`. Each child is already
1230// transformed inside the helper.
1231// (b) non-eval backend → lowers to a CallExpr:
1232// `new_map(sizeof(K), sizeof(V), &hash, &eq, &clone, &free)` for
1233// empty literals, `new_map_init(...)` for keyed literals.
1234//
1235// Direct-emit calls `transform_map_init_expr` once and dispatches on
1236// result type: `is ast.MapInitExpr` → encode typ/keys/vals via leaf
1237// `out.emit_expr` (pure `add_expr` — `transform_expr_to_flat` would
1238// double-transform) + assemble via the new
1239// `emit_map_init_expr_by_ids(typ_id, key_ids, val_ids, pos)` helper.
1240// Otherwise (lowered CallExpr) → route through leaf
1241// `out.emit_expr(result)`.
1242//
1243// Pattern note: extends the "always-lowers via helper, two outcome
1244// shapes" template established by SqlExpr (session 36) and AssignStmt
1245// (37). Identity branch picks up the direct-emit win (skips the
1246// `ast.MapInitExpr` wrapper struct allocation + the `transform_expr`
1247// match dispatch); lowered branch single-sources the `new_map`
1248// construction logic in the legacy helper.
1249//
1250// Reachability: map literals (`{}`, `{'a': 1}`, `map[string]int{}`)
1251// are common across the compiler (registries, lookup tables, codegen
1252// state). Most v2 backend builds use the non-eval path, so the
1253// identity-branch direct-emit win applies only on the eval backend;
1254// the dispatch-skip win applies on every backend. No new fixture this
1255// session — existing fixtures exercise map literals via for-in maps,
1256// tag tables, etc. All 168 transformer-diff tests continue to pass.
1257//
1258// New pub helper: `emit_map_init_expr_by_ids(typ_id, key_ids, val_ids,
1259// pos)` in `vlib/v2_toberemoved/ast/flat.v` mirrors `add_expr(MapInitExpr)`
1260// encoding exactly (opcode `expr_map_init`, aux1=-1,
1261// aux2=keys.len, edges = [typ, keys..., vals...]).
1262//
1263// Session 39 (2026-05-27): ForInStmt port (always-lowers via helper,
1264// cross-arm encoding reuse).
1265// `transform_stmt`'s ForInStmt arm is a single helper call to
1266// `transform_for_in_stmt` (for.v:1115), which wraps the ForInStmt as
1267// the `init` of a synthetic ForStmt and delegates to
1268// `transform_for_stmt`. The return type is `ast.ForStmt` — a
1269// DIFFERENT shape from the input (ForInStmt → ForStmt). All the
1270// for-in lowering (RangeExpr → range loop, runes_iterator → array-
1271// with-value-type, fixed-array / dynamic-array / string / map /
1272// untyped) happens inside `transform_for_stmt` once it sees the
1273// ForInStmt in `init`.
1274//
1275// Direct-emit calls `transform_for_in_stmt` once, then encodes the
1276// already-transformed ForStmt children via leaf emitters (no
1277// re-transform via `transform_*_to_flat` — would double-transform):
1278// init/post stmts via `out.emit_stmt`, cond expr via `out.emit_expr`,
1279// body stmts via `out.emit_stmt`. Reuses the existing
1280// `emit_for_stmt_by_ids(init_id, cond_id, post_id, stmt_ids)` helper
1281// from session 38 — no new flat helper needed since the lowered shape
1282// is ForStmt.
1283//
1284// Pattern note: stmt-level sibling of AssocExpr (session 34) and
1285// MatchExpr (session 35) — the "always-lowers via single helper to a
1286// DIFFERENT shape" template. Distinct from session 38 (ForStmt,
1287// identity-shape result) because the input arm is ForInStmt but the
1288// lowered shape is ForStmt. This is also "cross-arm encoding reuse"
1289// — analogous to session 33 (GenericArgOrIndexExpr routes to
1290// `transform_index_expr_to_flat` for the next stage) but here we
1291// reuse the ENCODING helper rather than re-dispatching through
1292// `transform_stmt_to_flat`. We must NOT route through
1293// `transform_stmt_to_flat`'s ForStmt arm because that would re-call
1294// `transform_for_stmt` on the already-fully-transformed ForStmt —
1295// double work and possible smartcast double-application.
1296//
1297// Skips the `ast.ForStmt` wrapper struct allocation for the result
1298// and the outer `transform_stmt` match dispatch on every ForInStmt
1299// occurrence. Reachability is high: `for x in arr`, `for k, v in
1300// map`, `for r in s.runes()` are extremely common (every iterator in
1301// the compiler itself uses them).
1302//
1303// No new flat helper this session — reuses `emit_for_stmt_by_ids`
1304// from session 38. All 168 transformer-diff tests continue to pass.
1305//
1306// Session 38 (2026-05-27): ForStmt port (always-lowers via helper,
1307// identity-shape result).
1308// `transform_stmt`'s ForStmt arm is a single helper call to
1309// `transform_for_stmt` (for.v:488), which always returns an
1310// `ast.ForStmt` regardless of its internal lowering paths. The
1311// helper handles for-in loops by dispatching on iterable type
1312// (RangeExpr → range lowering; runes_iterator() → array-with-
1313// value-type; smartcast array/string variant → array; fixed array
1314// → fixed-array; dynamic array/string → array; native backend any
1315// → untyped; map/channel/other → ForStmt-wrapped ForInStmt; no
1316// type info → untyped) and for plain `for cond { ... }` loops,
1317// sets up smartcast contexts from `is` checks in the condition
1318// (flattened across `&&` chains), recursively transforms init/
1319// cond/post and body via `transform_stmts`, then pops smartcasts
1320// and returns the assembled ForStmt. Opens/closes a child scope
1321// around the whole transform.
1322//
1323// Direct-emit calls `transform_for_stmt` once, then encodes the
1324// already-transformed children via leaf emitters (no re-transform
1325// via `transform_*_to_flat` — would double-transform): init/post
1326// stmts via `out.emit_stmt`, cond expr via `out.emit_expr`, body
1327// stmts via `out.emit_stmt`. Assembles via the new
1328// `emit_for_stmt_by_ids(init_id, cond_id, post_id, stmt_ids)`
1329// helper, which mirrors `add_stmt(ForStmt)` encoding exactly
1330// (pos = `token.Pos{}`, edges = [init, cond, post, body...]).
1331//
1332// Pattern note: stmt-level sibling of SqlExpr branch (b)
1333// (session 36) and AssignStmt's main-path (session 37) — the
1334// "always-lowers via single helper call, identity-shape result"
1335// template. Unlike AssignStmt, there's no pre-arm guard;
1336// `transform_for_stmt` handles everything internally. The
1337// remaining stmt-side helper-driven arm is ForInStmt (which
1338// returns ForStmt — a different shape than its input, so it's a
1339// sibling of AssocExpr session 34 / MatchExpr session 35:
1340// always-lowers via single helper to a different shape).
1341//
1342// Reachability is high — `for` loops appear in nearly every
1343// non-trivial function body. Top-level ForStmts (file scope)
1344// are rare, but the arm also fires from any recursive
1345// `transform_stmt_to_flat` calls (currently ComptimeStmt's
1346// non-$for branch). No new fixture this session. All 168
1347// transformer-diff tests continue to pass.
1348//
1349// New pub helper: `emit_for_stmt_by_ids(init_id, cond_id,
1350// post_id, stmt_ids)` in `vlib/v2_toberemoved/ast/flat.v` (~line 577).
1351//
1352// Session 37 (2026-05-27): AssignStmt port (pre-arm guard + helper).
1353// `transform_stmt`'s AssignStmt arm has a pre-arm dispatch block
1354// (transformer.v lines 2958-2966) plus the match-arm call:
1355// 1. `try_expand_or_expr_assign(stmt)` — stub returning `none`
1356// (transformer.v:3804). No-op, skip from direct-emit too.
1357// 2. `try_transform_map_index_assign(stmt)` — may rewrite map
1358// index assigns (`m[key] = v`, `m[k] += v`, `m[k].f = v`, etc.)
1359// into an `ExprStmt(map__set(...))` or `BlockStmt` hoisting
1360// temp-decl stmts before the call. Returned shape is NOT an
1361// AssignStmt — route through leaf `out.emit_stmt(...)`.
1362// 3. else → `transform_assign_stmt(stmt)` returns an AssignStmt
1363// with `lhs`/`rhs` exprs already transformed (string `+=` →
1364// `string__plus` call, result/option payload writes, etc.).
1365// Direct-emit encodes each lhs/rhs via leaf `out.emit_expr`
1366// (pure `add_expr` — `transform_expr_to_flat` would
1367// double-transform) and assembles via the new
1368// `emit_assign_stmt_by_ids(op, lhs_ids, rhs_ids, pos)` helper.
1369//
1370// Pattern note: stmt-level sibling of SqlExpr (session 36) —
1371// "always-lowers via single helper call" with two outcome shapes
1372// (rewrite vs identity-rebuild), with an additional pre-arm guard
1373// that can short-circuit to a lowered shape. Most assigns reach the
1374// identity-rebuild path (only map-index assigns hit the rewrite),
1375// so the dispatch-skip win applies broadly. The lowered shapes from
1376// `try_transform_map_index_assign` (BlockStmt/ExprStmt) currently
1377// route through legacy `emit_stmt`; when a BlockStmt cross-arm
1378// router lands they can switch to dispatched routing.
1379//
1380// Mirrors `add_stmt(AssignStmt)` encoding exactly: aux1=-1,
1381// aux2=lhs.len (lhs/rhs boundary), meta=u16(op), no flags, edges =
1382// lhs in order followed by rhs in order. Skips the `ast.AssignStmt`
1383// wrapper struct allocation per occurrence + the `transform_stmt`
1384// match dispatch.
1385//
1386// Reachability is very high — assignments appear in nearly every
1387// function body. No new fixture this session; existing fixtures
1388// exercise AssignStmt extensively (decl-assigns, mutations, compound
1389// ops). All 168 transformer-diff tests continue to pass.
1390//
1391// New pub helper: `emit_assign_stmt_by_ids(op, lhs_ids, rhs_ids,
1392// pos)` in `vlib/v2_toberemoved/ast/flat.v` (~line 577).
1393//
1394// Session 36 (2026-05-27): SqlExpr port (always-lowers via helper, two
1395// outcome shapes).
1396// Third sibling at the expr level of the "always-lowers via single
1397// helper call" template (after AssocExpr session 34 and MatchExpr
1398// session 35). `transform_expr`'s SqlExpr arm is a thin wrapper
1399// around `transform_sql_expr` (orm.v), which has two outcome shapes:
1400// (a) `is_create` AND `lower_sql_create_expr` finds the table
1401// struct → lowers to a CallExpr `expr.create(table_name,
1402// table_fields)`, then transforms that CallExpr and returns it
1403// — the result is no longer a SqlExpr. Routed through leaf
1404// `out.emit_expr(...)` (the lowered CallExpr is already
1405// transformed; no re-transform needed).
1406// (b) otherwise (read query, count, or create with missing table)
1407// → identity-shape rebuild: `SqlExpr{expr:
1408// transform_expr(expr.expr), table_name, is_count, is_create,
1409// pos}`. Direct-emit encodes `result.expr` via leaf
1410// `out.emit_expr` (pure `add_expr` encoder, no re-transform —
1411// `transform_expr_to_flat` would double-transform since
1412// `transform_sql_expr` already ran `transform_expr` on it) and
1413// assembles via the new `emit_sql_expr_by_id(table_name,
1414// is_count, is_create, expr_id, pos)` helper. Skips the
1415// `ast.SqlExpr` wrapper struct allocation per occurrence.
1416//
1417// Detection: call `transform_sql_expr` once, then dispatch on the
1418// result's runtime type — `is ast.SqlExpr` → branch (b), else →
1419// branch (a). This keeps the table-lookup logic single-sourced
1420// (don't reimplement `lower_sql_create_expr` here just to detect the
1421// branch upfront) and preserves the exact "rebuild on lookup miss"
1422// semantics.
1423//
1424// Pattern note: extends the "always-lowers via single helper call"
1425// template with two outcome shapes. AssocExpr (34) lowers to a leaf
1426// Ident; MatchExpr (35) lowers to an IfExpr (awaiting flat helper);
1427// SqlExpr (36) either lowers to a CallExpr OR identity-rebuilds.
1428// The branch-on-result-type pattern lets identity-shape rebuilds
1429// pick up the direct-emit win even when the legacy helper has
1430// multiple shapes. Future candidates: arms where a single legacy
1431// helper has both a lowering path and an identity-shape rebuild
1432// path (some CallExpr/InfixExpr lowering arms fit this).
1433//
1434// Reachability is limited to `sql ... { ... }` blocks (rare in the
1435// compiler itself). No new fixture this session — existing fixtures
1436// don't exercise SqlExpr. All 168 transformer-diff tests continue
1437// to pass.
1438//
1439// New pub helper: `emit_sql_expr_by_id(table_name, is_count,
1440// is_create, expr_id, pos)` in `vlib/v2_toberemoved/ast/flat.v` (~line 696).
1441// Mirrors `add_expr(SqlExpr)`'s encoding exactly: opcode `expr_sql`,
1442// pos, interned table_name (aux1), `flag_is_count | flag_is_create`
1443// packed into flags, one edge to inner expr.
1444//
1445// Session 35 (2026-05-27): MatchExpr port (always-lowers via helper).
1446// Sibling of AssocExpr (session 34) at the expr level —
1447// `transform_expr`'s MatchExpr arm is a single helper call to
1448// `t.transform_match_expr(expr)`, which always lowers a `match`
1449// construct to an IfExpr:
1450// - sum-type match: each branch's smartcast is set up (variant
1451// names → tags), then the chain `if x is Variant1 { ... } else
1452// if x is Variant2 { ... } ...` is built; the smartcast stack is
1453// restored after each branch.
1454// - non-sum match: chains `x == c1`/`x in c_list` IfBranches in
1455// declaration order, with `else` becoming the final IfBranch.
1456// The lowered IfExpr is returned as `ast.Expr` (not pre-transformed
1457// through `transform_expr`); the caller's match arm wraps it via
1458// legacy `add_expr(transformed_if)` which re-enters `transform_expr`
1459// on the IfExpr.
1460//
1461// Direct-emit calls `transform_match_expr` and routes the IfExpr
1462// result through the leaf `out.emit_expr(...)` — same as the `else`
1463// fallback would, minus the `transform_expr` match dispatch on
1464// MatchExpr. No dedicated IfExpr flat helper exists yet, so routing
1465// through `transform_expr_to_flat` for the result would only add
1466// one dispatch level without benefit. When an IfExpr flat helper
1467// lands, change this arm to feed the result back through
1468// `transform_expr_to_flat` (cross-arm routing, same pattern as
1469// GenericArgOrIndexExpr → transform_index_expr_to_flat in session
1470// 33).
1471//
1472// Pattern note: same "always-lowers via single helper call" shape
1473// as AssocExpr (session 34) — both arms have a single
1474// `t.lower_*(expr, ...)` body in legacy `transform_expr`. AssocExpr
1475// lowers to a leaf Ident (smartcast context handled via pending
1476// stmts); MatchExpr lowers to an IfExpr awaiting its own flat
1477// helper. Reachability is high — `match` is one of the most common
1478// constructs in the compiler. Win is purely the dispatch skip until
1479// IfExpr gets a flat helper.
1480//
1481// No new fixture this session — existing fixtures already exercise
1482// MatchExpr extensively (sum-type matches, literal matches, range
1483// matches). All 141 transformer-diff tests continue to pass.
1484//
1485// Session 34 (2026-05-26): AssocExpr port (always-lowers via helper).
1486// `transform_expr`'s AssocExpr arm is a single helper call —
1487// `t.lower_assoc_expr(expr, false)` always lowers the struct-update
1488// syntax `{base | field: val}` into a decl-assign of a typed tmp
1489// followed by per-field assignments, all hoisted into
1490// `t.pending_stmts` (which `transform_stmts` then drains as prefix
1491// stmts before the current stmt). The arm returns the tmp Ident
1492// (or `&tmp_ident` PrefixExpr in the `take_addr` form, which is
1493// only invoked by the PrefixExpr `.amp` rewrite branch — not by
1494// this arm).
1495//
1496// Direct-emit calls `lower_assoc_expr` and routes the Ident result
1497// through the leaf `out.emit_expr(...)` — same as the `else`
1498// fallback minus the `transform_expr` match dispatch on AssocExpr.
1499// The win is purely the dispatch skip. Routing through
1500// `transform_expr_to_flat` for the result would add a dispatch
1501// level without benefit since Idents are leaf direct-emit already.
1502//
1503// Pattern note: establishes the "always-lowers via single helper
1504// call" template — sibling of GenericArgOrIndexExpr (session 33)
1505// where cross-arm routing applies because the lowered shape has a
1506// dedicated flat helper. AssocExpr's result is a leaf Ident, so
1507// direct `out.emit_expr` is the right exit. Future siblings:
1508// MatchExpr (lowers to IfExpr via `transform_match_expr`) — same
1509// shape, route through `out.emit_expr` until an IfExpr flat helper
1510// exists; SqlExpr if it has a similar single-helper structure.
1511//
1512// Reachability is moderate — struct-update syntax `{base | f: v}`
1513// is used across the compiler for AST node copying. No new fixture
1514// this session. All 141 transformer-diff tests continue to pass.
1515//
1516// Session 33 (2026-05-26): GenericArgOrIndexExpr port (cross-arm routing).
1517// `transform_expr`'s GenericArgOrIndexExpr arm disambiguates the parser
1518// ambiguity for `x[y]` via the lhs type:
1519// (a) callable lhs → `specialize_generic_callable_expr(lhs, [expr],
1520// pos)` lowers to the generic arg specialization token (Ident or
1521// CallExpr) — different shape, route through leaf
1522// `out.emit_expr(...)`.
1523// (b) otherwise → constructs an `ast.IndexExpr{lhs, expr,
1524// is_gated: false, pos}` and runs it through
1525// `transform_index_expr`. Direct-emit routes through the
1526// existing `transform_index_expr_to_flat` helper so the IndexExpr
1527// arm's default-path direct-emit (lhs + expr via
1528// `transform_expr_to_flat`, assembled via
1529// `emit_index_expr_by_ids`) also applies here.
1530//
1531// Pattern note: first port where one ported `transform_*_to_flat`
1532// helper calls another (`GenericArgOrIndexExpr` → `transform_index_
1533// expr_to_flat`). Establishes the cross-arm routing template — when
1534// an arm always lowers into a shape that already has a flat helper,
1535// forward to that helper directly instead of going through legacy
1536// `transform_expr` dispatch.
1537//
1538// `is_callable_type` is an immutable lookup (`&Transformer` receiver);
1539// `specialize_generic_callable_expr` is `mut`. Gate the mut-call
1540// branch via an upfront immutable probe — same shape as the Ident /
1541// KeywordOperator / GenericArgs ports (sessions 23-25). Reachability
1542// is limited (the parser typically unifies into CallExpr/IndexExpr at
1543// parse time), but the port removes one fallback path from the `else`
1544// branch. No new fixture this session. All 141 transformer-diff tests
1545// continue to pass.
1546//
1547// Session 32 (2026-05-26): AssertStmt port (fallback stmt-level identity).
1548// `transform_stmt`'s AssertStmt arm is the rare fallback path — most
1549// assert stmts get expanded into `if !cond { panic(...) }` by
1550// `transform_stmts` before they reach the per-stmt dispatch. The
1551// fallback rebuilds `AssertStmt{expr: t.transform_expr(stmt.expr)}`
1552// without setting `extra`, so the result always has `extra =
1553// empty_expr` (struct default) and any original `stmt.extra` (the
1554// `"assert cond, msg"` text) is DROPPED.
1555//
1556// Direct-emit mirrors that exactly: transform `stmt.expr` via the
1557// recursive `transform_expr_to_flat` (so any ported deep-helper expr
1558// arm reached inside benefits too) and route through the new
1559// `emit_assert_stmt_by_id` helper, which encodes the second edge as
1560// `add_expr(empty_expr)` — hits the shared cached `empty_expr_id` so
1561// repeat AssertStmts don't duplicate the node. Mirrors
1562// `add_stmt(AssertStmt)` encoding exactly (pos = `token.Pos{}`, two
1563// edges: expr + empty_expr). Skips the `ast.AssertStmt` wrapper
1564// struct allocation per fallback occurrence.
1565//
1566// Reachability is low but the port completes the stmt-level
1567// identity-shape coverage planned in session 28's pattern note
1568// (BlockStmt → session 29, DeferStmt → session 30, ComptimeStmt →
1569// session 31, AssertStmt → this session). No new fixture this
1570// session. All 141 transformer-diff tests continue to pass.
1571//
1572// Session 31 (2026-05-26): ComptimeStmt port (two-branch stmt port).
1573// `transform_stmt`'s ComptimeStmt arm has two branches:
1574// (a) `stmt.stmt is ast.ForStmt` (the `$for field in Type.fields { ... }`
1575// form): rebuild ComptimeStmt(ForStmt{init, cond, post, stmts:
1576// transform_stmts(for_stmt.stmts)}) — `init`/`cond`/`post` are
1577// copied verbatim (NOT transformed by the legacy arm), only the
1578// body stmts go through the body driver.
1579// (b) otherwise (`$if` as stmt, etc.): legacy returns
1580// `transform_stmt(stmt.stmt)` — the ComptimeStmt wrapper is
1581// DROPPED entirely; the result is whatever the inner stmt
1582// transforms into.
1583//
1584// Direct-emit: branch (a) synthesises a `ForStmt{init, cond, post,
1585// transformed_stmts}` and routes it through legacy `out.emit_stmt(...)`
1586// for the encoding (ForStmt itself isn't ported yet), then wraps via
1587// the new `emit_comptime_stmt_by_id` helper. Branch (b) recurses via
1588// `transform_stmt_to_flat(stmt.stmt, ...)` so any ported inner stmt
1589// (ExprStmt, ReturnStmt, ...) also stays on the flat path.
1590// Mirrors `add_stmt(ComptimeStmt)` encoding exactly (pos =
1591// `token.Pos{}`, single edge to inner stmt). Skips the outer
1592// `ast.ComptimeStmt` wrapper struct allocation per `$for` occurrence
1593// AND the wrapper entirely on the non-`$for` recursive path.
1594//
1595// Reachability is reasonable — compile-time reflection via `$for`
1596// shows up in the compiler/runtime helpers. No new fixture this
1597// session. All 141 transformer-diff tests continue to pass.
1598//
1599// Pattern note: first stmt port with TWO branches that diverge at
1600// emit time (wrap-and-build vs. drop-and-recurse). Establishes the
1601// "branch-dispatch" template for future stmt ports where the legacy
1602// arm has conditional shape changes (e.g. AssertStmt expand-in-driver
1603// vs. fallback, PostfixExpr op-based lowering at the expr level
1604// already used this shape on the expr side).
1605//
1606// Session 30 (2026-05-26): DeferStmt port (wrap-only stmt port).
1607// Sibling of session 29 (BlockStmt) — same wrap-only shape with a
1608// `mode` field. `transform_stmt`'s DeferStmt arm always returns
1609// `DeferStmt{mode: stmt.mode, stmts: transform_stmts(stmt.stmts)}` —
1610// identity-shape with the body driver `transform_stmts` doing the
1611// same inter-stmt work (smartcast snapshot/restore, pending-stmt
1612// hoisting, one-to-many expansion). Same driver constraint as the
1613// BlockStmt arm.
1614//
1615// Wrap-only port: call legacy `transform_stmts(stmt.stmts)` to
1616// materialise the transformed `[]ast.Stmt`, emit each via the legacy
1617// `out.emit_stmt(...)` (already transformed), and assemble via the
1618// new `emit_defer_stmt_by_ids(mode, stmt_ids)` helper. Mirrors
1619// `add_stmt(DeferStmt)` encoding exactly: `pos = token.Pos{}`,
1620// `flags |= flag_defer_func` when mode is `.function`, edges = inner
1621// stmts in order. Skips the outer `ast.DeferStmt` wrapper struct
1622// allocation per occurrence; the inner `[]ast.Stmt` still
1623// materialises until the body driver is ported.
1624//
1625// Reachability is limited but clean — `defer { ... }` appears in
1626// resource-cleanup paths (file close, mutex unlock, ...) and
1627// `defer (func) { ... }` (function-scope variant) in fewer places.
1628// No new fixture this session. All 141 transformer-diff tests
1629// continue to pass.
1630//
1631// Phase 5: post-pass port.
1632// The active flat-direct paths run post_pass_to_flat and the post-pass tail
1633// without materialising a transformed []ast.File. Compatibility entry points
1634// still maintain legacy files for the `.v`/eval backends.
1635//
1636// Phase 6: drop compatibility materialisation.
1637// `transform_files_to_flat` and `_via_driver` still return []ast.File for
1638// legacy consumers. Flat-codegen backends use transform_flat_to_flat_direct
1639// or the parallel flat-direct path and keep the transformed program in
1640// FlatAst only.
1641//
1642// ----- Rewrite Site Inventory (55 sites, audited 2026-05-26) -----
1643//
1644// Phase 4's checklist. Categories are coarse — within a category the
1645// rewrites share most of their helper state (e.g. all or-block expansions
1646// share `expand_*_or_expr`).
1647//
1648// Control-flow lowering (~15 sites):
1649// - if.v: if-guard expansion (assign + stmt forms, 2 sites)
1650// - for.v: for-in map / array / range / fixed-array / untyped (6 sites)
1651// - expr.v: IfGuardExpr in IfExpr, match-expression lowering (~7 sites)
1652//
1653// Or-block / Result / Option (~8 sites):
1654// - transformer.v: expand_direct_or_expr_assign, expand_single_or_expr,
1655// string-range or-block, return-or-block (4 sites)
1656// - expr.v: OrExpr in expression context, deref-or, index-or (~4 sites)
1657//
1658// Operator / method lowering (~12 sites):
1659// - expr.v: operator overload, in-operator, infix coercions, prefix lower,
1660// index/slice rewrites, comptime field access (~10 sites)
1661// - fn.v: CallExpr method-to-function rewrites, CallOrCastExpr (2 sites)
1662//
1663// Structure init (~8 sites):
1664// - struct.v: array init, indexed array init, spread, map init, struct
1665// init, default-field fill (6 sites)
1666// - fn.v: generic specialization synthetic args, FnDecl rewrites (2 sites)
1667//
1668// String operations (~3 sites):
1669// - transformer.v: string interpolation lowering, embed_file
1670// - expr.v: string range / string method dispatch
1671//
1672// Defer / lock / go (~4 sites):
1673// - transformer.v: defer lowering, lock/rlock expansion, go-wrapper
1674// - fn.v: native interface vtable elision
1675//
1676// Generic specialization (~2 sites):
1677// - fn.v: monomorphize call rewrite
1678// - transformer.v: lower_assoc_expr
1679//
1680// Misc (~3 sites):
1681// - transformer.v: assert message lowering, assign_stmt multi-return,
1682// transform_stmt dispatcher special cases
1683// - orm.v: SQL expression lowering (1 site)
1684//
1685// ----- Per-file flat-write entry point -----
1686
1687// transform_file_index_to_flat is the per-file entry point for the multi-
1688// session port. It reads one file from `input_flat` through cursors, mirrors
1689// `transform_file`'s prologue, and emits each top-level stmt through the
1690// `transform_stmt_to_flat` seam. Returns the FlatNodeId of the appended file
1691// root, or `ast.invalid_flat_node_id` for an empty / missing source file.
1692//
1693// The file-level loop uses the flat stmt-list driver rather than dispatching
1694// each stmt directly. Top-level comptime `$if` blocks can expand to multiple
1695// declarations, and codegen expects those declarations to live directly under
1696// the file root.
1697//
1698// Callers must invoke `pre_pass_from_flat(input_flat)` before the per-file
1699// loop and `post_pass(mut collected_files)` after, mirroring the wedge. The
1700// per-file API does not run those passes itself so future phases can
1701// interleave file emissions with pre/post bookkeeping without re-running it.
1702pub fn (mut t Transformer) transform_file_index_to_flat(input_flat &ast.FlatAst, fi int, mut out ast.FlatBuilder) ast.FlatNodeId {
1703 return t.transform_flat_file_index_to_flat(input_flat, fi, []ast.Stmt{}, mut out)
1704}
1705
1706// transform_file_index_with_extra_to_flat is the pub entry the parallel
1707// flat-direct transform uses: it forwards the monomorphize-generated `extra`
1708// stmts for file `fi` (from prepare_flat_for_transform) into the cursor-native
1709// per-file transform. The plain `transform_file_index_to_flat` above passes no
1710// extras (sequential generic-free callers).
1711pub fn (mut t Transformer) transform_file_index_with_extra_to_flat(input_flat &ast.FlatAst, fi int, extra_stmts []ast.Stmt, mut out ast.FlatBuilder) ast.FlatNodeId {
1712 return t.transform_flat_file_index_to_flat(input_flat, fi, extra_stmts, mut out)
1713}
1714
1715fn (mut t Transformer) transform_flat_file_index_to_flat(input_flat &ast.FlatAst, fi int, extra_stmts []ast.Stmt, mut out ast.FlatBuilder) ast.FlatNodeId {
1716 if fi < 0 || fi >= input_flat.files.len {
1717 return ast.invalid_flat_node_id
1718 }
1719 fc := input_flat.file_cursor(fi)
1720 t.cur_file_name = fc.name()
1721 t.cur_module = fc.mod()
1722 t.cur_import_aliases = flat_import_aliases_for_generic_collect(input_flat, fi)
1723 if scope := t.get_module_scope(t.cur_module) {
1724 t.scope = scope
1725 } else {
1726 t.scope = unsafe { nil }
1727 }
1728 stmt_ids := t.transform_cursor_stmts_to_flat_direct(fc.stmts(), extra_stmts, mut out)
1729 attrs_id := copy_cursor_aux_list_to_flat(fc.attrs(), mut out)
1730 imports_id := copy_cursor_aux_list_to_flat(fc.imports(), mut out)
1731 return out.append_file_with_flat_lists_and_stmt_ids(fc.name(), fc.mod(), fc.selector_names(),
1732 attrs_id, imports_id, stmt_ids)
1733}
1734
1735// transform_file_to_flat transforms one legacy file and appends the transformed
1736// tree directly to `out`. It is the AST-input counterpart to
1737// `transform_file_index_to_flat`, used by flat-output pipelines after
1738// whole-program generic preparation has produced concrete appended stmts.
1739pub fn (mut t Transformer) transform_file_to_flat(file ast.File, mut out ast.FlatBuilder) ast.FlatNodeId {
1740 // Mirror transform_file's per-file prologue. transform_stmt and the
1741 // rewrite sites read these fields to resolve cross-stmt references.
1742 t.cur_file_name = file.name
1743 t.cur_module = file.mod
1744 t.cur_import_aliases = import_aliases_for_generic_collect(file.imports)
1745 if scope := t.get_module_scope(file.mod) {
1746 t.scope = scope
1747 } else {
1748 t.scope = unsafe { nil }
1749 }
1750 // Top-level comptime `$if` blocks can expand to multiple declarations
1751 // (for example platform-specific worker structs/functions). Use the same
1752 // stmt-list driver as function bodies so selected branch stmts are spliced
1753 // into the file root instead of being hidden inside a top-level BlockStmt.
1754 stmt_ids := t.transform_stmts_to_flat_direct(file.stmts, mut out)
1755 return out.append_file_with_stmt_ids(file, stmt_ids)
1756}
1757
1758// transform_stmts_to_flat is the consolidated seam for "transform a body
1759// stmt list, then encode each result into the flat builder" — the pattern
1760// repeated at every flat-write arm that wraps a body (BlockStmt, DeferStmt,
1761// ComptimeStmt $for body, UnsafeExpr body, FnLiteral body). Currently a
1762// literal pass-through over `transform_stmts` + leaf-encode loop, kept as a
1763// single function so future sessions can replace the body with direct-emit
1764// ports for `transform_stmts`'s expansion sites (comptime $if assign, or-
1765// block assign, tuple if-assign, lock/rlock, for-in-map, assert, ...) in
1766// one place. The FnDecl arm has its own seam (`transform_fn_decl_parts_to_flat`)
1767// because the FnDecl body driver also runs `lower_defer_stmts` between
1768// `transform_stmts` and the flat encoding — porting that needs a separate
1769// thrust.
1770pub fn (mut t Transformer) transform_stmts_to_flat(stmts []ast.Stmt, mut out ast.FlatBuilder) []ast.FlatNodeId {
1771 return t.transform_stmts_to_flat_direct(stmts, mut out)
1772}
1773
1774// transform_stmts_to_flat_direct is the flat-direct mirror of
1775// `transform_stmts` (transformer.v:3072). Drives the per-stmt loop emitting
1776// `FlatNodeId`s straight into `out` instead of materialising a `[]ast.Stmt`
1777// intermediate. Mirrors every expansion site (comptime $if assign, native
1778// interface cast/sincos, or-block assign, tuple if-assign, tuple call-assign,
1779// if-guard assign, if-expr assign, $if expr-stmt, or-expr stmt, if-guard
1780// stmt, flag-enum set/clear, return-match, or-return, return-if, lock/rlock,
1781// map-index push/postfix, native selector postfix, for-in-map, assert) and
1782// the default fall-through via `append_transformed_stmt_to_flat`.
1783//
1784// Stmts produced by expansion helpers (legacy `try_expand_*` /
1785// `expand_*` helpers that still return `ast.Stmt` / `[]ast.Stmt`) are leaf-
1786// encoded via `out.emit_stmt(...)` — these helpers transform the contained
1787// exprs internally, so leaf-encoding is bit-equal to the legacy
1788// `result << expanded_X` push. The `transform_stmt(stmt)` dispatch on the
1789// default fall-through routes through `transform_stmt_to_flat`'s direct-emit
1790// arms via the appender, which skips the legacy stmt-wrapper allocation for
1791// the many already-ported variants.
1792//
1793// Saves the outer `[]ast.Stmt` allocation (one per call) and the stmt-
1794// wrapper allocations on the default path. Expansion sites still materialise
1795// their inner intermediates — those need per-helper `_to_flat` ports in
1796// future sessions to fully drop the legacy materialisation.
1797pub fn (mut t Transformer) transform_stmts_to_flat_direct(stmts []ast.Stmt, mut out ast.FlatBuilder) []ast.FlatNodeId {
1798 mut ids := []ast.FlatNodeId{cap: stmts.len}
1799 block_smartcast_depth := t.smartcast_stack.len
1800 has_smartcast_state := block_smartcast_depth > 0
1801 block_smartcast_stack := if has_smartcast_state {
1802 t.smartcast_stack.clone()
1803 } else {
1804 []SmartcastContext{}
1805 }
1806 block_smartcast_counts := if has_smartcast_state {
1807 t.smartcast_expr_counts.clone()
1808 } else {
1809 map[string]int{}
1810 }
1811 for stmt in stmts {
1812 t.restore_flat_stmt_list_smartcast_context(block_smartcast_depth, block_smartcast_stack,
1813 block_smartcast_counts)
1814 t.transform_stmt_list_item_to_flat(stmt, mut ids, mut out)
1815 }
1816 t.restore_flat_stmt_list_smartcast_context(block_smartcast_depth, block_smartcast_stack,
1817 block_smartcast_counts)
1818 return ids
1819}
1820
1821pub fn (mut t Transformer) transform_cursor_stmts_to_flat_direct(stmts ast.CursorList, extra_stmts []ast.Stmt, mut out ast.FlatBuilder) []ast.FlatNodeId {
1822 mut ids := []ast.FlatNodeId{cap: stmts.len() + extra_stmts.len}
1823 block_smartcast_depth := t.smartcast_stack.len
1824 has_smartcast_state := block_smartcast_depth > 0
1825 block_smartcast_stack := if has_smartcast_state {
1826 t.smartcast_stack.clone()
1827 } else {
1828 []SmartcastContext{}
1829 }
1830 block_smartcast_counts := if has_smartcast_state {
1831 t.smartcast_expr_counts.clone()
1832 } else {
1833 map[string]int{}
1834 }
1835 for i in 0 .. stmts.len() {
1836 t.restore_flat_stmt_list_smartcast_context(block_smartcast_depth, block_smartcast_stack,
1837 block_smartcast_counts)
1838 t.transform_stmt_list_item_cursor_to_flat(stmts.at(i), mut ids, mut out)
1839 }
1840 for stmt in extra_stmts {
1841 t.restore_flat_stmt_list_smartcast_context(block_smartcast_depth, block_smartcast_stack,
1842 block_smartcast_counts)
1843 t.transform_stmt_list_item_to_flat(stmt, mut ids, mut out)
1844 }
1845 t.restore_flat_stmt_list_smartcast_context(block_smartcast_depth, block_smartcast_stack,
1846 block_smartcast_counts)
1847 return ids
1848}
1849
1850fn (mut t Transformer) restore_flat_stmt_list_smartcast_context(block_smartcast_depth int, block_smartcast_stack []SmartcastContext, block_smartcast_counts map[string]int) {
1851 if t.smartcast_stack.len < block_smartcast_depth {
1852 t.smartcast_stack = block_smartcast_stack.clone()
1853 t.smartcast_expr_counts = block_smartcast_counts.clone()
1854 } else if t.smartcast_stack.len > block_smartcast_depth {
1855 t.truncate_smartcasts(block_smartcast_depth)
1856 }
1857}
1858
1859fn (mut t Transformer) transform_stmt_list_item_to_flat(stmt ast.Stmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) {
1860 if stmt is ast.AssignStmt {
1861 assign_stmt := stmt as ast.AssignStmt
1862 if t.try_expand_comptime_if_assign_to_flat(assign_stmt, mut ids, mut out) {
1863 return
1864 }
1865 t.track_interface_assign_to_flat(assign_stmt)
1866 if t.try_expand_interface_cast_assign_to_flat(assign_stmt, mut ids, mut out) {
1867 return
1868 }
1869 if t.try_expand_sincos_assign_to_flat(assign_stmt, mut ids, mut out) {
1870 return
1871 }
1872 if t.try_expand_or_expr_assign_stmts_to_flat(&assign_stmt, mut ids, mut out) {
1873 return
1874 }
1875 if t.try_expand_tuple_if_assign_stmts_to_flat(assign_stmt, mut ids, mut out) {
1876 return
1877 }
1878 if t.try_expand_tuple_call_assign_to_flat(assign_stmt, mut ids, mut out) {
1879 return
1880 }
1881 if t.try_expand_if_guard_assign_stmts_to_flat(assign_stmt, mut ids, mut out) {
1882 return
1883 }
1884 if t.try_expand_if_expr_assign_stmts_to_flat(assign_stmt, mut ids, mut out) {
1885 return
1886 }
1887 }
1888 if stmt is ast.ExprStmt {
1889 if t.try_expand_comptime_if_stmt_to_flat(stmt, mut ids, mut out) {
1890 return
1891 }
1892 }
1893 if stmt is ast.ExprStmt {
1894 if t.try_expand_or_expr_stmt_to_flat(stmt, mut ids, mut out) {
1895 return
1896 }
1897 if t.try_expand_if_guard_stmt_to_flat(stmt, mut ids, mut out) {
1898 return
1899 }
1900 }
1901 if stmt is ast.ExprStmt {
1902 if t.try_emit_flag_enum_set_clear_to_flat(stmt, mut ids, mut out) {
1903 return
1904 }
1905 }
1906 if stmt is ast.ReturnStmt {
1907 if t.try_expand_return_match_expr_to_flat(stmt, mut ids, mut out) {
1908 return
1909 }
1910 if t.try_expand_or_expr_return_to_flat(stmt, mut ids, mut out) {
1911 return
1912 }
1913 if t.try_expand_return_if_expr_to_flat(stmt, mut ids, mut out) {
1914 return
1915 }
1916 }
1917 if stmt is ast.ExprStmt {
1918 if stmt.expr is ast.LockExpr {
1919 t.expand_lock_expr_to_flat(stmt.expr, mut ids, mut out)
1920 return
1921 }
1922 if t.try_emit_map_index_push_to_flat(stmt, mut ids, mut out) {
1923 return
1924 }
1925 if t.try_emit_map_index_postfix_to_flat(stmt, mut ids, mut out) {
1926 return
1927 }
1928 if t.try_emit_selector_postfix_to_flat(stmt, mut ids, mut out) {
1929 return
1930 }
1931 }
1932 if stmt is ast.ForStmt {
1933 if t.try_expand_for_in_map_to_flat(stmt, mut ids, mut out) {
1934 return
1935 }
1936 }
1937 if stmt is ast.AssertStmt {
1938 t.expand_assert_stmt_to_flat(stmt, mut ids, mut out)
1939 return
1940 }
1941 t.append_transformed_stmt_to_flat(mut ids, stmt, mut out)
1942}
1943
1944// transform_stmt_list_item_cursor_to_flat is the cursor-input mirror of
1945// `transform_stmt_list_item_to_flat`. It dispatches on the top-level statement's
1946// FlatNodeKind so converted arms can be handled without routing through the
1947// legacy guard chain, and unconverted kinds fall back to the proven decode path
1948// in one line. This is the seam that lets the transform become cursor-native one
1949// statement kind at a time (eliminating the whole-subtree `.stmt()` decode at
1950// the `transform_cursor_stmts_to_flat_direct` loop).
1951//
1952// First converted set: true-passthrough top-level kinds that carry no
1953// `try_expand_*` guard and that `transform_stmt_to_flat` emits verbatim. Those
1954// copy their flat subtrees directly, so they do not route through `c.stmt()`.
1955
1956// classify_call_fallback_cursor builds a diagnostic key describing WHY a call
1957// fell back to the legacy decode path. Instrumentation for V2_FLAT_FB_STATS.
1958fn (t &Transformer) classify_call_fallback_cursor(c ast.Cursor, prefix string) string {
1959 lhs := c.edge(0)
1960 if lhs.kind() != .expr_selector {
1961 return '${prefix}/lhs=${lhs.kind()}'
1962 }
1963 receiver := lhs.edge(0)
1964 method_name := selector_rhs_name_cursor(lhs)
1965 if t.has_active_smartcast() {
1966 return '${prefix}/sel/smartcast'
1967 }
1968 if method_name_needs_legacy_selector_pipeline(method_name) {
1969 return '${prefix}/sel/legacy-list:${method_name}'
1970 }
1971 recv_type := t.get_expr_type_cursor(receiver) or { return '${prefix}/sel/no-recv-type' }
1972 base := t.unwrap_alias_and_pointer_type(recv_type)
1973 if base is types.Struct {
1974 if !t.type_has_cached_method(base, method_name) {
1975 return '${prefix}/sel/no-cached-method'
1976 }
1977 } else {
1978 // Mirror the non-struct receiver gate: only its rejects classify as
1979 // receiver failures; accepted receivers fall through to late gates.
1980 if method_name in ['filter', 'map', 'any', 'count', 'wait'] {
1981 return '${prefix}/sel/expansion-method:${method_name}'
1982 }
1983 if base is types.Interface || base is types.SumType {
1984 return '${prefix}/sel/recv-dispatch=${base.name()}'
1985 }
1986 if t.resolve_alias_receiver_method_name(recv_type, method_name) == none
1987 && !(t.type_is_string(recv_type)
1988 && t.lookup_method_cached('string', method_name) != none) {
1989 return '${prefix}/sel/recv=${typeof(base).name}:${base.name()}:m=${method_name}'
1990 }
1991 }
1992 if t.resolve_static_type_method_call_cursor(receiver, method_name) != none {
1993 return '${prefix}/sel/static-shadow'
1994 }
1995 call_name := t.resolve_method_call_name_cursor(receiver, method_name) or {
1996 return '${prefix}/sel/no-call-name'
1997 }
1998 if call_name in t.elided_fns {
1999 return '${prefix}/sel/elided'
2000 }
2001 info := t.generic_aware_call_fn_info_cursor(c.edge(0), call_name) or {
2002 return '${prefix}/sel/no-fn-info'
2003 }
2004 arg_count := if c.kind() == .expr_call_or_cast {
2005 arg0 := c.edge(1)
2006 if arg0.is_valid() && arg0.kind() != .expr_empty {
2007 1
2008 } else {
2009 0
2010 }
2011 } else {
2012 c.edge_count() - 1
2013 }
2014 if info.param_types.len != arg_count {
2015 return '${prefix}/sel/arity:${call_name}:${info.param_types.len}vs${arg_count}'
2016 }
2017 if info.is_variadic {
2018 return '${prefix}/sel/variadic'
2019 }
2020 if info.generic_params.len > 0 {
2021 return '${prefix}/sel/generic'
2022 }
2023 for i in 0 .. arg_count {
2024 arg := c.edge(i + 1)
2025 if arg.kind() == .aux_field_init {
2026 return '${prefix}/sel/field-init-arg'
2027 }
2028 if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i]) {
2029 param_base := t.unwrap_alias_type(info.param_types[i])
2030 param_kind := match param_base {
2031 types.Struct { 'struct' }
2032 types.Enum { 'enum' }
2033 types.Array { 'array' }
2034 types.ArrayFixed { 'array_fixed' }
2035 types.Map { 'map' }
2036 types.SumType { 'sumtype' }
2037 types.Interface { 'interface' }
2038 types.FnType { 'fn' }
2039 types.OptionType { 'option' }
2040 types.ResultType { 'result' }
2041 types.Pointer { 'pointer' }
2042 types.String { 'string' }
2043 else { 'other' }
2044 }
2045
2046 return '${prefix}/sel/arg-not-identity/param=${param_kind}'
2047 }
2048 }
2049 return '${prefix}/sel/struct-late-gate-unknown'
2050}
2051
2052fn (mut t Transformer) transform_stmt_list_item_cursor_to_flat(c ast.Cursor, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) {
2053 match c.kind() {
2054 .stmt_import, .stmt_module, .stmt_directive, .stmt_empty, .stmt_enum_decl,
2055 .stmt_interface_decl, .stmt_type_decl, .stmt_asm, .stmt_flow_control, .stmt_attributes {
2056 id := out.copy_subtree_from(c.flat, c.id)
2057 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2058 }
2059 .stmt_const_decl {
2060 id := t.transform_const_decl_cursor_to_flat(c, mut out)
2061 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2062 }
2063 .stmt_block {
2064 id := t.transform_block_stmt_cursor_to_flat(c, mut out)
2065 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2066 }
2067 .stmt_defer {
2068 id := t.transform_defer_stmt_cursor_to_flat(c, mut out)
2069 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2070 }
2071 .stmt_label {
2072 id := t.transform_label_stmt_cursor_to_flat(c, mut out)
2073 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2074 }
2075 .stmt_global_decl {
2076 id := t.transform_global_decl_cursor_to_flat(c, mut out)
2077 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2078 }
2079 .stmt_struct_decl {
2080 t.count_flat_fallback('stmt_struct_decl')
2081 id := out.emit_stmt(t.transform_struct_decl(c.struct_decl()))
2082 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2083 }
2084 .stmt_assert {
2085 t.expand_assert_stmt_cursor_to_flat(c, mut ids, mut out)
2086 }
2087 .stmt_assign {
2088 if id := t.transform_map_index_assign_cursor_to_flat(c, mut out) {
2089 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2090 return
2091 }
2092 if t.try_expand_if_expr_assign_cursor_to_flat(c, mut ids, mut out) {
2093 return
2094 }
2095 if t.assign_stmt_cursor_needs_legacy_expand(c) {
2096 t.count_flat_fallback('stmt_assign')
2097 t.transform_stmt_list_item_to_flat(assign_stmt_from_cursor(c), mut ids, mut out)
2098 } else {
2099 id := t.transform_assign_stmt_cursor_to_flat(c, mut out)
2100 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2101 }
2102 }
2103 .stmt_comptime {
2104 id := t.transform_comptime_stmt_cursor_to_flat(c, mut out)
2105 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2106 }
2107 .stmt_expr {
2108 if t.try_expand_comptime_if_stmt_cursor_to_flat(c, mut ids, mut out) {
2109 return
2110 }
2111 if c.edge(0).kind() == .expr_lock {
2112 t.expand_lock_expr_cursor_to_flat(c.edge(0), mut ids, mut out)
2113 return
2114 }
2115 if id := t.transform_flag_enum_set_clear_cursor_to_flat(c, mut out) {
2116 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2117 return
2118 }
2119 if t.expr_stmt_cursor_needs_legacy_expand(c) {
2120 t.count_flat_fallback('stmt_expr')
2121 t.transform_stmt_list_item_to_flat(expr_stmt_from_cursor(c), mut ids, mut out)
2122 } else {
2123 expr := c.edge(0)
2124 if expr.kind() == .expr_infix
2125 && unsafe { token.Token(int(expr.aux())) } == .left_shift {
2126 if t.try_emit_map_index_push_to_flat(expr_stmt_from_cursor(c), mut ids, mut out) {
2127 return
2128 }
2129 }
2130 id := t.transform_expr_stmt_cursor_to_flat(c, mut out)
2131 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2132 }
2133 }
2134 .stmt_return {
2135 if t.try_expand_return_if_expr_cursor_to_flat(c, mut ids, mut out) {
2136 return
2137 }
2138 if t.return_stmt_cursor_needs_legacy_expand(c) {
2139 t.count_flat_fallback('stmt_return')
2140 t.transform_stmt_list_item_to_flat(return_stmt_from_cursor(c), mut ids, mut out)
2141 } else {
2142 id := t.transform_return_stmt_cursor_to_flat(c, mut out)
2143 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2144 }
2145 }
2146 .stmt_for_in {
2147 t.count_flat_fallback('stmt_for_in')
2148 t.transform_stmt_list_item_to_flat(for_in_stmt_from_cursor(c), mut ids, mut out)
2149 }
2150 .stmt_fn_decl {
2151 decl := c.fn_decl_signature()
2152 if t.omit_backend_generic_decl(decl) {
2153 return
2154 }
2155 // Stream the body cursor-native when it has no defers (defer
2156 // lowering needs the whole body, so those take the legacy
2157 // whole-decl decode path).
2158 if flat_body_has_defer(c.list_at(3)) {
2159 t.count_flat_fallback('stmt_fn_decl_defer')
2160 decl_with_body := fn_decl_signature_with_body_cursor(c.fn_decl_signature(), c)
2161 t.transform_stmt_list_item_to_flat(decl_with_body, mut ids, mut out)
2162 } else {
2163 id := t.transform_fn_decl_streaming_to_flat(c, mut out)
2164 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2165 }
2166 }
2167 .stmt_for {
2168 // Stream plain `for` loops (cond / classic / bare) cursor-native.
2169 // For-in loops dispatch through cursor-native lowerings for the
2170 // migrated iterable families, and only unported forms fall back to
2171 // the legacy whole-loop decode path.
2172 init_c := c.edge(0)
2173 if init_c.is_valid() && init_c.kind() == .stmt_for_in {
2174 if t.try_expand_range_for_in_cursor_to_flat(c, mut ids, mut out) {
2175 return
2176 }
2177 if t.try_expand_array_for_in_cursor_to_flat(c, mut ids, mut out) {
2178 return
2179 }
2180 if t.try_expand_fixed_array_for_in_cursor_to_flat(c, mut ids, mut out) {
2181 return
2182 }
2183 if t.try_expand_for_in_map_cursor_to_flat(c, mut ids, mut out) {
2184 return
2185 }
2186 if t.try_expand_untyped_for_in_cursor_to_flat(c, mut ids, mut out) {
2187 return
2188 }
2189 if t.try_expand_passthrough_for_in_cursor_to_flat(c, mut ids, mut out) {
2190 return
2191 }
2192 panic('unhandled flat for-in lowering')
2193 } else {
2194 id := t.transform_for_stmt_streaming_to_flat(c, mut out)
2195 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2196 }
2197 }
2198 else {
2199 panic('unexpected flat statement kind: ${c.kind()}')
2200 }
2201 }
2202}
2203
2204// append_transformed_stmt_to_flat is the flat-builder mirror of
2205// `append_transformed_stmt` (transformer.v:3061). Runs `transform_stmt_to_flat`
2206// on the input stmt (direct-emitting via the dispatch seam), then drains any
2207// `t.pending_stmts` produced by sub-transforms (e.g. or-block side effects
2208// pushed by `expand_single_or_expr`) by leaf-encoding them into `out` BEFORE
2209// the just-emitted main stmt — matching the ordering invariant of the legacy
2210// appender ("pending stmts hoist ahead of the transformed result").
2211//
2212// Scaffolding for the `transform_stmts` body driver port (arc #1). Future
2213// sessions fork the legacy `transform_stmts` body into a direct-emit driver
2214// that drives the per-stmt loop via this appender, replacing the inner
2215// `t.append_transformed_stmt(mut result, ast.Stmt(...))` calls with their
2216// `_to_flat` equivalents. Used today by `transform_file_index_to_flat`'s
2217// top-level loop (replacing the previous push/pop/restore pattern).
2218pub fn (mut t Transformer) append_transformed_stmt_to_flat(mut ids []ast.FlatNodeId, stmt ast.Stmt, mut out ast.FlatBuilder) {
2219 id := t.transform_stmt_to_flat(stmt, mut out)
2220 t.append_transformed_stmt_id_to_flat(mut ids, id, mut out)
2221}
2222
2223// append_transformed_stmt_id_to_flat hoists any `t.pending_stmts` produced
2224// while building `id` ahead of it (matching the appender's ordering invariant),
2225// then pushes `id`. Shared by `append_transformed_stmt_to_flat` and the
2226// cursor-native stmt arms (transform_stmt_list_item_cursor_to_flat) so both
2227// drain pending side effects identically.
2228fn (mut t Transformer) append_transformed_stmt_id_to_flat(mut ids []ast.FlatNodeId, id ast.FlatNodeId, mut out ast.FlatBuilder) {
2229 // Flat-side hoists drain first: arms that push pending_flat_stmt_ids
2230 // flush the legacy pending_stmts queue into it beforehand, so emitting
2231 // the flat queue first preserves the legacy chronological order.
2232 if t.pending_flat_stmt_ids.len > 0 {
2233 for fid in t.pending_flat_stmt_ids {
2234 ids << fid
2235 }
2236 t.pending_flat_stmt_ids.clear()
2237 }
2238 if t.pending_stmts.len > 0 {
2239 pending := t.pending_stmts.clone()
2240 t.pending_stmts.clear()
2241 for ps in pending {
2242 ids << out.emit_stmt(ps)
2243 }
2244 }
2245 ids << id
2246}
2247
2248// transform_const_decl_cursor_to_flat is the cursor-input mirror of the
2249// `ast.ConstDecl` arm of `transform_stmt_to_flat` (flat_write.v). It reads the
2250// const decl's is_public flag and field-init list straight from the cursor —
2251// the ConstDecl wrapper + FieldInit structure are never decoded to legacy — and
2252// transforms each field value via the existing `transform_expr_to_flat`. Emit
2253// order matches the flat-output arm exactly (per-field value+field_init, then
2254// the fields aux_list, then the stmt).
2255fn (mut t Transformer) transform_const_decl_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId {
2256 fields := c.list_at(0)
2257 mut field_ids := []ast.FlatNodeId{cap: fields.len()}
2258 for i in 0 .. fields.len() {
2259 fc := fields.at(i)
2260 value_id := t.transform_expr_cursor_to_flat(fc.edge(0), mut out)
2261 field_ids << out.emit_field_init_by_id(fc.name(), value_id)
2262 }
2263 fields_list_id := out.emit_aux_list_from_ids(field_ids)
2264 return out.emit_const_decl_by_ids(c.flag(ast.flag_is_public), fields_list_id)
2265}
2266
2267// transform_global_decl_cursor_to_flat is the cursor-input mirror of the
2268// `ast.GlobalDecl` arm of `transform_stmt_to_flat`. It reads the decl
2269// attributes, field-decl list, and per-field metadata/flags from the cursor and
2270// transforms each field's typ + value via `transform_expr_to_flat`. Emit order
2271// matches the flat-output arm (decl attrs, then per-field typ/value/attrs/
2272// field_decl, then the fields aux_list, then the stmt).
2273fn (mut t Transformer) transform_global_decl_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId {
2274 decl_attrs_id := copy_cursor_aux_list_to_flat(c.list_at(0), mut out)
2275 fields := c.list_at(1)
2276 mut field_ids := []ast.FlatNodeId{cap: fields.len()}
2277 for i in 0 .. fields.len() {
2278 fc := fields.at(i)
2279 typ_id := t.transform_expr_cursor_to_flat(fc.edge(0), mut out)
2280 value_id := t.transform_expr_cursor_to_flat(fc.edge(1), mut out)
2281 field_attrs_id := copy_cursor_aux_list_to_flat(fc.list_at(2), mut out)
2282 field := ast.FieldDecl{
2283 name: fc.name()
2284 is_public: fc.flag(ast.flag_is_public)
2285 is_mut: fc.flag(ast.flag_is_mut)
2286 is_module_mut: fc.flag(ast.flag_field_is_module_mut)
2287 is_interface_method: fc.flag(ast.flag_field_is_interface_method)
2288 }
2289 field_ids << out.emit_field_decl_by_ids(field, typ_id, value_id, field_attrs_id)
2290 }
2291 fields_list_id := out.emit_aux_list_from_ids(field_ids)
2292 return out.emit_global_decl_by_ids(c.flag(ast.flag_is_public), decl_attrs_id, fields_list_id)
2293}
2294
2295fn assoc_expr_from_cursor(c ast.Cursor) ast.AssocExpr {
2296 mut fields := []ast.FieldInit{cap: c.edge_count() - 2}
2297 for i in 2 .. c.edge_count() {
2298 field := c.edge(i)
2299 fields << ast.FieldInit{
2300 name: field.name()
2301 value: field.edge(0).expr()
2302 }
2303 }
2304 return ast.AssocExpr{
2305 typ: c.edge(0).expr()
2306 expr: c.edge(1).expr()
2307 fields: fields
2308 pos: c.pos()
2309 }
2310}
2311
2312fn or_expr_from_cursor(c ast.Cursor) ast.OrExpr {
2313 mut stmts := []ast.Stmt{cap: c.edge_count() - 1}
2314 for i in 1 .. c.edge_count() {
2315 stmts << c.edge(i).stmt()
2316 }
2317 return ast.OrExpr{
2318 expr: c.edge(0).expr()
2319 stmts: stmts
2320 pos: c.pos()
2321 }
2322}
2323
2324fn infix_expr_from_cursor(c ast.Cursor) ast.InfixExpr {
2325 return ast.InfixExpr{
2326 op: unsafe { token.Token(int(c.aux())) }
2327 lhs: c.edge(0).expr()
2328 rhs: c.edge(1).expr()
2329 pos: c.pos()
2330 }
2331}
2332
2333fn (t &Transformer) cursor_is_string_concat_operand(c ast.Cursor) bool {
2334 if !c.is_valid() {
2335 return false
2336 }
2337 if t.is_string_expr_cursor(c) {
2338 return true
2339 }
2340 match c.kind() {
2341 .expr_call {
2342 if c.edge_count() == 0 {
2343 return false
2344 }
2345 lhs := c.edge(0)
2346 return lhs.kind() == .expr_ident && lhs.name().starts_with('string__')
2347 }
2348 .expr_paren, .expr_modifier {
2349 return c.edge_count() > 0 && t.cursor_is_string_concat_operand(c.edge(0))
2350 }
2351 .expr_infix {
2352 op := unsafe { token.Token(int(c.aux())) }
2353 return op == .plus && (t.cursor_is_string_concat_operand(c.edge(0))
2354 || t.cursor_is_string_concat_operand(c.edge(1)))
2355 }
2356 else {
2357 return false
2358 }
2359 }
2360}
2361
2362fn (t &Transformer) infix_plus_cursor_can_transform_direct(c ast.Cursor) bool {
2363 if t.cursor_is_string_concat_operand(c.edge(0)) || t.cursor_is_string_concat_operand(c.edge(1)) {
2364 return false
2365 }
2366 if lhs_type := t.get_expr_type_cursor(c.edge(0)) {
2367 if lhs_type is types.Struct {
2368 type_name := t.type_to_c_name(lhs_type)
2369 if type_name == 'time__Time' {
2370 return false
2371 }
2372 }
2373 }
2374 return true
2375}
2376
2377fn cursor_is_enum_shorthand_selector(c ast.Cursor) bool {
2378 if !c.is_valid() || c.kind() != .expr_selector || c.edge_count() == 0 {
2379 return false
2380 }
2381 lhs := c.edge(0)
2382 return lhs.kind() == .expr_empty || (lhs.kind() == .expr_ident && lhs.name() == '')
2383}
2384
2385fn (mut t Transformer) enum_shorthand_cursor_to_flat(c ast.Cursor, enum_type string, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2386 if enum_type == '' || !cursor_is_enum_shorthand_selector(c) {
2387 return none
2388 }
2389 member := c.edge(1)
2390 if typ := t.lookup_type(enum_type) {
2391 t.register_synth_type(c.pos(), typ)
2392 if typ is types.Enum {
2393 return out.emit_ident_by_name(t.enum_member_ident_for_lookup(enum_type, typ,
2394 member.name()), c.pos())
2395 }
2396 }
2397 return out.emit_ident_by_name(enum_member_ident(enum_type, member.name()), c.pos())
2398}
2399
2400fn (mut t Transformer) transform_enum_shorthand_compare_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2401 if op !in [.eq, .ne] || c.edge_count() < 2 {
2402 return none
2403 }
2404 lhs := c.edge(0)
2405 rhs := c.edge(1)
2406 if cursor_is_enum_shorthand_selector(rhs) {
2407 enum_type := t.get_enum_type_name_cursor(lhs)
2408 rhs_id := t.enum_shorthand_cursor_to_flat(rhs, enum_type, mut out) or { return none }
2409 lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out)
2410 return out.emit_infix_expr_by_ids(op, lhs_id, rhs_id, c.pos())
2411 }
2412 if cursor_is_enum_shorthand_selector(lhs) {
2413 enum_type := t.get_enum_type_name_cursor(rhs)
2414 lhs_id := t.enum_shorthand_cursor_to_flat(lhs, enum_type, mut out) or { return none }
2415 rhs_id := t.transform_expr_cursor_to_flat(rhs, mut out)
2416 return out.emit_infix_expr_by_ids(op, lhs_id, rhs_id, c.pos())
2417 }
2418 return none
2419}
2420
2421fn (t &Transformer) sumtype_check_variant_name_cursor(rhs ast.Cursor) (string, string) {
2422 if rhs.kind() == .expr_ident {
2423 return rhs.name(), ''
2424 }
2425 if rhs.kind() == .expr_selector {
2426 mut variant_module := ''
2427 rhs_lhs := rhs.edge(0)
2428 if rhs_lhs.kind() == .expr_ident {
2429 variant_module = rhs_lhs.name()
2430 }
2431 return selector_rhs_name_cursor(rhs), variant_module
2432 }
2433 return t.type_expr_to_variant_name_cursor(rhs), ''
2434}
2435
2436fn (mut t Transformer) transform_interface_self_type_check_cursor_to_flat(lhs ast.Cursor, variant_name string, op token.Token, pos token.Pos, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2437 lhs_type := t.get_expr_type_cursor(lhs) or { return none }
2438 lhs_base := t.unwrap_alias_and_pointer_type(lhs_type)
2439 if lhs_base !is types.Interface {
2440 return none
2441 }
2442 lhs_base_name := lhs_base.name()
2443 if !lhs_base_name.ends_with(variant_name) && lhs_base_name != variant_name {
2444 return none
2445 }
2446 lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out)
2447 type_id := t.synth_selector_cursor_to_flat(lhs_id, '_type_id', types.Type(types.int_), mut out)
2448 zero_id := out.emit_basic_literal_by_value(.number, '0', pos)
2449 cmp_op := if op in [.key_is, .eq] { token.Token.ne } else { token.Token.eq }
2450 return out.emit_infix_expr_by_ids(cmp_op, type_id, zero_id, pos)
2451}
2452
2453fn (mut t Transformer) transform_sumtype_check_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2454 if op !in [.key_is, .not_is, .eq, .ne] || c.edge_count() < 2 || t.has_active_smartcast() {
2455 return none
2456 }
2457 lhs := c.edge(0)
2458 rhs := c.edge(1)
2459 variant_name, variant_module := t.sumtype_check_variant_name_cursor(rhs)
2460 if variant_name == '' {
2461 return none
2462 }
2463 variant_lookup_name := sum_type_variant_name_with_module(variant_module, variant_name)
2464 mut sumtype_name := t.generic_scan_sumtype_name_for_expr_cursor(lhs)
2465 mut lhs_is_enum := t.get_enum_type_name_cursor(lhs) != ''
2466 if lhs_type := t.get_expr_type_cursor(lhs) {
2467 lhs_base := t.unwrap_alias_and_pointer_type(lhs_type)
2468 lhs_is_enum = lhs_is_enum || lhs_base is types.Enum
2469 }
2470 rhs_is_type_like := variant_name.len > 0 && variant_name[0] >= `A` && variant_name[0] <= `Z`
2471 if sumtype_name == '' && !lhs_is_enum && !cursor_is_enum_shorthand_selector(rhs)
2472 && (op in [.key_is, .not_is] || rhs_is_type_like) {
2473 sumtype_name = t.find_sumtype_for_variant(variant_lookup_name)
2474 }
2475 if sumtype_name == '' {
2476 if result_id := t.transform_interface_self_type_check_cursor_to_flat(lhs, variant_name, op,
2477 c.pos(), mut out)
2478 {
2479 return result_id
2480 }
2481 return none
2482 }
2483 variants := t.get_sum_type_variants(sumtype_name)
2484 mut tag_value := -1
2485 for i, variant in variants {
2486 if sum_type_variant_matches_for_sumtype(sumtype_name, variant, variant_lookup_name) {
2487 tag_value = i
2488 break
2489 }
2490 }
2491 if tag_value < 0 {
2492 return none
2493 }
2494 lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out)
2495 tag_id := t.synth_selector_cursor_to_flat(lhs_id, '_tag', types.Type(types.int_), mut out)
2496 tag_value_id := out.emit_basic_literal_by_value(.number, tag_value.str(), c.pos())
2497 cmp_op := if op in [.key_is, .eq] { token.Token.eq } else { token.Token.ne }
2498 return out.emit_infix_expr_by_ids(cmp_op, tag_id, tag_value_id, c.pos())
2499}
2500
2501fn (t &Transformer) scalar_equality_type_can_transform_direct(typ types.Type) bool {
2502 if t.type_is_string(typ) {
2503 return false
2504 }
2505 base := t.unwrap_alias_type(typ)
2506 return match base {
2507 types.Primitive, types.Char, types.ISize, types.Nil, types.Pointer, types.Rune,
2508 types.USize {
2509 true
2510 }
2511 else {
2512 false
2513 }
2514 }
2515}
2516
2517fn (t &Transformer) infix_equality_cursor_can_transform_direct(c ast.Cursor) bool {
2518 lhs := c.edge(0)
2519 rhs := c.edge(1)
2520 if cursor_is_enum_shorthand_selector(lhs) || cursor_is_enum_shorthand_selector(rhs) {
2521 return false
2522 }
2523 lhs_type := t.get_expr_type_cursor(lhs) or { return false }
2524 rhs_type := t.get_expr_type_cursor(rhs) or { return false }
2525 return t.scalar_equality_type_can_transform_direct(lhs_type)
2526 && t.scalar_equality_type_can_transform_direct(rhs_type)
2527}
2528
2529fn (t &Transformer) is_nil_expr_cursor(c ast.Cursor) bool {
2530 return match c.kind() {
2531 .expr_ident {
2532 c.name() == 'nil'
2533 }
2534 .expr_keyword {
2535 unsafe { token.Token(int(c.aux())) } == .key_nil
2536 }
2537 .typ_nil {
2538 true
2539 }
2540 .expr_basic_literal {
2541 unsafe { token.Token(int(c.aux())) } == .number && c.name() == '0'
2542 }
2543 .expr_paren, .expr_cast, .expr_modifier {
2544 c.edge_count() > 0 && t.is_nil_expr_cursor(c.edge(0))
2545 }
2546 else {
2547 false
2548 }
2549 }
2550}
2551
2552fn cursor_is_c_string_literal(c ast.Cursor) bool {
2553 return match c.kind() {
2554 .expr_string {
2555 unsafe { ast.StringLiteralKind(int(c.aux())) } == .c
2556 }
2557 .expr_paren, .expr_modifier {
2558 c.edge_count() > 0 && cursor_is_c_string_literal(c.edge(0))
2559 }
2560 else {
2561 false
2562 }
2563 }
2564}
2565
2566fn cursor_is_foldable_v_string_literal(c ast.Cursor) bool {
2567 return match c.kind() {
2568 .expr_string {
2569 unsafe { ast.StringLiteralKind(int(c.aux())) } == .v
2570 }
2571 .expr_paren {
2572 c.edge_count() > 0 && cursor_is_foldable_v_string_literal(c.edge(0))
2573 }
2574 else {
2575 false
2576 }
2577 }
2578}
2579
2580fn (t &Transformer) is_v_string_expr_cursor(c ast.Cursor) bool {
2581 return !cursor_is_c_string_literal(c) && t.is_string_expr_cursor(c)
2582}
2583
2584fn (t &Transformer) infix_string_compare_cursor_can_transform_direct(c ast.Cursor, op token.Token) bool {
2585 if op !in [.eq, .ne, .lt, .gt, .le, .ge] || c.edge_count() < 2 {
2586 return false
2587 }
2588 lhs := c.edge(0)
2589 rhs := c.edge(1)
2590 return !t.is_nil_expr_cursor(lhs) && !t.is_nil_expr_cursor(rhs)
2591 && t.is_v_string_expr_cursor(lhs) && t.is_v_string_expr_cursor(rhs)
2592}
2593
2594fn (mut t Transformer) string_compare_call_cursor_to_flat(name string, first ast.Cursor, second ast.Cursor, pos token.Pos, mut out ast.FlatBuilder) ast.FlatNodeId {
2595 lhs_id := out.emit_ident_by_name(name, token.Pos{})
2596 first_id := t.transform_expr_cursor_to_flat(first, mut out)
2597 second_id := t.transform_expr_cursor_to_flat(second, mut out)
2598 return out.emit_call_expr_by_ids(lhs_id, [first_id, second_id], pos)
2599}
2600
2601fn (mut t Transformer) transform_string_compare_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2602 if !t.infix_string_compare_cursor_can_transform_direct(c, op) {
2603 return none
2604 }
2605 lhs := c.edge(0)
2606 rhs := c.edge(1)
2607 match op {
2608 .eq {
2609 return t.string_compare_call_cursor_to_flat('string__eq', lhs, rhs, c.pos(), mut out)
2610 }
2611 .ne {
2612 call_id :=
2613 t.string_compare_call_cursor_to_flat('string__eq', lhs, rhs, c.pos(), mut out)
2614 return out.emit_prefix_expr_by_id(.not, call_id, c.pos())
2615 }
2616 .lt {
2617 return t.string_compare_call_cursor_to_flat('string__lt', lhs, rhs, c.pos(), mut out)
2618 }
2619 .gt {
2620 return t.string_compare_call_cursor_to_flat('string__lt', rhs, lhs, c.pos(), mut out)
2621 }
2622 .le {
2623 call_id :=
2624 t.string_compare_call_cursor_to_flat('string__lt', rhs, lhs, c.pos(), mut out)
2625 return out.emit_prefix_expr_by_id(.not, call_id, c.pos())
2626 }
2627 .ge {
2628 call_id :=
2629 t.string_compare_call_cursor_to_flat('string__lt', lhs, rhs, c.pos(), mut out)
2630 return out.emit_prefix_expr_by_id(.not, call_id, c.pos())
2631 }
2632 else {
2633 return none
2634 }
2635 }
2636}
2637
2638fn (t &Transformer) infix_string_concat_cursor_can_transform_direct(c ast.Cursor, op token.Token) bool {
2639 if op != .plus || c.edge_count() < 2 {
2640 return false
2641 }
2642 lhs := c.edge(0)
2643 rhs := c.edge(1)
2644 if lhs.kind() == .expr_infix || rhs.kind() == .expr_infix {
2645 return false
2646 }
2647 if cursor_is_foldable_v_string_literal(lhs) && cursor_is_foldable_v_string_literal(rhs) {
2648 return false
2649 }
2650 return t.is_v_string_expr_cursor(lhs) && t.is_v_string_expr_cursor(rhs)
2651}
2652
2653fn (mut t Transformer) transform_string_concat_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2654 if !t.infix_string_concat_cursor_can_transform_direct(c, op) {
2655 return none
2656 }
2657 lhs_id := out.emit_ident_by_name('string__plus', token.Pos{})
2658 left_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
2659 right_id := t.transform_expr_cursor_to_flat(c.edge(1), mut out)
2660 return out.emit_call_expr_by_ids(lhs_id, [left_id, right_id], c.pos())
2661}
2662
2663fn (mut t Transformer) transform_range_membership_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2664 if op !in [.key_in, .not_in] || c.edge_count() < 2 {
2665 return none
2666 }
2667 lhs := c.edge(0)
2668 range := c.edge(1)
2669 if !range.is_valid() || range.kind() != .expr_range || range.edge_count() < 2 {
2670 return none
2671 }
2672 lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out)
2673 start_id := t.transform_expr_cursor_to_flat(range.edge(0), mut out)
2674 lower_id := out.emit_infix_expr_by_ids(.ge, lhs_id, start_id, c.pos())
2675 mut range_check_id := lower_id
2676 end := range.edge(1)
2677 if end.is_valid() && end.kind() != .expr_empty {
2678 range_op := unsafe { token.Token(int(range.aux())) }
2679 upper_op := if range_op == .dotdot { token.Token.lt } else { token.Token.le }
2680 end_id := t.transform_expr_cursor_to_flat(end, mut out)
2681 upper_id := out.emit_infix_expr_by_ids(upper_op, lhs_id, end_id, c.pos())
2682 range_check_id = out.emit_infix_expr_by_ids(.and, lower_id, upper_id, c.pos())
2683 }
2684 if op == .not_in {
2685 return out.emit_prefix_expr_by_id(.not, range_check_id, c.pos())
2686 }
2687 return range_check_id
2688}
2689
2690fn (mut t Transformer) transform_inline_array_membership_elem_cursor_to_flat(elem ast.Cursor, enum_type string, mut out ast.FlatBuilder) ast.FlatNodeId {
2691 if enum_type != '' {
2692 if enum_id := t.enum_shorthand_cursor_to_flat(elem, enum_type, mut out) {
2693 return enum_id
2694 }
2695 }
2696 return t.transform_expr_cursor_to_flat(elem, mut out)
2697}
2698
2699fn (mut t Transformer) transform_inline_array_membership_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2700 if op !in [.key_in, .not_in] || c.edge_count() < 2 {
2701 return none
2702 }
2703 lhs := c.edge(0)
2704 rhs := c.edge(1)
2705 if !rhs.is_valid() || rhs.kind() != .expr_array_init || rhs.edge_count() <= 5 {
2706 return none
2707 }
2708 enum_type := t.get_enum_type_name_cursor(lhs)
2709 lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out)
2710 first_elem_id := t.transform_inline_array_membership_elem_cursor_to_flat(rhs.edge(5),
2711 enum_type, mut out)
2712 mut chain_id := out.emit_infix_expr_by_ids(.eq, lhs_id, first_elem_id, c.pos())
2713 for i in 6 .. rhs.edge_count() {
2714 elem_id :=
2715 t.transform_inline_array_membership_elem_cursor_to_flat(rhs.edge(i), enum_type, mut out)
2716 elem_cmp_id := out.emit_infix_expr_by_ids(.eq, lhs_id, elem_id, c.pos())
2717 chain_id = out.emit_infix_expr_by_ids(.logical_or, chain_id, elem_cmp_id, c.pos())
2718 }
2719 if op == .not_in {
2720 return out.emit_prefix_expr_by_id(.not, chain_id, c.pos())
2721 }
2722 return chain_id
2723}
2724
2725fn (mut t Transformer) transform_map_membership_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2726 if op !in [.key_in, .not_in] || c.edge_count() < 2 {
2727 return none
2728 }
2729 lhs := c.edge(0)
2730 rhs := c.edge(1)
2731 rhs_type := t.map_index_lhs_type_cursor(rhs) or { return none }
2732 map_type := t.unwrap_map_type(rhs_type) or { return none }
2733 if t.is_eval_backend() {
2734 lhs_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
2735 rhs_id := t.transform_expr_cursor_to_flat(c.edge(1), mut out)
2736 return out.emit_infix_expr_by_ids(op, lhs_id, rhs_id, c.pos())
2737 }
2738 map_ptr_id := t.map_expr_to_runtime_ptr_cursor(rhs, rhs_type, mut out) or { return none }
2739 key_value_id := t.transform_expr_cursor_to_flat(lhs, mut out)
2740 mut key_ptr_id := ast.FlatNodeId(0)
2741 mut key_temp_assign_id := ast.FlatNodeId(0)
2742 has_key_temp := !t.can_take_address_expr_cursor(lhs)
2743 || t.is_enum_rvalue_cursor(lhs, map_type.key_type)
2744 if has_key_temp {
2745 key_ident := t.typed_temp_ident(t.gen_temp_name(), map_type.key_type)
2746 key_lhs_id := out.emit_ident_by_name(key_ident.name, key_ident.pos)
2747 key_temp_assign_id = out.emit_assign_stmt_by_ids(.decl_assign, [key_lhs_id], [
2748 key_value_id,
2749 ], key_ident.pos)
2750 key_ref_id := out.emit_ident_by_name(key_ident.name, key_ident.pos)
2751 key_ptr_id = out.emit_prefix_expr_by_id(.amp, key_ref_id, token.Pos{})
2752 } else {
2753 key_ptr_id = out.emit_prefix_expr_by_id(.amp, key_value_id, token.Pos{})
2754 }
2755 call_lhs_id := out.emit_ident_by_name('map__exists', token.Pos{})
2756 call_id := out.emit_call_expr_by_ids(call_lhs_id, [map_ptr_id, key_ptr_id], c.pos())
2757 result_id := if op == .not_in {
2758 out.emit_prefix_expr_by_id(.not, call_id, c.pos())
2759 } else {
2760 call_id
2761 }
2762 if has_key_temp {
2763 expr_stmt_id := out.emit_expr_stmt_by_id(result_id)
2764 return out.emit_unsafe_expr_by_ids([key_temp_assign_id, expr_stmt_id], token.Pos{})
2765 }
2766 return result_id
2767}
2768
2769fn (t &Transformer) map_index_lhs_type_cursor(lhs ast.Cursor) ?types.Type {
2770 if !lhs.is_valid() {
2771 return none
2772 }
2773 if lhs.kind() == .expr_paren {
2774 return t.map_index_lhs_type_cursor(lhs.edge(0))
2775 }
2776 if lhs.kind() == .expr_prefix {
2777 op := unsafe { token.Token(int(lhs.aux())) }
2778 if op == .mul {
2779 if inner_type := t.get_expr_type_cursor(lhs.edge(0)) {
2780 if inner_type is types.Pointer {
2781 return inner_type.base_type
2782 }
2783 }
2784 }
2785 }
2786 return t.get_expr_type_cursor(lhs)
2787}
2788
2789fn (mut t Transformer) map_expr_to_runtime_ptr_cursor(expr ast.Cursor, typ types.Type, mut out ast.FlatBuilder) ?ast.FlatNodeId {
2790 if t.is_pointer_type(typ) {
2791 return t.transform_expr_cursor_to_flat(expr, mut out)
2792 }
2793 if !t.can_take_address_expr_cursor(expr) {
2794 return none
2795 }
2796 transformed_id := t.transform_expr_cursor_to_flat(expr, mut out)
2797 return out.emit_prefix_expr_by_id(.amp, transformed_id, token.Pos{})
2798}
2799
2800fn (t &Transformer) is_enum_rvalue_cursor(expr ast.Cursor, typ types.Type) bool {
2801 if expr.kind() !in [.expr_ident, .expr_selector] {
2802 return false
2803 }
2804 mut base := typ
2805 for _ in 0 .. 64 {
2806 if base is types.Alias {
2807 base = (base as types.Alias).base_type
2808 continue
2809 }
2810 if base is types.Pointer {
2811 base = (base as types.Pointer).base_type
2812 continue
2813 }
2814 break
2815 }
2816 if base is types.Enum {
2817 return true
2818 }
2819 if expr_typ := t.get_expr_type_cursor(expr) {
2820 mut expr_base := expr_typ
2821 for _ in 0 .. 64 {
2822 if expr_base is types.Alias {
2823 expr_base = (expr_base as types.Alias).base_type
2824 continue
2825 }
2826 break
2827 }
2828 if expr_base is types.Enum {
2829 return true
2830 }
2831 }
2832 return false
2833}
2834
2835fn (t &Transformer) can_take_address_expr_cursor(expr ast.Cursor) bool {
2836 if !expr.is_valid() {
2837 return false
2838 }
2839 return match expr.kind() {
2840 .expr_ident {
2841 t.ident_is_addressable_lvalue(expr.name())
2842 }
2843 .expr_selector {
2844 t.selector_expr_is_addressable_lvalue_cursor(expr)
2845 }
2846 .expr_index {
2847 t.index_expr_is_addressable_lvalue_cursor(expr)
2848 }
2849 .expr_string, .expr_init, .expr_array_init {
2850 true
2851 }
2852 .expr_prefix {
2853 unsafe { token.Token(int(expr.aux())) } == .mul
2854 }
2855 .expr_paren {
2856 t.can_take_address_expr_cursor(expr.edge(0))
2857 }
2858 else {
2859 false
2860 }
2861 }
2862}
2863
2864fn (t &Transformer) index_expr_is_addressable_lvalue_cursor(expr ast.Cursor) bool {
2865 if map_type := t.map_index_lhs_type_cursor(expr.edge(0)) {
2866 if _ := t.unwrap_map_type(map_type) {
2867 return false
2868 }
2869 }
2870 return t.selector_lhs_can_take_field_address_cursor(expr.edge(0))
2871}
2872
2873fn (t &Transformer) selector_expr_is_addressable_lvalue_cursor(expr ast.Cursor) bool {
2874 lhs := expr.edge(0)
2875 if !lhs.is_valid() || lhs.kind() == .expr_empty {
2876 return false
2877 }
2878 if t.selector_resolves_to_non_lvalue_symbol_cursor(expr) {
2879 return false
2880 }
2881 return t.selector_lhs_can_take_field_address_cursor(lhs)
2882}
2883
2884fn (t &Transformer) selector_lhs_can_take_field_address_cursor(lhs ast.Cursor) bool {
2885 match lhs.kind() {
2886 .expr_ident {
2887 return t.ident_is_addressable_lvalue(lhs.name())
2888 }
2889 .expr_selector {
2890 return t.selector_expr_is_addressable_lvalue_cursor(lhs)
2891 }
2892 .expr_index {
2893 return t.index_expr_is_addressable_lvalue_cursor(lhs)
2894 }
2895 .expr_prefix {
2896 op := unsafe { token.Token(int(lhs.aux())) }
2897 return op == .mul || op == .amp
2898 }
2899 .expr_paren, .expr_modifier {
2900 return t.selector_lhs_can_take_field_address_cursor(lhs.edge(0))
2901 }
2902 .expr_string, .expr_init, .expr_array_init {
2903 return true
2904 }
2905 else {
2906 return t.expr_has_pointer_type_cursor(lhs)
2907 }
2908 }
2909}
2910
2911fn (t &Transformer) expr_has_pointer_type_cursor(expr ast.Cursor) bool {
2912 if typ := t.get_expr_type_cursor(expr) {
2913 return t.is_pointer_type(typ)
2914 }
2915 return false
2916}
2917
2918fn (t &Transformer) selector_resolves_to_non_lvalue_symbol_cursor(expr ast.Cursor) bool {
2919 lhs := expr.edge(0)
2920 if lhs.kind() == .expr_ident {
2921 lhs_name := lhs.name()
2922 rhs_name := selector_rhs_name_cursor(expr)
2923 if t.is_module_ident(lhs_name) {
2924 if obj := t.lookup_module_object(lhs_name, rhs_name) {
2925 return obj is types.Const || obj is types.Fn || obj is types.Module
2926 || obj is types.TypeObject || obj is types.Type
2927 }
2928 return true
2929 }
2930 qualified := if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin'
2931 && !lhs_name.contains('__') {
2932 '${t.cur_module}__${lhs_name}'
2933 } else {
2934 lhs_name
2935 }
2936 if _ := t.lookup_type(qualified) {
2937 return true
2938 }
2939 }
2940 return false
2941}
2942
2943fn (t &Transformer) infix_and_cursor_can_transform_direct(c ast.Cursor) bool {
2944 for term in t.flatten_and_terms_unwrapped_cursor(c) {
2945 if term.kind() == .expr_infix {
2946 if _ := t.smartcast_context_from_condition_term_cursor(term) {
2947 return false
2948 }
2949 }
2950 }
2951 return true
2952}
2953
2954// get_struct_field_type_cursor mirrors get_struct_field_type (struct.v) for a
2955// cursor selector, minus the smartcast branch — callers gate on
2956// !has_active_smartcast(), which makes that branch a no-op in the legacy
2957// pipeline too.
2958fn (t &Transformer) get_struct_field_type_cursor(sel ast.Cursor) ?types.Type {
2959 field_name := selector_rhs_name_cursor(sel)
2960 if field_name == '' {
2961 return none
2962 }
2963 lhs := sel.edge(0)
2964 mut struct_type_name := ''
2965 if lhs.kind() == .expr_ident {
2966 lhs_name := lhs.name()
2967 if lhs_name == '' {
2968 return none
2969 }
2970 if lhs_type := t.lookup_var_type(lhs_name) {
2971 if field_typ := t.field_type_from_receiver_type(lhs_type, field_name) {
2972 return field_typ
2973 }
2974 base_type := lhs_type.base_type()
2975 if base_type is types.Struct {
2976 if field_typ := t.lookup_struct_field_type(base_type.name, field_name) {
2977 return field_typ
2978 }
2979 }
2980 struct_type_name = t.type_to_name(base_type)
2981 }
2982 }
2983 if struct_type_name != '' {
2984 looked_up_type := t.lookup_type(struct_type_name) or { return none }
2985 looked_base := if looked_up_type is types.Pointer {
2986 looked_up_type.base_type
2987 } else {
2988 looked_up_type
2989 }
2990 match looked_base {
2991 types.Struct {
2992 if field_typ := t.lookup_struct_field_type(looked_base.name, field_name) {
2993 return field_typ
2994 }
2995 }
2996 else {}
2997 }
2998 }
2999 struct_type := t.get_expr_type_cursor(lhs) or { return none }
3000 base_type := if struct_type is types.Pointer {
3001 struct_type.base_type
3002 } else {
3003 struct_type
3004 }
3005 match base_type {
3006 types.Struct {
3007 if field_typ := t.lookup_struct_field_type(base_type.name, field_name) {
3008 return field_typ
3009 }
3010 }
3011 else {}
3012 }
3013
3014 return none
3015}
3016
3017// try_transform_array_append_cursor_to_flat streams the common `arr << value`
3018// single-element append cursor-native, mirroring the legacy lowering in
3019// transform_infix_expr (expr.v):
3020// builtin__array_push_noscan((array*)&arr, (elem_type[]){ value })
3021// The gates are deliberately strict so that every accepted shape provably
3022// takes the same single-push path in the legacy pipeline:
3023// - lhs is a plain ident whose scope type resolves to a dynamic array
3024// (mirrors get_array_elem_type_str's first, deterministic branch);
3025// - the elem type is not a (pointer-to-)sum type (no
3026// wrap_array_push_elem_value effect) and not a nested array (no
3027// push_many ambiguity);
3028// - the rhs has no calls (the legacy path hoists call-bearing values into
3029// a pending `_ap_tN` temp), is not enum shorthand, not a bitwise infix
3030// (reassociation), not an `_or_tN.data` extract, and its checker type is
3031// definitively NOT an array (so push_many cannot apply).
3032// Everything else returns none and keeps the legacy decode fallback.
3033fn (mut t Transformer) try_transform_array_append_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
3034 if t.is_eval_backend() {
3035 return none
3036 }
3037 lhs := c.edge(0)
3038 rhs := c.edge(1)
3039 mut elem_type_name := ''
3040 mut lhs_is_ptr := false
3041 if lhs.kind() == .expr_ident {
3042 lhs_typ := t.lookup_var_type(lhs.name()) or { return none }
3043 lhs_base := t.unwrap_alias_and_pointer_type(lhs_typ)
3044 if lhs_base !is types.Array {
3045 return none
3046 }
3047 arr_type := lhs_base as types.Array
3048 // Mirrors the ident branch of get_array_elem_type_str (which
3049 // normalizes literal elem type names).
3050 elem_type_name =
3051 t.normalize_literal_type(t.array_elem_type_name_for_helpers(arr_type.elem_type))
3052 lhs_is_ptr = (lhs.name() == t.cur_fn_recv_param && t.cur_fn_recv_is_ptr)
3053 || t.is_pointer_type(lhs_typ)
3054 } else if lhs.kind() == .expr_selector {
3055 // Mirrors the SelectorExpr branch of get_array_elem_type_str: struct
3056 // field type first, then the env type of the whole selector. Neither
3057 // normalizes the elem name. Smartcast scopes stay legacy (the legacy
3058 // chain consults smartcast_type_for_expr first).
3059 if t.has_active_smartcast() {
3060 return none
3061 }
3062 if field_typ := t.get_struct_field_type_cursor(lhs) {
3063 field_base := t.unwrap_alias_and_pointer_type(field_typ)
3064 if field_base is types.Array {
3065 elem_type_name = t.array_elem_type_name_for_helpers(field_base.elem_type)
3066 }
3067 }
3068 if elem_type_name == '' {
3069 typ := t.get_expr_type_cursor(lhs) or { return none }
3070 base := t.unwrap_alias_and_pointer_type(typ)
3071 if base !is types.Array {
3072 return none
3073 }
3074 arr_type := base as types.Array
3075 elem_type_name = t.array_elem_type_name_for_helpers(arr_type.elem_type)
3076 }
3077 // is_pointer_type_expr has no selector branch: always address-of.
3078 lhs_is_ptr = false
3079 } else {
3080 return none
3081 }
3082 if elem_type_name == '' || elem_type_name == 'void' {
3083 return none
3084 }
3085 if elem_type_name.starts_with('Array_') || elem_type_name == 'array' {
3086 return none
3087 }
3088 if t.sumtype_pointer_base(elem_type_name) != '' {
3089 return none
3090 }
3091 elem_is_sumtype := t.is_sum_type(elem_type_name)
3092 if rhs.kind() == .expr_selector {
3093 rhs_lhs := rhs.edge(0)
3094 if !rhs_lhs.is_valid() || rhs_lhs.kind() == .expr_empty {
3095 return none
3096 }
3097 if selector_rhs_name_cursor(rhs) == 'data' && rhs_lhs.kind() == .expr_ident
3098 && rhs_lhs.name().starts_with('_or_t') {
3099 return none
3100 }
3101 }
3102 if rhs.kind() == .expr_infix {
3103 rhs_op := unsafe { token.Token(int(rhs.aux())) }
3104 if rhs_op in [.amp, .pipe, .xor, .left_shift, .right_shift] {
3105 return none
3106 }
3107 }
3108 rhs_typ := t.get_expr_type_cursor(rhs) or { return none }
3109 rhs_base := t.unwrap_alias_and_pointer_type(rhs_typ)
3110 mut push_many := false
3111 if rhs_base is types.Array {
3112 // push_many candidate: resolve the rhs elem type the same way the
3113 // legacy append_rhs_is_array_value_compatible chain does (ident:
3114 // scope type, normalized; selector: struct field type, then the env
3115 // type of the whole selector, unnormalized), then require
3116 // push-compatibility with the lhs elem. Other rhs shapes keep the
3117 // legacy path.
3118 mut rhs_elem := ''
3119 if rhs.kind() == .expr_ident {
3120 rhs_scope_typ := t.lookup_var_type(rhs.name()) or { return none }
3121 rhs_scope_base := t.unwrap_alias_and_pointer_type(rhs_scope_typ)
3122 if rhs_scope_base !is types.Array {
3123 return none
3124 }
3125 rhs_arr := rhs_scope_base as types.Array
3126 rhs_elem =
3127 t.normalize_literal_type(t.array_elem_type_name_for_helpers(rhs_arr.elem_type))
3128 } else if rhs.kind() == .expr_selector {
3129 if t.has_active_smartcast() {
3130 return none
3131 }
3132 if field_typ := t.get_struct_field_type_cursor(rhs) {
3133 field_base := t.unwrap_alias_and_pointer_type(field_typ)
3134 if field_base is types.Array {
3135 rhs_elem = t.array_elem_type_name_for_helpers(field_base.elem_type)
3136 }
3137 }
3138 if rhs_elem == '' {
3139 rhs_arr := rhs_base as types.Array
3140 rhs_elem = t.array_elem_type_name_for_helpers(rhs_arr.elem_type)
3141 }
3142 } else {
3143 return none
3144 }
3145 if !t.array_elem_types_compatible(elem_type_name, rhs_elem) {
3146 return none
3147 }
3148 push_many = true
3149 } else if rhs_base is types.ArrayFixed {
3150 return none
3151 } else if rhs.kind() == .expr_ident {
3152 // The legacy rhs analysis consults the scope before the checker env;
3153 // require both to agree that the value is not an array.
3154 if rhs_scope_typ := t.lookup_var_type(rhs.name()) {
3155 rhs_scope_base := t.unwrap_alias_and_pointer_type(rhs_scope_typ)
3156 if rhs_scope_base is types.Array || rhs_scope_base is types.ArrayFixed {
3157 return none
3158 }
3159 }
3160 }
3161 if elem_is_sumtype {
3162 // Only the no-wrap case streams: when the pushed value's checker type
3163 // IS the sum type, wrap_array_push_elem_value provably returns it
3164 // unchanged (for hoisted call values the legacy path wraps the
3165 // `_ap_tN` temp ident, whose registered type is the sum type — same
3166 // no-op). Variant-typed values that need the sumtype init wrap keep
3167 // the legacy path, as do smartcast scopes.
3168 if t.has_active_smartcast() {
3169 return none
3170 }
3171 if !t.is_same_sumtype_name(t.type_to_c_name(rhs_typ), elem_type_name) {
3172 return none
3173 }
3174 if rhs.kind() == .expr_ident {
3175 rhs_scope_typ := t.lookup_var_type(rhs.name()) or { return none }
3176 if !t.is_same_sumtype_name(t.type_to_c_name(rhs_scope_typ), elem_type_name) {
3177 return none
3178 }
3179 } else if rhs.kind() != .expr_selector && !t.contains_call_expr_cursor(rhs) {
3180 return none
3181 }
3182 }
3183 // Emission mirrors the legacy tree shape and evaluation order exactly
3184 // (lhs transform, then rhs transform; synthetic wrapper nodes carry a
3185 // zero pos like their legacy counterparts).
3186 lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out)
3187 cast_inner_id := if lhs_is_ptr {
3188 lhs_id
3189 } else {
3190 out.emit_prefix_expr_by_id(.amp, lhs_id, c.pos())
3191 }
3192 array_ptr_typ_id := out.emit_ident_by_name('array*', token.Pos{})
3193 arr_ptr_id := out.emit_cast_expr_by_ids(array_ptr_typ_id, cast_inner_id, token.Pos{})
3194 mut rhs_id := t.transform_expr_cursor_to_flat(rhs, mut out)
3195 if push_many {
3196 // array__push_many(arr_ptr, rhs.data, rhs.len). Call-bearing values
3197 // are hoisted into a `_pm_tN` temp first (the legacy path does the
3198 // same so .data/.len don't evaluate the call twice); ident/selector
3199 // rhs is never a PrefixExpr, so no paren wrap applies. data/len are
3200 // typed synth selectors exactly like the legacy synth_selector calls
3201 // (same synth-pos consumption order).
3202 if t.contains_call_expr_cursor(rhs) {
3203 if t.pending_stmts.len > 0 {
3204 pending := t.pending_stmts.clone()
3205 t.pending_stmts.clear()
3206 for ps in pending {
3207 t.pending_flat_stmt_ids << out.emit_stmt(ps)
3208 }
3209 }
3210 t.temp_counter++
3211 tmp_name := '_pm_t${t.temp_counter}'
3212 if rhs_type := t.get_expr_type_cursor(rhs) {
3213 t.register_temp_var(tmp_name, rhs_type)
3214 }
3215 tmp_lhs_id := out.emit_ident_by_name(tmp_name, token.Pos{})
3216 t.pending_flat_stmt_ids << out.emit_assign_stmt_by_ids(.decl_assign, [
3217 tmp_lhs_id,
3218 ], [rhs_id], token.Pos{})
3219 rhs_id = out.emit_ident_by_name(tmp_name, token.Pos{})
3220 }
3221 data_id :=
3222 t.synth_selector_cursor_to_flat(rhs_id, 'data', types.Type(types.voidptr_), mut out)
3223 len_id := t.synth_selector_cursor_to_flat(rhs_id, 'len', types.Type(types.int_), mut out)
3224 push_many_lhs_id := out.emit_ident_by_name('array__push_many', token.Pos{})
3225 return out.emit_call_expr_by_ids(push_many_lhs_id, [arr_ptr_id, data_id, len_id], c.pos())
3226 }
3227 if t.contains_call_expr_cursor(rhs) {
3228 // The legacy path hoists call-bearing values into a `_ap_tN` temp via
3229 // pending_stmts so the call is evaluated before the push. Flush the
3230 // chronologically-earlier legacy pendings into the flat queue first,
3231 // then queue the temp assign as an already-flat stmt.
3232 if t.pending_stmts.len > 0 {
3233 pending := t.pending_stmts.clone()
3234 t.pending_stmts.clear()
3235 for ps in pending {
3236 t.pending_flat_stmt_ids << out.emit_stmt(ps)
3237 }
3238 }
3239 t.temp_counter++
3240 tmp_name := '_ap_t${t.temp_counter}'
3241 if rhs_type := t.get_expr_type_cursor(rhs) {
3242 t.register_temp_var(tmp_name, rhs_type)
3243 }
3244 tmp_lhs_id := out.emit_ident_by_name(tmp_name, token.Pos{})
3245 t.pending_flat_stmt_ids << out.emit_assign_stmt_by_ids(.decl_assign, [
3246 tmp_lhs_id,
3247 ], [rhs_id], token.Pos{})
3248 rhs_id = out.emit_ident_by_name(tmp_name, token.Pos{})
3249 }
3250 value_id := if elem_type_name == 'string' {
3251 clone_lhs_id := out.emit_ident_by_name('string__clone', token.Pos{})
3252 out.emit_call_expr_by_ids(clone_lhs_id, [rhs_id], token.Pos{})
3253 } else {
3254 rhs_id
3255 }
3256 elem_ident_id := out.emit_ident_by_name(elem_type_name, token.Pos{})
3257 arr_typ_id := out.emit_array_type_by_elem_id(elem_ident_id)
3258 arr_lit_id := out.emit_array_init_expr_by_ids(arr_typ_id, out.emit_expr(ast.empty_expr),
3259 out.emit_expr(ast.empty_expr), out.emit_expr(ast.empty_expr),
3260 out.emit_expr(ast.empty_expr), [value_id], token.Pos{})
3261 push_lhs_id := out.emit_ident_by_name('builtin__array_push_noscan', token.Pos{})
3262 return out.emit_call_expr_by_ids(push_lhs_id, [arr_ptr_id, arr_lit_id], c.pos())
3263}
3264
3265fn (t &Transformer) infix_left_shift_cursor_can_transform_direct(c ast.Cursor) bool {
3266 lhs_type := t.get_expr_type_cursor(c.edge(0)) or { return false }
3267 lhs_base := t.unwrap_alias_and_pointer_type(lhs_type)
3268 return lhs_base !is types.Array && lhs_base !is types.ArrayFixed
3269}
3270
3271fn (t &Transformer) infix_cursor_can_transform_direct(c ast.Cursor, op token.Token) bool {
3272 match op {
3273 .and {
3274 return t.infix_and_cursor_can_transform_direct(c)
3275 }
3276 .plus {
3277 return t.infix_plus_cursor_can_transform_direct(c)
3278 }
3279 .eq, .ne {
3280 return t.infix_equality_cursor_can_transform_direct(c)
3281 }
3282 .minus, .mul, .div, .mod {
3283 if lhs_type := t.get_expr_type_cursor(c.edge(0)) {
3284 if lhs_type is types.Struct {
3285 type_name := t.type_to_c_name(lhs_type)
3286 if type_name == 'time__Time' {
3287 return false
3288 }
3289 }
3290 }
3291 return true
3292 }
3293 .right_shift, .right_shift_unsigned {
3294 return true
3295 }
3296 .left_shift {
3297 return t.infix_left_shift_cursor_can_transform_direct(c)
3298 }
3299 .amp, .pipe, .xor {
3300 lhs := c.edge(0)
3301 if lhs.kind() == .expr_infix {
3302 lhs_op := unsafe { token.Token(int(lhs.aux())) }
3303 if lhs_op == .left_shift && !t.infix_left_shift_cursor_can_transform_direct(lhs) {
3304 return false
3305 }
3306 }
3307 return true
3308 }
3309 .logical_or {
3310 return true
3311 }
3312 .lt, .gt, .le, .ge {
3313 return !t.is_string_expr_cursor(c.edge(0)) && !t.is_string_expr_cursor(c.edge(1))
3314 }
3315 else {
3316 return false
3317 }
3318 }
3319}
3320
3321fn (mut t Transformer) infix_cursor_result_pos(c ast.Cursor) token.Pos {
3322 pos := c.pos()
3323 if pos.id != 0 || pos.offset != 0 {
3324 return pos
3325 }
3326 return t.next_synth_pos()
3327}
3328
3329fn call_expr_from_cursor(c ast.Cursor) ast.CallExpr {
3330 args_cap := if c.edge_count() > 1 { c.edge_count() - 1 } else { 0 }
3331 mut args := []ast.Expr{cap: args_cap}
3332 for i in 1 .. c.edge_count() {
3333 args << c.edge(i).expr()
3334 }
3335 return ast.CallExpr{
3336 lhs: c.edge(0).expr()
3337 args: args
3338 pos: c.pos()
3339 }
3340}
3341
3342fn (t &Transformer) empty_ident_call_has_identity_arg_pipeline(lhs ast.Cursor) bool {
3343 if lhs.kind() != .expr_ident || lhs.name() == '' {
3344 return false
3345 }
3346 info := t.generic_aware_call_fn_info_cursor(lhs, lhs.name()) or { return true }
3347 return info.param_types.len == 0 && !info.is_variadic && info.generic_params.len == 0
3348}
3349
3350fn (t &Transformer) ident_call_name_needs_legacy_arg_pipeline(fn_name string) bool {
3351 return
3352 fn_name in ['d', 'builtin__new_array_from_c_array_noscan', 'array__has', 'array__all', 'print', 'println', 'eprint', 'eprintln']
3353 || fn_name in t.elided_fns
3354}
3355
3356fn (t &Transformer) identity_call_arg_can_transform_direct(arg ast.Cursor, param_typ types.Type) bool {
3357 if t.type_is_string(param_typ) {
3358 return t.is_string_expr_cursor(arg)
3359 }
3360 base := t.unwrap_alias_type(param_typ)
3361 match base {
3362 types.Pointer {
3363 if arg.kind() == .expr_prefix {
3364 op := unsafe { token.Token(int(arg.aux())) }
3365 if op == .amp {
3366 return true
3367 }
3368 }
3369 arg_typ := t.get_expr_type_cursor(arg) or { return false }
3370 arg_base := t.unwrap_alias_type(arg_typ)
3371 return arg_base is types.Pointer || arg_base is types.Nil
3372 }
3373 types.Primitive, types.Char, types.ISize, types.Nil, types.Rune, types.String, types.USize {
3374 return true
3375 }
3376 types.Struct {
3377 // An arg whose checker type is EXACTLY the (non-generic) param
3378 // struct needs no call-boundary coercion (no sumtype wrap, no
3379 // interface boxing, no auto-deref). Smartcast scopes stay on the
3380 // legacy path: an active cast can change an ident's effective
3381 // type between the declared and narrowed form.
3382 if base.generic_params.len > 0 || t.has_active_smartcast() {
3383 return false
3384 }
3385 arg_typ := t.get_expr_type_cursor(arg) or { return false }
3386 arg_base := t.unwrap_alias_type(arg_typ)
3387 if arg_base is types.Struct {
3388 return arg_base.name == base.name && arg_base.generic_params.len == 0
3389 }
3390 return false
3391 }
3392 types.SumType {
3393 // Exact sumtype-to-sumtype: the arg is already the param's sum
3394 // type, so no variant wrapping applies.
3395 if t.has_active_smartcast() {
3396 return false
3397 }
3398 arg_typ := t.get_expr_type_cursor(arg) or { return false }
3399 arg_base := t.unwrap_alias_type(arg_typ)
3400 if arg_base is types.SumType {
3401 return arg_base.name == base.name
3402 }
3403 return false
3404 }
3405 types.Enum {
3406 // Typed enum values pass through unchanged. Bare `.value`
3407 // shorthand args need the param's enum context to resolve, so
3408 // they keep the legacy pipeline (as does anything that is not a
3409 // plain ident/qualified selector).
3410 if t.has_active_smartcast() {
3411 return false
3412 }
3413 if arg.kind() == .expr_ident {
3414 // fine: a typed enum variable resolves without param context
3415 } else if arg.kind() == .expr_selector {
3416 lhs := arg.edge(0)
3417 if !lhs.is_valid() || lhs.kind() == .expr_empty {
3418 return false
3419 }
3420 } else {
3421 return false
3422 }
3423 arg_typ := t.get_expr_type_cursor(arg) or { return false }
3424 arg_base := t.unwrap_alias_type(arg_typ)
3425 if arg_base is types.Enum {
3426 return arg_base.name == base.name
3427 }
3428 return false
3429 }
3430 else {
3431 return false
3432 }
3433 }
3434}
3435
3436fn (t &Transformer) call_empty_ident_can_transform_direct(c ast.Cursor) bool {
3437 if c.kind() != .expr_call || c.edge_count() != 1 || t.has_active_smartcast() {
3438 return false
3439 }
3440 return t.empty_ident_call_has_identity_arg_pipeline(c.edge(0))
3441}
3442
3443fn (t &Transformer) call_ident_args_can_transform_direct(c ast.Cursor) bool {
3444 if c.kind() != .expr_call || c.edge_count() <= 1 {
3445 return false
3446 }
3447 lhs := c.edge(0)
3448 if lhs.kind() != .expr_ident || lhs.name() == '' {
3449 return false
3450 }
3451 fn_name := lhs.name()
3452 if t.ident_call_name_needs_legacy_arg_pipeline(fn_name) {
3453 return false
3454 }
3455 info := t.generic_aware_call_fn_info_cursor(lhs, fn_name) or { return false }
3456 arg_count := c.edge_count() - 1
3457 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 {
3458 return false
3459 }
3460 for i in 0 .. arg_count {
3461 arg := c.edge(i + 1)
3462 if arg.kind() == .aux_field_init {
3463 return false
3464 }
3465 if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i]) {
3466 return false
3467 }
3468 }
3469 return true
3470}
3471
3472fn (t &Transformer) resolve_module_call_name_cursor(lhs ast.Cursor) ?string {
3473 if lhs.kind() != .expr_selector {
3474 return none
3475 }
3476 rhs_name := selector_rhs_name_cursor(lhs)
3477 if rhs_name == '' {
3478 return none
3479 }
3480 base := lhs.edge(0)
3481 if base.kind() == .expr_selector {
3482 inner_base := base.edge(0)
3483 if inner_base.kind() == .expr_ident && t.is_module_ident(inner_base.name()) {
3484 sub_mod := selector_rhs_name_cursor(base)
3485 if sub_mod != '' {
3486 if t.get_module_scope(sub_mod) != none {
3487 return '${sub_mod}__${rhs_name}'
3488 }
3489 full_mod := '${inner_base.name()}__${sub_mod}'
3490 if t.get_module_scope(full_mod) != none {
3491 return '${full_mod}__${rhs_name}'
3492 }
3493 }
3494 }
3495 return none
3496 }
3497 if base.kind() != .expr_ident {
3498 return none
3499 }
3500 lhs_name := base.name()
3501 if t.lookup_var_type(lhs_name) != none {
3502 return none
3503 }
3504 if resolved_mod := t.resolve_module_name(lhs_name) {
3505 mut module_names := []string{cap: 2}
3506 module_names << resolved_mod
3507 if lhs_name != resolved_mod {
3508 module_names << lhs_name
3509 }
3510 for mod_name in module_names {
3511 if _ := t.lookup_fn_cached(mod_name, rhs_name) {
3512 call_mod := if mod_name.contains('.') {
3513 mod_name.all_after_last('.')
3514 } else if mod_name.contains('__') {
3515 mod_name.all_after_last('__')
3516 } else {
3517 mod_name
3518 }
3519 return '${call_mod}__${rhs_name}'
3520 }
3521 }
3522 }
3523 if call_prefix := t.resolve_module_call_prefix(lhs_name) {
3524 if _ := t.lookup_fn_cached(call_prefix, rhs_name) {
3525 return '${call_prefix}__${rhs_name}'
3526 }
3527 }
3528 return none
3529}
3530
3531fn (t &Transformer) call_selector_module_name_can_transform_direct(c ast.Cursor) ?string {
3532 if c.kind() != .expr_call || c.edge_count() == 0 {
3533 return none
3534 }
3535 lhs := c.edge(0)
3536 call_name := t.resolve_module_call_name_cursor(lhs) or { return none }
3537 rhs_name := selector_rhs_name_cursor(lhs)
3538 if rhs_name in t.elided_fns || call_name in t.elided_fns {
3539 return none
3540 }
3541 info := t.generic_aware_call_fn_info_cursor(lhs, call_name) or { return none }
3542 arg_count := c.edge_count() - 1
3543 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 {
3544 return none
3545 }
3546 for i in 0 .. arg_count {
3547 arg := c.edge(i + 1)
3548 if arg.kind() == .aux_field_init {
3549 return none
3550 }
3551 if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i]) {
3552 return none
3553 }
3554 }
3555 return call_name
3556}
3557
3558fn (t &Transformer) call_selector_static_name_can_transform_direct(c ast.Cursor) ?string {
3559 if c.kind() != .expr_call || c.edge_count() == 0 {
3560 return none
3561 }
3562 lhs := c.edge(0)
3563 if lhs.kind() != .expr_selector {
3564 return none
3565 }
3566 method_name := selector_rhs_name_cursor(lhs)
3567 if method_name == '' || method_name in t.elided_fns {
3568 return none
3569 }
3570 call_name := t.resolve_static_type_method_call_cursor(lhs.edge(0), method_name) or {
3571 return none
3572 }
3573 if call_name in t.elided_fns {
3574 return none
3575 }
3576 info := t.generic_aware_call_fn_info_cursor(lhs, call_name) or { return none }
3577 arg_count := c.edge_count() - 1
3578 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 {
3579 return none
3580 }
3581 for i in 0 .. arg_count {
3582 arg := c.edge(i + 1)
3583 if arg.kind() == .aux_field_init {
3584 return none
3585 }
3586 if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i]) {
3587 return none
3588 }
3589 }
3590 return call_name
3591}
3592
3593fn method_name_needs_legacy_selector_pipeline(method_name string) bool {
3594 return method_name in ['sort', 'sorted', 'zero', 'has', 'all', 'contains', 'index', 'last_index',
3595 'str', 'type_name', 'clone', 'insert', 'prepend']
3596}
3597
3598fn (t &Transformer) receiver_method_cursor_can_transform_direct(receiver ast.Cursor, method_name string) bool {
3599 if t.has_active_smartcast() || method_name_needs_legacy_selector_pipeline(method_name) {
3600 return false
3601 }
3602 recv_type := t.get_expr_type_cursor(receiver) or { return false }
3603 base := t.unwrap_alias_and_pointer_type(recv_type)
3604 if base is types.Struct {
3605 return t.type_has_cached_method(base, method_name)
3606 }
3607 // Non-struct receivers. Methods with transformer-level expansions
3608 // (hoisted loops) and dynamic-dispatch receivers stay on the legacy
3609 // pipeline; the rest resolve to a plain method fn by name.
3610 if method_name in ['filter', 'map', 'any', 'count', 'wait'] {
3611 return false
3612 }
3613 if base is types.Interface || base is types.SumType {
3614 return false
3615 }
3616 // Alias receiver with its own declared method (e.g. strings.Builder.writeln).
3617 if t.resolve_alias_receiver_method_name(recv_type, method_name) != none {
3618 return true
3619 }
3620 // Plain string methods (string__starts_with etc.).
3621 if t.type_is_string(recv_type) {
3622 return t.lookup_method_cached('string', method_name) != none
3623 }
3624 return false
3625}
3626
3627// enum_shorthand_arg_can_transform_direct reports whether `arg` is a bare
3628// `.member` enum shorthand for an enum-typed parameter — the one non-identity
3629// arg shape the direct call arms resolve themselves (mirroring
3630// resolve_expr_with_expected_type -> resolve_enum_shorthand).
3631fn (t &Transformer) enum_shorthand_arg_can_transform_direct(arg ast.Cursor, param_typ types.Type) bool {
3632 if t.has_active_smartcast() {
3633 return false
3634 }
3635 param_base := t.unwrap_alias_type(param_typ)
3636 if param_base !is types.Enum {
3637 return false
3638 }
3639 if arg.kind() != .expr_selector {
3640 return false
3641 }
3642 lhs := arg.edge(0)
3643 return !lhs.is_valid() || lhs.kind() == .expr_empty
3644}
3645
3646// transform_call_arg_cursor_to_flat emits one direct-call argument with the
3647// parameter type as context: bare enum shorthand resolves to the member ident
3648// exactly like the legacy resolve_enum_shorthand (same synth-type
3649// registration at the selector pos); everything else transforms normally.
3650fn (mut t Transformer) transform_call_arg_cursor_to_flat(arg ast.Cursor, param_typ types.Type, mut out ast.FlatBuilder) ast.FlatNodeId {
3651 if t.enum_shorthand_arg_can_transform_direct(arg, param_typ) {
3652 param_base := t.unwrap_alias_type(param_typ)
3653 enum_name := t.type_to_c_name(param_base)
3654 member := selector_rhs_name_cursor(arg)
3655 if typ := t.lookup_type(enum_name) {
3656 t.register_synth_type(arg.pos(), typ)
3657 if typ is types.Enum {
3658 return out.emit_ident_by_name(t.enum_member_ident_for_lookup(enum_name, typ, member),
3659 arg.pos())
3660 }
3661 }
3662 return out.emit_ident_by_name(enum_member_ident(enum_name, member), arg.pos())
3663 }
3664 return t.transform_expr_cursor_to_flat(arg, mut out)
3665}
3666
3667// direct_method_call_fn_info_cursor returns the resolved method's declared
3668// receiver-less signature for the direct-call gates. The shared
3669// generic_aware_call_fn_info_cursor path heuristically strips a "receiver"
3670// param from checker fn types, which eats the real first param whenever its
3671// type equals the receiver type (e.g. s.starts_with(prefix) — both string).
3672// The cached method declaration never includes the receiver in its params,
3673// so it is the ground truth for arity and per-arg identity checks.
3674fn (t &Transformer) direct_method_call_fn_info_cursor(lhs ast.Cursor, method_name string, call_name string) ?CallFnInfo {
3675 recv_key := call_name.all_before_last('__')
3676 mut lookup_names := []string{cap: 2}
3677 lookup_names << recv_key
3678 if recv_key.contains('__') {
3679 lookup_names << recv_key.all_after_last('__')
3680 }
3681 for name in lookup_names {
3682 if fn_type := t.lookup_method_cached(name, method_name) {
3683 return call_fn_info_from_fn_type(fn_type)
3684 }
3685 }
3686 return t.generic_aware_call_fn_info_cursor(lhs, call_name)
3687}
3688
3689fn (t &Transformer) call_selector_method_name_can_transform_direct(c ast.Cursor) ?string {
3690 if c.kind() != .expr_call || c.edge_count() == 0 {
3691 return none
3692 }
3693 lhs := c.edge(0)
3694 if lhs.kind() != .expr_selector {
3695 return none
3696 }
3697 receiver := lhs.edge(0)
3698 method_name := selector_rhs_name_cursor(lhs)
3699 if method_name == '' || method_name in t.elided_fns
3700 || !t.receiver_method_cursor_can_transform_direct(receiver, method_name) {
3701 return none
3702 }
3703 if t.resolve_static_type_method_call_cursor(receiver, method_name) != none {
3704 return none
3705 }
3706 call_name := t.resolve_method_call_name_cursor(receiver, method_name) or { return none }
3707 if call_name in t.elided_fns {
3708 return none
3709 }
3710 info := t.direct_method_call_fn_info_cursor(lhs, method_name, call_name) or { return none }
3711 arg_count := c.edge_count() - 1
3712 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 {
3713 return none
3714 }
3715 for i in 0 .. arg_count {
3716 arg := c.edge(i + 1)
3717 if arg.kind() == .aux_field_init {
3718 return none
3719 }
3720 if !t.identity_call_arg_can_transform_direct(arg, info.param_types[i])
3721 && !t.enum_shorthand_arg_can_transform_direct(arg, info.param_types[i]) {
3722 return none
3723 }
3724 }
3725 return call_name
3726}
3727
3728fn call_or_cast_expr_from_cursor(c ast.Cursor) ast.CallOrCastExpr {
3729 return ast.CallOrCastExpr{
3730 lhs: c.edge(0).expr()
3731 expr: c.edge(1).expr()
3732 pos: c.pos()
3733 }
3734}
3735
3736fn (t &Transformer) call_or_cast_empty_ident_call_can_transform_direct(c ast.Cursor) bool {
3737 if c.kind() != .expr_call_or_cast || c.edge_count() < 2 {
3738 return false
3739 }
3740 lhs := c.edge(0)
3741 arg := c.edge(1)
3742 if lhs.kind() != .expr_ident || (arg.is_valid() && arg.kind() != .expr_empty) {
3743 return false
3744 }
3745 if t.call_or_cast_lhs_is_type_cursor(lhs) {
3746 return true
3747 }
3748 return t.empty_ident_call_has_identity_arg_pipeline(lhs)
3749}
3750
3751fn (t &Transformer) call_or_cast_ident_arg_can_transform_direct(c ast.Cursor) bool {
3752 if c.kind() != .expr_call_or_cast || c.edge_count() < 2 {
3753 return false
3754 }
3755 lhs := c.edge(0)
3756 arg := c.edge(1)
3757 if lhs.kind() != .expr_ident || lhs.name() == '' || !arg.is_valid() || arg.kind() == .expr_empty {
3758 return false
3759 }
3760 fn_name := lhs.name()
3761 if t.call_or_cast_lhs_is_type_cursor(lhs)
3762 || t.ident_call_name_needs_legacy_arg_pipeline(fn_name) {
3763 return false
3764 }
3765 info := t.generic_aware_call_fn_info_cursor(lhs, fn_name) or { return false }
3766 if info.param_types.len != 1 || info.is_variadic || info.generic_params.len > 0
3767 || arg.kind() == .aux_field_init {
3768 return false
3769 }
3770 return t.identity_call_arg_can_transform_direct(arg, info.param_types[0])
3771}
3772
3773fn (t &Transformer) call_or_cast_selector_static_name_can_transform_direct(c ast.Cursor) ?string {
3774 if c.kind() != .expr_call_or_cast || c.edge_count() < 2 {
3775 return none
3776 }
3777 lhs := c.edge(0)
3778 if lhs.kind() != .expr_selector {
3779 return none
3780 }
3781 method_name := selector_rhs_name_cursor(lhs)
3782 if method_name == '' || method_name in t.elided_fns {
3783 return none
3784 }
3785 call_name := t.resolve_static_type_method_call_cursor(lhs.edge(0), method_name) or {
3786 return none
3787 }
3788 if call_name in t.elided_fns {
3789 return none
3790 }
3791 arg := c.edge(1)
3792 arg_count := if arg.is_valid() && arg.kind() != .expr_empty { 1 } else { 0 }
3793 info := t.generic_aware_call_fn_info_cursor(lhs, call_name) or { return none }
3794 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 {
3795 return none
3796 }
3797 if arg_count == 1 {
3798 if arg.kind() == .aux_field_init
3799 || !t.identity_call_arg_can_transform_direct(arg, info.param_types[0]) {
3800 return none
3801 }
3802 }
3803 return call_name
3804}
3805
3806fn (t &Transformer) call_or_cast_selector_method_name_can_transform_direct(c ast.Cursor) ?string {
3807 if c.kind() != .expr_call_or_cast || c.edge_count() < 2 {
3808 return none
3809 }
3810 lhs := c.edge(0)
3811 if lhs.kind() != .expr_selector {
3812 return none
3813 }
3814 receiver := lhs.edge(0)
3815 method_name := selector_rhs_name_cursor(lhs)
3816 if method_name == '' || method_name in t.elided_fns
3817 || !t.receiver_method_cursor_can_transform_direct(receiver, method_name) {
3818 return none
3819 }
3820 if t.resolve_static_type_method_call_cursor(receiver, method_name) != none {
3821 return none
3822 }
3823 call_name := t.resolve_method_call_name_cursor(receiver, method_name) or { return none }
3824 if call_name in t.elided_fns {
3825 return none
3826 }
3827 arg := c.edge(1)
3828 arg_count := if arg.is_valid() && arg.kind() != .expr_empty { 1 } else { 0 }
3829 info := t.direct_method_call_fn_info_cursor(lhs, method_name, call_name) or { return none }
3830 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 {
3831 return none
3832 }
3833 if arg_count == 1 {
3834 if arg.kind() == .aux_field_init
3835 || (!t.identity_call_arg_can_transform_direct(arg, info.param_types[0])
3836 && !t.enum_shorthand_arg_can_transform_direct(arg, info.param_types[0])) {
3837 return none
3838 }
3839 }
3840 return call_name
3841}
3842
3843fn (t &Transformer) call_or_cast_selector_module_name_can_transform_direct(c ast.Cursor) ?string {
3844 if c.kind() != .expr_call_or_cast || c.edge_count() < 2 {
3845 return none
3846 }
3847 lhs := c.edge(0)
3848 if lhs.kind() != .expr_selector || t.call_or_cast_lhs_is_type_cursor(lhs) {
3849 return none
3850 }
3851 call_name := t.resolve_module_call_name_cursor(lhs) or { return none }
3852 rhs_name := selector_rhs_name_cursor(lhs)
3853 if rhs_name in t.elided_fns || call_name in t.elided_fns {
3854 return none
3855 }
3856 arg := c.edge(1)
3857 arg_count := if arg.is_valid() && arg.kind() != .expr_empty { 1 } else { 0 }
3858 info := t.generic_aware_call_fn_info_cursor(lhs, call_name) or { return none }
3859 if info.param_types.len != arg_count || info.is_variadic || info.generic_params.len > 0 {
3860 return none
3861 }
3862 if arg_count == 1 {
3863 if arg.kind() == .aux_field_init
3864 || !t.identity_call_arg_can_transform_direct(arg, info.param_types[0]) {
3865 return none
3866 }
3867 }
3868 return call_name
3869}
3870
3871fn index_expr_from_cursor(c ast.Cursor) ast.IndexExpr {
3872 return ast.IndexExpr{
3873 lhs: c.edge(0).expr()
3874 expr: c.edge(1).expr()
3875 is_gated: c.flag(ast.flag_is_gated)
3876 pos: c.pos()
3877 }
3878}
3879
3880fn selector_expr_from_cursor(c ast.Cursor) ast.SelectorExpr {
3881 return ast.SelectorExpr{
3882 lhs: c.edge(0).expr()
3883 rhs: c.edge(1).ident()
3884 pos: c.pos()
3885 }
3886}
3887
3888fn if_expr_from_cursor(c ast.Cursor) ast.IfExpr {
3889 stmts_cap := if c.edge_count() > 2 { c.edge_count() - 2 } else { 0 }
3890 mut stmts := []ast.Stmt{cap: stmts_cap}
3891 for i in 2 .. c.edge_count() {
3892 stmts << c.edge(i).stmt()
3893 }
3894 return ast.IfExpr{
3895 cond: c.edge(0).expr()
3896 else_expr: c.edge(1).expr()
3897 stmts: stmts
3898 pos: c.pos()
3899 }
3900}
3901
3902fn map_init_expr_from_cursor(c ast.Cursor) ast.MapInitExpr {
3903 keys_len := c.extra_int()
3904 mut keys := []ast.Expr{cap: keys_len}
3905 for i in 0 .. keys_len {
3906 keys << c.edge(1 + i).expr()
3907 }
3908 vals_cap := if c.edge_count() > 1 + keys_len {
3909 c.edge_count() - 1 - keys_len
3910 } else {
3911 0
3912 }
3913 mut vals := []ast.Expr{cap: vals_cap}
3914 for i in (1 + keys_len) .. c.edge_count() {
3915 vals << c.edge(i).expr()
3916 }
3917 return ast.MapInitExpr{
3918 typ: c.edge(0).expr()
3919 keys: keys
3920 vals: vals
3921 pos: c.pos()
3922 }
3923}
3924
3925struct MapCursorTypeParts {
3926mut:
3927 key_cursor ast.Cursor
3928 val_cursor ast.Cursor
3929 key_expr ast.Expr
3930 val_expr ast.Expr
3931 key_type_name string
3932}
3933
3934fn (t &Transformer) map_cursor_type_parts(c ast.Cursor) MapCursorTypeParts {
3935 mut parts := MapCursorTypeParts{
3936 key_expr: ast.Expr(ast.Ident{
3937 name: 'int'
3938 })
3939 val_expr: ast.Expr(ast.Ident{
3940 name: 'int'
3941 })
3942 key_type_name: 'int'
3943 }
3944 typ_c := c.edge(0)
3945 match typ_c.kind() {
3946 .typ_map {
3947 parts.key_cursor = typ_c.edge(0)
3948 parts.val_cursor = typ_c.edge(1)
3949 parts.key_type_name = t.type_expr_name_full_cursor(parts.key_cursor)
3950 if parts.key_type_name == '' {
3951 parts.key_type_name = 'int'
3952 }
3953 return parts
3954 }
3955 .expr_ident, .expr_selector {
3956 type_name := if typ_c.kind() == .expr_ident {
3957 typ_c.name()
3958 } else {
3959 t.type_expr_name_full_cursor(typ_c)
3960 }
3961 if explicit_map_typ := t.lookup_type(type_name) {
3962 if explicit_map := t.unwrap_map_type(explicit_map_typ) {
3963 parts.key_expr = t.type_to_ast_type_expr(explicit_map.key_type)
3964 parts.val_expr = t.type_to_ast_type_expr(explicit_map.value_type)
3965 parts.key_type_name = t.type_to_c_name(explicit_map.key_type)
3966 return parts
3967 }
3968 }
3969 }
3970 else {}
3971 }
3972
3973 if inferred := t.get_expr_type_cursor(c) {
3974 if inferred_map := t.unwrap_map_type(inferred) {
3975 parts.key_expr = t.type_to_ast_type_expr(inferred_map.key_type)
3976 parts.val_expr = t.type_to_ast_type_expr(inferred_map.value_type)
3977 parts.key_type_name = t.type_to_c_name(inferred_map.key_type)
3978 }
3979 }
3980 if parts.key_type_name == '' {
3981 parts.key_type_name = 'int'
3982 }
3983 return parts
3984}
3985
3986fn cursor_is_string_literal(c ast.Cursor) bool {
3987 if !c.is_valid() {
3988 return false
3989 }
3990 if c.kind() == .expr_string || c.kind() == .expr_string_inter {
3991 return true
3992 }
3993 if c.kind() == .expr_basic_literal {
3994 kind := unsafe { token.Token(int(c.aux())) }
3995 return kind == .string
3996 }
3997 return false
3998}
3999
4000fn map_key_cursor_is_enum_shorthand(c ast.Cursor) bool {
4001 if !c.is_valid() || c.kind() != .expr_selector {
4002 return false
4003 }
4004 lhs := c.edge(0)
4005 if lhs.kind() == .expr_empty {
4006 return true
4007 }
4008 return lhs.kind() == .expr_ident && lhs.name() == ''
4009}
4010
4011fn emit_map_cursor_type_id(type_cursor ast.Cursor, type_expr ast.Expr, mut out ast.FlatBuilder) ast.FlatNodeId {
4012 if type_cursor.is_valid() {
4013 return out.copy_subtree_from(type_cursor.flat, type_cursor.id)
4014 }
4015 return out.emit_expr(type_expr)
4016}
4017
4018fn emit_addr_of_ident_to_flat(name string, mut out ast.FlatBuilder) ast.FlatNodeId {
4019 ident_id := out.emit_ident_by_name(name, token.Pos{})
4020 return out.emit_prefix_expr_by_id(.amp, ident_id, token.Pos{})
4021}
4022
4023fn emit_sizeof_type_id_to_flat(type_id ast.FlatNodeId, mut out ast.FlatBuilder) ast.FlatNodeId {
4024 return out.emit_keyword_operator_by_ids(.key_sizeof, [type_id], token.Pos{})
4025}
4026
4027fn (mut t Transformer) transform_map_key_cursor_to_flat(c ast.Cursor, key_type_name string, mut out ast.FlatBuilder) ast.FlatNodeId {
4028 if key_type_name == '' || key_type_name == 'int' || !map_key_cursor_is_enum_shorthand(c) {
4029 return t.transform_expr_cursor_to_flat(c, mut out)
4030 }
4031 member := c.edge(1)
4032 if typ := t.lookup_type(key_type_name) {
4033 t.register_synth_type(c.pos(), typ)
4034 if typ is types.Enum {
4035 return out.emit_ident_by_name(t.enum_member_ident_for_lookup(key_type_name, typ,
4036 member.name()), c.pos())
4037 }
4038 }
4039 return out.emit_ident_by_name(enum_member_ident(key_type_name, member.name()), c.pos())
4040}
4041
4042fn array_elem_type_cursor(type_cursor ast.Cursor) ?ast.Cursor {
4043 if type_cursor.is_valid() && type_cursor.kind() == .typ_array {
4044 return type_cursor.edge(0)
4045 }
4046 return none
4047}
4048
4049fn array_elem_type_expr(type_expr ast.Expr) ?ast.Expr {
4050 if type_expr is ast.Type {
4051 type_payload := type_expr as ast.Type
4052 if type_payload is ast.ArrayType {
4053 array_type := type_payload as ast.ArrayType
4054 return array_type.elem_type
4055 }
4056 }
4057 return none
4058}
4059
4060fn (mut t Transformer) transform_map_value_cursor_to_flat(c ast.Cursor, parts MapCursorTypeParts, mut out ast.FlatBuilder) ?ast.FlatNodeId {
4061 if c.kind() == .expr_array_init {
4062 typ := c.edge(0)
4063 if !typ.is_valid() || typ.kind() == .expr_empty {
4064 if elem_cursor := array_elem_type_cursor(parts.val_cursor) {
4065 return t.transform_dynamic_array_init_cursor_with_elem_type_to_flat(c, elem_cursor,
4066 ast.empty_expr, mut out)
4067 }
4068 if elem_expr := array_elem_type_expr(parts.val_expr) {
4069 return t.transform_dynamic_array_init_cursor_with_elem_type_to_flat(c, ast.Cursor{},
4070 elem_expr, mut out)
4071 }
4072 return none
4073 }
4074 }
4075 return t.transform_expr_cursor_to_flat(c, mut out)
4076}
4077
4078fn (mut t Transformer) transform_map_init_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
4079 keys_len := c.extra_int()
4080 mut parts := t.map_cursor_type_parts(c)
4081 if keys_len > 0 && parts.key_type_name == 'int' {
4082 first_key := c.edge(1)
4083 first_val := c.edge(1 + keys_len)
4084 if cursor_is_string_literal(first_key) {
4085 parts.key_cursor = ast.Cursor{}
4086 parts.key_expr = ast.Expr(ast.Ident{
4087 name: 'string'
4088 })
4089 parts.key_type_name = 'string'
4090 }
4091 if cursor_is_string_literal(first_val) {
4092 parts.val_cursor = ast.Cursor{}
4093 parts.val_expr = ast.Expr(ast.Ident{
4094 name: 'string'
4095 })
4096 }
4097 }
4098 mut key_ids := []ast.FlatNodeId{cap: keys_len}
4099 for i in 0 .. keys_len {
4100 key_ids << t.transform_map_key_cursor_to_flat(c.edge(1 + i), parts.key_type_name, mut out)
4101 }
4102 vals_start := 1 + keys_len
4103 mut val_ids := []ast.FlatNodeId{cap: c.edge_count() - vals_start}
4104 for i in vals_start .. c.edge_count() {
4105 val_ids << t.transform_map_value_cursor_to_flat(c.edge(i), parts, mut out) or {
4106 return none
4107 }
4108 }
4109 if t.is_eval_backend() {
4110 typ_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4111 return out.emit_map_init_expr_by_ids(typ_id, key_ids, val_ids, c.pos())
4112 }
4113 hash_fn, eq_fn, clone_fn, free_fn := map_runtime_key_fns_from_type_name(parts.key_type_name)
4114 if keys_len == 0 {
4115 lhs_id := out.emit_ident_by_name('new_map', token.Pos{})
4116 key_sizeof_id := emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(parts.key_cursor,
4117 parts.key_expr, mut out), mut out)
4118 val_sizeof_id := emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(parts.val_cursor,
4119 parts.val_expr, mut out), mut out)
4120 return out.emit_call_expr_by_ids(lhs_id, [
4121 key_sizeof_id,
4122 val_sizeof_id,
4123 emit_addr_of_ident_to_flat(hash_fn, mut out),
4124 emit_addr_of_ident_to_flat(eq_fn, mut out),
4125 emit_addr_of_ident_to_flat(clone_fn, mut out),
4126 emit_addr_of_ident_to_flat(free_fn, mut out),
4127 ], c.pos())
4128 }
4129 lhs_id := out.emit_ident_by_name('new_map_init_noscan_value', token.Pos{})
4130 key_size_id := emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(parts.key_cursor,
4131 parts.key_expr, mut out), mut out)
4132 val_size_id := emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(parts.val_cursor,
4133 parts.val_expr, mut out), mut out)
4134 key_array_typ_id := out.emit_array_type_by_elem_id(emit_map_cursor_type_id(parts.key_cursor,
4135 parts.key_expr, mut out))
4136 val_array_typ_id := out.emit_array_type_by_elem_id(emit_map_cursor_type_id(parts.val_cursor,
4137 parts.val_expr, mut out))
4138 key_array_id := out.emit_array_init_expr_by_ids(key_array_typ_id,
4139 out.emit_expr(ast.empty_expr), out.emit_expr(ast.empty_expr),
4140 out.emit_expr(ast.empty_expr), out.emit_expr(ast.empty_expr), key_ids, token.Pos{})
4141 val_array_id := out.emit_array_init_expr_by_ids(val_array_typ_id,
4142 out.emit_expr(ast.empty_expr), out.emit_expr(ast.empty_expr),
4143 out.emit_expr(ast.empty_expr), out.emit_expr(ast.empty_expr), val_ids, token.Pos{})
4144 return out.emit_call_expr_by_ids(lhs_id, [
4145 emit_addr_of_ident_to_flat(hash_fn, mut out),
4146 emit_addr_of_ident_to_flat(eq_fn, mut out),
4147 emit_addr_of_ident_to_flat(clone_fn, mut out),
4148 emit_addr_of_ident_to_flat(free_fn, mut out),
4149 out.emit_basic_literal_by_value(.number, keys_len.str(), token.Pos{}),
4150 key_size_id,
4151 val_size_id,
4152 key_array_id,
4153 val_array_id,
4154 ], c.pos())
4155}
4156
4157fn array_init_expr_from_cursor(c ast.Cursor) ast.ArrayInitExpr {
4158 exprs_cap := if c.edge_count() > 5 { c.edge_count() - 5 } else { 0 }
4159 mut exprs := []ast.Expr{cap: exprs_cap}
4160 for i in 5 .. c.edge_count() {
4161 exprs << c.edge(i).expr()
4162 }
4163 return ast.ArrayInitExpr{
4164 typ: c.edge(0).expr()
4165 init: c.edge(1).expr()
4166 cap: c.edge(2).expr()
4167 len: c.edge(3).expr()
4168 update_expr: c.edge(4).expr()
4169 exprs: exprs
4170 pos: c.pos()
4171 }
4172}
4173
4174fn (mut t Transformer) transform_fixed_array_init_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
4175 if t.is_native_be {
4176 return none
4177 }
4178 typ := c.edge(0)
4179 if !typ.is_valid() || typ.kind() != .typ_array_fixed {
4180 return none
4181 }
4182 if c.edge(4).is_valid() && c.edge(4).kind() != .expr_empty {
4183 return none
4184 }
4185 typ_id := out.copy_subtree_from(typ.flat, typ.id)
4186 init_id := t.transform_expr_cursor_to_flat(c.edge(1), mut out)
4187 cap_id := t.transform_expr_cursor_to_flat(c.edge(2), mut out)
4188 len_id := t.transform_expr_cursor_to_flat(c.edge(3), mut out)
4189 update_id := out.emit_expr(ast.empty_expr)
4190 mut expr_ids := []ast.FlatNodeId{cap: c.edge_count() - 5}
4191 for i in 5 .. c.edge_count() {
4192 expr_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out)
4193 }
4194 return out.emit_array_init_expr_by_ids(typ_id, init_id, cap_id, len_id, update_id, expr_ids,
4195 c.pos())
4196}
4197
4198fn (mut t Transformer) transform_explicit_dynamic_array_init_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
4199 typ := c.edge(0)
4200 if !typ.is_valid() || typ.kind() != .typ_array {
4201 return none
4202 }
4203 return t.transform_dynamic_array_init_cursor_with_elem_type_to_flat(c, typ.edge(0),
4204 ast.empty_expr, mut out)
4205}
4206
4207fn (mut t Transformer) transform_no_literal_dynamic_array_init_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
4208 if t.is_eval_backend() || t.is_native_be {
4209 return none
4210 }
4211 typ := c.edge(0)
4212 if c.edge_count() != 5 {
4213 return none
4214 }
4215 update_expr := c.edge(4)
4216 if update_expr.is_valid() && update_expr.kind() != .expr_empty {
4217 return none
4218 }
4219 mut elem_type := ast.Cursor{}
4220 mut elem_type_expr := ast.Expr(ast.empty_expr)
4221 mut elem_resolved_type := types.Type(types.void_)
4222 mut has_elem_resolved_type := false
4223 if typ.is_valid() && typ.kind() == .typ_array {
4224 elem_type = typ.edge(0)
4225 } else if !typ.is_valid() || typ.kind() == .expr_empty {
4226 array_type := t.get_expr_type_cursor(c) or { return none }
4227 array_base := t.unwrap_alias_and_pointer_type(array_type)
4228 if array_base !is types.Array {
4229 return none
4230 }
4231 array_info := array_base as types.Array
4232 elem_resolved_type = array_info.elem_type
4233 has_elem_resolved_type = true
4234 elem_type_expr = t.type_to_ast_type_expr(array_info.elem_type)
4235 } else {
4236 return none
4237 }
4238 init := c.edge(1)
4239 init_id := if !init.is_valid() || init.kind() == .expr_empty {
4240 if elem_type.is_valid() {
4241 if t.dynamic_array_empty_default_needs_legacy(elem_type) {
4242 return none
4243 }
4244 } else if !has_elem_resolved_type
4245 || t.dynamic_array_empty_default_type_needs_legacy(elem_resolved_type) {
4246 return none
4247 }
4248 out.emit_ident_by_name('nil', token.Pos{})
4249 } else {
4250 if t.cursor_subtree_has_or_expr(init) || cursor_subtree_has_ident_named(init, 'index')
4251 || t.dynamic_array_init_value_needs_legacy(init) {
4252 return none
4253 }
4254 transformed_init_id := t.transform_expr_cursor_to_flat(init, mut out)
4255 if numeric_literal_cursor(init) {
4256 typ_id := emit_map_cursor_type_id(elem_type, elem_type_expr, mut out)
4257 out.emit_cast_expr_by_ids(typ_id, transformed_init_id, token.Pos{})
4258 } else {
4259 transformed_init_id
4260 }
4261 }
4262 len_id := t.array_init_bound_cursor_to_flat(c.edge(3), mut out)
4263 cap_id := t.array_init_bound_cursor_to_flat(c.edge(2), mut out)
4264 sizeof_id :=
4265 emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(elem_type, elem_type_expr, mut out), mut out)
4266 lhs_id := out.emit_ident_by_name('__new_array_with_default_noscan', token.Pos{})
4267 return out.emit_call_expr_by_ids(lhs_id, [len_id, cap_id, sizeof_id, init_id], c.pos())
4268}
4269
4270fn (mut t Transformer) array_init_bound_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId {
4271 if !c.is_valid() || c.kind() == .expr_empty {
4272 return out.emit_basic_literal_by_value(.number, '0', token.Pos{})
4273 }
4274 return t.transform_expr_cursor_to_flat(c, mut out)
4275}
4276
4277fn (t &Transformer) dynamic_array_empty_default_needs_legacy(elem_type ast.Cursor) bool {
4278 if !elem_type.is_valid() {
4279 return true
4280 }
4281 if elem_type.kind() in [.typ_array, .typ_map] {
4282 return true
4283 }
4284 elem_name := t.type_expr_name_full_cursor(elem_type)
4285 if elem_name == '' {
4286 return true
4287 }
4288 typ := t.lookup_type(elem_name) or { return false }
4289 base := t.unwrap_alias_and_pointer_type(typ)
4290 return t.dynamic_array_empty_default_base_needs_legacy(base)
4291}
4292
4293fn (t &Transformer) dynamic_array_empty_default_type_needs_legacy(typ types.Type) bool {
4294 base := t.unwrap_alias_and_pointer_type(typ)
4295 return t.dynamic_array_empty_default_base_needs_legacy(base)
4296}
4297
4298fn (t &Transformer) dynamic_array_empty_default_base_needs_legacy(base types.Type) bool {
4299 match base {
4300 types.Array, types.Map {
4301 return true
4302 }
4303 types.Struct {
4304 for field in base.fields {
4305 if field.typ is types.Map {
4306 return true
4307 }
4308 }
4309 }
4310 else {}
4311 }
4312
4313 return false
4314}
4315
4316fn (t &Transformer) dynamic_array_init_value_needs_legacy(init ast.Cursor) bool {
4317 if !init.is_valid() {
4318 return true
4319 }
4320 if init.kind() == .expr_array_init {
4321 return true
4322 }
4323 init_type := t.get_expr_type_cursor(init) or { return false }
4324 base := t.unwrap_alias_and_pointer_type(init_type)
4325 return base is types.Array
4326}
4327
4328fn numeric_literal_cursor(c ast.Cursor) bool {
4329 if !c.is_valid() {
4330 return false
4331 }
4332 match c.kind() {
4333 .expr_basic_literal {
4334 kind := unsafe { token.Token(int(c.aux())) }
4335 return kind == .number
4336 }
4337 .expr_prefix {
4338 op := unsafe { token.Token(int(c.aux())) }
4339 return op == .minus && numeric_literal_cursor(c.edge(0))
4340 }
4341 .expr_paren {
4342 return numeric_literal_cursor(c.edge(0))
4343 }
4344 else {
4345 return false
4346 }
4347 }
4348}
4349
4350fn (mut t Transformer) transform_dynamic_array_init_cursor_with_elem_type_to_flat(c ast.Cursor, elem_type ast.Cursor, elem_type_expr ast.Expr, mut out ast.FlatBuilder) ?ast.FlatNodeId {
4351 if t.is_eval_backend() || t.is_native_be {
4352 return none
4353 }
4354 for i in 1 .. 5 {
4355 field := c.edge(i)
4356 if field.is_valid() && field.kind() != .expr_empty {
4357 return none
4358 }
4359 }
4360 exprs_len := c.edge_count() - 5
4361 if exprs_len <= 0 {
4362 return none
4363 }
4364 if !elem_type.is_valid() && elem_type_expr is ast.EmptyExpr {
4365 return none
4366 }
4367 mut expr_ids := []ast.FlatNodeId{cap: exprs_len}
4368 for i in 5 .. c.edge_count() {
4369 expr_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out)
4370 }
4371 inner_array_typ_id := out.emit_array_type_by_elem_id(emit_map_cursor_type_id(elem_type,
4372 elem_type_expr, mut out))
4373 values_id := out.emit_array_init_expr_by_ids(inner_array_typ_id, out.emit_expr(ast.empty_expr),
4374 out.emit_expr(ast.empty_expr), out.emit_expr(ast.empty_expr),
4375 out.emit_expr(ast.empty_expr), expr_ids, token.Pos{})
4376 lhs_id := out.emit_ident_by_name('builtin__new_array_from_c_array_noscan', token.Pos{})
4377 len_id := out.emit_basic_literal_by_value(.number, exprs_len.str(), token.Pos{})
4378 cap_id := out.emit_basic_literal_by_value(.number, exprs_len.str(), token.Pos{})
4379 sizeof_id :=
4380 emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(elem_type, elem_type_expr, mut out), mut out)
4381 return out.emit_call_expr_by_ids(lhs_id, [len_id, cap_id, sizeof_id, values_id], c.pos())
4382}
4383
4384fn (mut t Transformer) transform_empty_map_init_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
4385 if c.edge_count() != 1 {
4386 return none
4387 }
4388 typ := c.edge(0)
4389 if !typ.is_valid() || typ.kind() != .typ_map {
4390 return none
4391 }
4392 if t.is_eval_backend() {
4393 typ_id := out.copy_subtree_from(typ.flat, typ.id)
4394 return out.emit_map_init_expr_by_ids(typ_id, []ast.FlatNodeId{}, []ast.FlatNodeId{},
4395 c.pos())
4396 }
4397 parts := t.map_cursor_type_parts(c)
4398 hash_fn, eq_fn, clone_fn, free_fn := map_runtime_key_fns_from_type_name(parts.key_type_name)
4399 lhs_id := out.emit_ident_by_name('new_map', token.Pos{})
4400 key_sizeof_id := emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(parts.key_cursor,
4401 parts.key_expr, mut out), mut out)
4402 val_sizeof_id := emit_sizeof_type_id_to_flat(emit_map_cursor_type_id(parts.val_cursor,
4403 parts.val_expr, mut out), mut out)
4404 return out.emit_call_expr_by_ids(lhs_id, [
4405 key_sizeof_id,
4406 val_sizeof_id,
4407 emit_addr_of_ident_to_flat(hash_fn, mut out),
4408 emit_addr_of_ident_to_flat(eq_fn, mut out),
4409 emit_addr_of_ident_to_flat(clone_fn, mut out),
4410 emit_addr_of_ident_to_flat(free_fn, mut out),
4411 ], c.pos())
4412}
4413
4414fn init_expr_from_cursor(c ast.Cursor) ast.InitExpr {
4415 fields_cap := if c.edge_count() > 1 { c.edge_count() - 1 } else { 0 }
4416 mut fields := []ast.FieldInit{cap: fields_cap}
4417 for i in 1 .. c.edge_count() {
4418 field := c.edge(i)
4419 fields << ast.FieldInit{
4420 name: field.name()
4421 value: field.edge(0).expr()
4422 }
4423 }
4424 return ast.InitExpr{
4425 typ: c.edge(0).expr()
4426 fields: fields
4427 pos: c.pos()
4428 }
4429}
4430
4431fn match_branch_from_cursor(c ast.Cursor) ast.MatchBranch {
4432 conds := c.list_at(0)
4433 mut cond := []ast.Expr{cap: conds.len()}
4434 for i in 0 .. conds.len() {
4435 cond << conds.at(i).expr()
4436 }
4437 stmt_list := c.list_at(1)
4438 mut stmts := []ast.Stmt{cap: stmt_list.len()}
4439 for i in 0 .. stmt_list.len() {
4440 stmts << stmt_list.at(i).stmt()
4441 }
4442 return ast.MatchBranch{
4443 cond: cond
4444 stmts: stmts
4445 pos: c.pos()
4446 }
4447}
4448
4449fn match_expr_from_cursor(c ast.Cursor) ast.MatchExpr {
4450 mut branches := []ast.MatchBranch{cap: c.edge_count() - 1}
4451 for i in 1 .. c.edge_count() {
4452 branches << match_branch_from_cursor(c.edge(i))
4453 }
4454 return ast.MatchExpr{
4455 expr: c.edge(0).expr()
4456 branches: branches
4457 pos: c.pos()
4458 }
4459}
4460
4461fn (mut t Transformer) transform_expr_cursor_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId {
4462 if !c.is_valid() {
4463 return out.emit_expr(ast.empty_expr)
4464 }
4465 match c.kind() {
4466 .expr_empty, .expr_keyword, .expr_lifetime, .expr_range, .expr_select, .expr_tuple,
4467 .typ_anon_struct, .typ_array_fixed, .typ_array, .typ_channel, .typ_fn, .typ_generic,
4468 .typ_map, .typ_nil, .typ_none, .typ_option, .typ_pointer, .typ_result, .typ_thread,
4469 .typ_tuple {
4470 return out.copy_subtree_from(c.flat, c.id)
4471 }
4472 .expr_basic_literal {
4473 kind := unsafe { token.Token(int(c.aux())) }
4474 return out.emit_basic_literal_by_value(kind, c.name(), c.pos())
4475 }
4476 .expr_ident {
4477 return t.transform_ident_cursor_to_flat(c, mut out)
4478 }
4479 .expr_string {
4480 kind := unsafe { ast.StringLiteralKind(int(c.aux())) }
4481 return out.emit_string_literal_by_value(kind, c.name(), c.pos())
4482 }
4483 .aux_field_init {
4484 value_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4485 return out.emit_field_init_by_id(c.name(), value_id)
4486 }
4487 .expr_if_guard {
4488 assign := c.edge(0)
4489 if assign.is_valid() && assign.kind() == .stmt_assign {
4490 lhs_len := assign.extra_int()
4491 if assign.edge_count() > lhs_len {
4492 return t.transform_expr_cursor_to_flat(assign.edge(lhs_len), mut out)
4493 }
4494 }
4495 return out.copy_subtree_from(c.flat, c.id)
4496 }
4497 .expr_paren {
4498 inner_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4499 return out.emit_paren_expr_by_id(inner_id, c.pos())
4500 }
4501 .expr_modifier {
4502 kind := unsafe { token.Token(int(c.aux())) }
4503 inner_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4504 return out.emit_modifier_expr_by_id(kind, inner_id, c.pos())
4505 }
4506 .expr_prefix {
4507 op := unsafe { token.Token(int(c.aux())) }
4508 if op == .amp {
4509 if id := t.try_transform_amp_type_cast_cursor_to_flat(c.edge(0), mut out) {
4510 return id
4511 }
4512 if !cursor_unwraps_to_assoc_expr(c.edge(0)) {
4513 inner_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4514 return out.emit_prefix_expr_by_id(op, inner_id, c.pos())
4515 }
4516 } else if !(op == .arrow && c.edge(0).kind() == .expr_or) {
4517 inner_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4518 return out.emit_prefix_expr_by_id(op, inner_id, c.pos())
4519 }
4520 return t.transform_expr_to_flat(ast.Expr(ast.PrefixExpr{
4521 op: op
4522 expr: c.edge(0).expr()
4523 pos: c.pos()
4524 }), mut out)
4525 }
4526 .expr_postfix {
4527 op := unsafe { token.Token(int(c.aux())) }
4528 if op != .not && op != .question {
4529 inner_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4530 return out.emit_postfix_expr_by_id(op, inner_id, c.pos())
4531 }
4532 if id := t.transform_result_postfix_cursor_to_flat(c, op, mut out) {
4533 return id
4534 }
4535 return t.transform_expr_to_flat(ast.Expr(ast.PostfixExpr{
4536 op: op
4537 expr: c.edge(0).expr()
4538 pos: c.pos()
4539 }), mut out)
4540 }
4541 .expr_cast {
4542 sumtype_name := t.type_expr_name_full_cursor(c.edge(0))
4543 if sumtype_name != '' && t.is_sum_type(sumtype_name) {
4544 if wrapped_id := t.wrap_sumtype_value_cursor_to_flat(c.edge(1), sumtype_name, mut
4545 out)
4546 {
4547 return wrapped_id
4548 }
4549 value_expr := c.edge(1).expr()
4550 if wrapped := t.wrap_sumtype_value(value_expr, sumtype_name) {
4551 return t.emit_lowered_expr_result_to_flat(wrapped, mut out)
4552 }
4553 transformed_value := t.transform_expr(value_expr)
4554 if wrapped := t.wrap_sumtype_value_transformed(transformed_value, sumtype_name) {
4555 return t.emit_lowered_expr_result_to_flat(wrapped, mut out)
4556 }
4557 } else {
4558 typ_id := out.copy_subtree_from(c.edge(0).flat, c.edge(0).id)
4559 expr_id := t.transform_expr_cursor_to_flat(c.edge(1), mut out)
4560 return out.emit_cast_expr_by_ids(typ_id, expr_id, c.pos())
4561 }
4562 }
4563 .expr_as_cast {
4564 expr_key := expr_cursor_to_string(c.edge(0))
4565 saved_smartcast_stack := t.smartcast_stack.clone()
4566 saved_smartcast_expr_counts := t.smartcast_expr_counts.clone()
4567 for _ in 0 .. smartcast_search_limit {
4568 if _ := t.remove_smartcast_for_expr(expr_key) {
4569 continue
4570 }
4571 break
4572 }
4573 expr_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4574 t.smartcast_stack = saved_smartcast_stack.clone()
4575 t.smartcast_expr_counts = saved_smartcast_expr_counts.clone()
4576 typ_id := out.copy_subtree_from(c.edge(1).flat, c.edge(1).id)
4577 return out.emit_as_cast_expr_by_ids(expr_id, typ_id, c.pos())
4578 }
4579 .expr_sql {
4580 is_count := c.flag(ast.flag_is_count)
4581 is_create := c.flag(ast.flag_is_create)
4582 if !is_create || t.lookup_sql_table_struct(c.name()) == none {
4583 expr_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4584 return out.emit_sql_expr_by_id(c.name(), is_count, is_create, expr_id, c.pos())
4585 }
4586 result := t.transform_sql_expr(ast.SqlExpr{
4587 expr: c.edge(0).expr()
4588 table_name: c.name()
4589 is_count: is_count
4590 is_create: true
4591 pos: c.pos()
4592 })
4593 return t.emit_lowered_expr_result_to_flat(result, mut out)
4594 }
4595 .expr_assoc {
4596 t.count_flat_fallback('expr_assoc')
4597 result := t.lower_assoc_expr(assoc_expr_from_cursor(c), false)
4598 return t.emit_lowered_expr_result_to_flat(result, mut out)
4599 }
4600 .expr_or {
4601 t.count_flat_fallback('expr_or')
4602 return t.transform_expr_to_flat(ast.Expr(or_expr_from_cursor(c)), mut out)
4603 }
4604 .expr_infix {
4605 op := unsafe { token.Token(int(c.aux())) }
4606 if result_id := t.transform_enum_shorthand_compare_cursor_to_flat(c, op, mut out) {
4607 return result_id
4608 }
4609 if result_id := t.transform_sumtype_check_cursor_to_flat(c, op, mut out) {
4610 return result_id
4611 }
4612 if result_id := t.transform_string_concat_cursor_to_flat(c, op, mut out) {
4613 return result_id
4614 }
4615 if result_id := t.transform_string_compare_cursor_to_flat(c, op, mut out) {
4616 return result_id
4617 }
4618 if result_id := t.transform_range_membership_cursor_to_flat(c, op, mut out) {
4619 return result_id
4620 }
4621 if result_id := t.transform_inline_array_membership_cursor_to_flat(c, op, mut out) {
4622 return result_id
4623 }
4624 if result_id := t.transform_map_membership_cursor_to_flat(c, op, mut out) {
4625 return result_id
4626 }
4627 if op == .left_shift {
4628 if result_id := t.try_transform_array_append_cursor_to_flat(c, mut out) {
4629 return result_id
4630 }
4631 }
4632 if t.infix_cursor_can_transform_direct(c, op) {
4633 lhs_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4634 rhs_id := t.transform_expr_cursor_to_flat(c.edge(1), mut out)
4635 return out.emit_infix_expr_by_ids(op, lhs_id, rhs_id, t.infix_cursor_result_pos(c))
4636 }
4637 t.count_flat_fallback('expr_infix/op=${op}')
4638 result := t.transform_infix_expr(infix_expr_from_cursor(c))
4639 return t.emit_lowered_expr_result_to_flat(result, mut out)
4640 }
4641 .expr_call {
4642 if t.call_empty_ident_can_transform_direct(c) {
4643 lhs := c.edge(0)
4644 lhs_id := out.emit_ident_by_name(lhs.name(), lhs.pos())
4645 return out.emit_call_expr_by_ids(lhs_id, []ast.FlatNodeId{}, c.pos())
4646 }
4647 if t.call_ident_args_can_transform_direct(c) {
4648 lhs := c.edge(0)
4649 lhs_id := out.emit_ident_by_name(lhs.name(), lhs.pos())
4650 mut arg_ids := []ast.FlatNodeId{cap: c.edge_count() - 1}
4651 for i in 1 .. c.edge_count() {
4652 arg_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out)
4653 }
4654 return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos())
4655 }
4656 if call_name := t.call_selector_static_name_can_transform_direct(c) {
4657 lhs_id := out.emit_ident_by_name(call_name, c.edge(0).pos())
4658 mut arg_ids := []ast.FlatNodeId{cap: c.edge_count() - 1}
4659 for i in 1 .. c.edge_count() {
4660 arg_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out)
4661 }
4662 return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos())
4663 }
4664 if call_name := t.call_selector_module_name_can_transform_direct(c) {
4665 lhs_id := out.emit_ident_by_name(call_name, c.edge(0).pos())
4666 mut arg_ids := []ast.FlatNodeId{cap: c.edge_count() - 1}
4667 for i in 1 .. c.edge_count() {
4668 arg_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out)
4669 }
4670 return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos())
4671 }
4672 if call_name := t.call_selector_method_name_can_transform_direct(c) {
4673 lhs := c.edge(0)
4674 info := t.direct_method_call_fn_info_cursor(lhs, selector_rhs_name_cursor(lhs),
4675 call_name) or { CallFnInfo{} }
4676 lhs_id := out.emit_ident_by_name(call_name, lhs.pos())
4677 mut arg_ids := []ast.FlatNodeId{cap: c.edge_count()}
4678 arg_ids << t.transform_expr_cursor_to_flat(lhs.edge(0), mut out)
4679 for i in 1 .. c.edge_count() {
4680 if i - 1 < info.param_types.len {
4681 arg_ids << t.transform_call_arg_cursor_to_flat(c.edge(i),
4682 info.param_types[i - 1], mut out)
4683 } else {
4684 arg_ids << t.transform_expr_cursor_to_flat(c.edge(i), mut out)
4685 }
4686 }
4687 return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos())
4688 }
4689 t.count_flat_fallback(t.classify_call_fallback_cursor(c, 'expr_call'))
4690 result := t.transform_call_expr(call_expr_from_cursor(c))
4691 return t.emit_lowered_expr_result_to_flat(result, mut out)
4692 }
4693 .expr_call_or_cast {
4694 lhs := c.edge(0)
4695 arg := c.edge(1)
4696 if t.call_or_cast_empty_ident_call_can_transform_direct(c) {
4697 lhs_id := out.emit_ident_by_name(lhs.name(), lhs.pos())
4698 return out.emit_call_expr_by_ids(lhs_id, []ast.FlatNodeId{}, c.pos())
4699 }
4700 if t.call_or_cast_ident_arg_can_transform_direct(c) {
4701 lhs_id := out.emit_ident_by_name(lhs.name(), lhs.pos())
4702 arg_id := t.transform_expr_cursor_to_flat(arg, mut out)
4703 return out.emit_call_expr_by_ids(lhs_id, [arg_id], c.pos())
4704 }
4705 if call_name := t.call_or_cast_selector_static_name_can_transform_direct(c) {
4706 lhs_id := out.emit_ident_by_name(call_name, lhs.pos())
4707 mut arg_ids := []ast.FlatNodeId{cap: 1}
4708 if arg.is_valid() && arg.kind() != .expr_empty {
4709 arg_ids << t.transform_expr_cursor_to_flat(arg, mut out)
4710 }
4711 return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos())
4712 }
4713 if call_name := t.call_or_cast_selector_module_name_can_transform_direct(c) {
4714 lhs_id := out.emit_ident_by_name(call_name, lhs.pos())
4715 mut arg_ids := []ast.FlatNodeId{cap: 1}
4716 if arg.is_valid() && arg.kind() != .expr_empty {
4717 arg_ids << t.transform_expr_cursor_to_flat(arg, mut out)
4718 }
4719 return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos())
4720 }
4721 if call_name := t.call_or_cast_selector_method_name_can_transform_direct(c) {
4722 info := t.direct_method_call_fn_info_cursor(lhs, selector_rhs_name_cursor(lhs),
4723 call_name) or { CallFnInfo{} }
4724 lhs_id := out.emit_ident_by_name(call_name, lhs.pos())
4725 mut arg_ids := []ast.FlatNodeId{cap: 2}
4726 arg_ids << t.transform_expr_cursor_to_flat(lhs.edge(0), mut out)
4727 if arg.is_valid() && arg.kind() != .expr_empty {
4728 if info.param_types.len > 0 {
4729 arg_ids << t.transform_call_arg_cursor_to_flat(arg, info.param_types[0], mut out)
4730 } else {
4731 arg_ids << t.transform_expr_cursor_to_flat(arg, mut out)
4732 }
4733 }
4734 return out.emit_call_expr_by_ids(lhs_id, arg_ids, c.pos())
4735 }
4736 if arg.is_valid() && arg.kind() != .expr_empty && t.call_or_cast_lhs_is_type_cursor(lhs) {
4737 mut sumtype_name := t.type_expr_name_full_cursor(lhs)
4738 if (sumtype_name == '' || !t.is_sum_type(sumtype_name)) && c.pos().is_valid() {
4739 if expr_typ := t.get_expr_type_cursor(c) {
4740 if expr_typ is types.SumType {
4741 sumtype_name = expr_typ.get_name()
4742 }
4743 }
4744 }
4745 if sumtype_name != '' && t.is_sum_type(sumtype_name) {
4746 if wrapped_id := t.wrap_sumtype_value_cursor_to_flat(arg, sumtype_name, mut out) {
4747 return wrapped_id
4748 }
4749 arg_expr := arg.expr()
4750 if wrapped := t.wrap_sumtype_value(arg_expr, sumtype_name) {
4751 return t.emit_lowered_expr_result_to_flat(wrapped, mut out)
4752 }
4753 transformed_sum_arg := t.transform_expr(arg_expr)
4754 if wrapped := t.wrap_sumtype_value_transformed(transformed_sum_arg,
4755 sumtype_name)
4756 {
4757 return t.emit_lowered_expr_result_to_flat(wrapped, mut out)
4758 }
4759 } else {
4760 typ_id := out.copy_subtree_from(lhs.flat, lhs.id)
4761 expr_id := t.transform_expr_cursor_to_flat(arg, mut out)
4762 return out.emit_cast_expr_by_ids(typ_id, expr_id, c.pos())
4763 }
4764 }
4765 t.count_flat_fallback(t.classify_call_fallback_cursor(c, 'expr_coc'))
4766 result := t.transform_call_or_cast_expr(call_or_cast_expr_from_cursor(c))
4767 return t.emit_lowered_expr_result_to_flat(result, mut out)
4768 }
4769 .expr_map_init {
4770 if result_id := t.transform_map_init_cursor_to_flat(c, mut out) {
4771 return result_id
4772 }
4773 t.count_flat_fallback('expr_map_init')
4774 result := t.transform_map_init_expr(map_init_expr_from_cursor(c))
4775 return t.emit_lowered_expr_result_to_flat(result, mut out)
4776 }
4777 .expr_array_init {
4778 if result_id := t.transform_fixed_array_init_cursor_to_flat(c, mut out) {
4779 return result_id
4780 }
4781 if result_id := t.transform_no_literal_dynamic_array_init_cursor_to_flat(c, mut out) {
4782 return result_id
4783 }
4784 if result_id := t.transform_explicit_dynamic_array_init_cursor_to_flat(c, mut out) {
4785 return result_id
4786 }
4787 t.count_flat_fallback('expr_array_init')
4788 result := t.transform_array_init_expr(array_init_expr_from_cursor(c))
4789 return t.emit_lowered_expr_result_to_flat(result, mut out)
4790 }
4791 .expr_init {
4792 if result_id := t.transform_empty_map_init_cursor_to_flat(c, mut out) {
4793 return result_id
4794 }
4795 t.count_flat_fallback('expr_init')
4796 return t.transform_init_expr_to_flat(init_expr_from_cursor(c), mut out)
4797 }
4798 .expr_if {
4799 if t.if_expr_cursor_can_transform_plain(c) {
4800 return t.transform_plain_if_expr_cursor_to_flat(c, mut out)
4801 }
4802 t.count_flat_fallback('expr_if')
4803 result := t.transform_if_expr(if_expr_from_cursor(c))
4804 return t.emit_lowered_expr_result_to_flat(result, mut out)
4805 }
4806 .expr_match {
4807 t.count_flat_fallback('expr_match')
4808 match_expr, branches := t.transform_match_expr_parts(match_expr_from_cursor(c))
4809 return t.lower_match_expr_to_if_flat(match_expr, branches, mut out)
4810 }
4811 .expr_comptime {
4812 return t.transform_comptime_expr_cursor_to_flat(c, mut out)
4813 }
4814 .expr_keyword_operator {
4815 op := unsafe { token.Token(int(c.aux())) }
4816 if op == .key_typeof && c.edge_count() > 0 {
4817 type_name := t.resolve_typeof_expr_cursor(c.edge(0))
4818 if type_name != '' {
4819 return out.emit_string_literal_by_value(.v, quote_v_string_literal(type_name),
4820 c.pos())
4821 }
4822 }
4823 if op == .key_go && c.edge_count() > 0 {
4824 inner := c.edge(0)
4825 if inner.is_valid() {
4826 if id := t.lower_go_call_cursor_to_flat(c, inner, mut out) {
4827 return id
4828 }
4829 }
4830 }
4831 return out.copy_subtree_from(c.flat, c.id)
4832 }
4833 .expr_string_inter {
4834 return t.transform_string_inter_literal_cursor_to_flat(c, mut out)
4835 }
4836 .expr_unsafe {
4837 body := ast.CursorList{
4838 flat: c.flat
4839 parent_id: c.id
4840 }
4841 stmt_ids := t.transform_cursor_stmts_to_flat_direct(body, [], mut out)
4842 return out.emit_unsafe_expr_by_ids(stmt_ids, c.pos())
4843 }
4844 .expr_lock {
4845 stmt_ids := t.lock_expr_cursor_stmt_ids(c, true, mut out)
4846 return out.emit_unsafe_expr_by_ids(stmt_ids, token.Pos{})
4847 }
4848 .expr_fn_literal {
4849 typ := c.edge(0)
4850 typ_id := out.copy_subtree_from(typ.flat, typ.id)
4851 captured_len := c.extra_int()
4852 mut captured_ids := []ast.FlatNodeId{cap: captured_len}
4853 for i in 0 .. captured_len {
4854 captured := c.edge(1 + i)
4855 captured_ids << out.copy_subtree_from(captured.flat, captured.id)
4856 }
4857 body := ast.CursorList{
4858 flat: c.flat
4859 parent_id: c.id
4860 offset: 1 + captured_len
4861 }
4862 stmt_ids := t.transform_cursor_stmts_to_flat_direct(body, [], mut out)
4863 return out.emit_fn_literal_by_ids(typ_id, captured_ids, stmt_ids, c.pos())
4864 }
4865 .expr_generic_args {
4866 lhs := c.edge(0)
4867 if lhs.kind() == .expr_ident && lhs.name() == 'typeof' {
4868 return out.copy_subtree_from(c.flat, c.id)
4869 }
4870 if c.edge_count() == 2 {
4871 if lhs_type := t.get_expr_type_cursor(lhs) {
4872 if !t.is_callable_type(lhs_type) {
4873 arg := c.edge(1)
4874 if t.generic_index_cursor_can_transform_direct(lhs, arg) {
4875 return t.transform_generic_index_cursor_to_flat(lhs, arg, c.pos(), mut
4876 out)
4877 }
4878 return t.transform_index_expr_to_flat_parts(lhs.expr(), c.edge(1).expr(),
4879 false, c.pos(), mut out)
4880 }
4881 }
4882 }
4883 if suffix := t.generic_specialization_suffix_from_cursor_edges(c, 1) {
4884 if id := t.specialize_generic_callable_cursor_to_flat(lhs, suffix, c.pos(), mut out) {
4885 return id
4886 }
4887 }
4888 return out.copy_subtree_from(c.flat, c.id)
4889 }
4890 .expr_generic_arg_or_index {
4891 lhs := c.edge(0)
4892 arg := c.edge(1)
4893 if lhs_type := t.get_expr_type_cursor(lhs) {
4894 if t.is_callable_type(lhs_type) {
4895 if suffix := t.generic_specialization_suffix_from_cursors([arg]) {
4896 if id := t.specialize_generic_callable_cursor_to_flat(lhs, suffix, c.pos(), mut
4897 out)
4898 {
4899 return id
4900 }
4901 }
4902 return out.copy_subtree_from(c.flat, c.id)
4903 }
4904 }
4905 if t.generic_index_cursor_can_transform_direct(lhs, arg) {
4906 return t.transform_generic_index_cursor_to_flat(lhs, arg, c.pos(), mut out)
4907 }
4908 return t.transform_index_expr_to_flat_parts(lhs.expr(), arg.expr(), false, c.pos(), mut
4909 out)
4910 }
4911 .expr_selector {
4912 if result_id := t.transform_selector_cursor_special_to_flat(c, mut out) {
4913 return result_id
4914 }
4915 if t.selector_cursor_can_transform_direct(c) {
4916 lhs_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4917 rhs := c.edge(1)
4918 rhs_id := out.copy_subtree_from(rhs.flat, rhs.id)
4919 return out.emit_selector_expr_by_ids(lhs_id, rhs_id, c.pos())
4920 }
4921 t.count_flat_fallback('expr_selector')
4922 result := t.transform_selector_expr(selector_expr_from_cursor(c))
4923 return t.emit_lowered_expr_result_to_flat(result, mut out)
4924 }
4925 .expr_index {
4926 if t.index_cursor_can_transform_direct(c) {
4927 lhs_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4928 index_id := t.transform_expr_cursor_to_flat(c.edge(1), mut out)
4929 return out.emit_index_expr_by_ids(lhs_id, index_id, c.flag(ast.flag_is_gated),
4930 c.pos())
4931 }
4932 t.count_flat_fallback('expr_index')
4933 result := t.transform_index_expr(index_expr_from_cursor(c))
4934 return t.emit_lowered_expr_result_to_flat(result, mut out)
4935 }
4936 .expr_lambda {
4937 inner_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
4938 mut arg_ids := []ast.FlatNodeId{cap: c.edge_count() - 1}
4939 for i in 1 .. c.edge_count() {
4940 arg := c.edge(i)
4941 arg_ids << out.copy_subtree_from(arg.flat, arg.id)
4942 }
4943 return out.emit_lambda_expr_by_ids(inner_id, arg_ids, c.pos())
4944 }
4945 else {}
4946 }
4947
4948 return out.copy_subtree_from(c.flat, c.id)
4949}
4950
4951fn (mut t Transformer) emit_lowered_expr_result_to_flat(result ast.Expr, mut out ast.FlatBuilder) ast.FlatNodeId {
4952 if result is ast.CallExpr {
4953 lhs_id := out.emit_expr(result.lhs)
4954 mut arg_ids := []ast.FlatNodeId{cap: result.args.len}
4955 for arg in result.args {
4956 arg_ids << out.emit_expr(arg)
4957 }
4958 return out.emit_call_expr_by_ids(lhs_id, arg_ids, result.pos)
4959 }
4960 if result is ast.CastExpr {
4961 typ_id := out.emit_expr(result.typ)
4962 expr_id := out.emit_expr(result.expr)
4963 return out.emit_cast_expr_by_ids(typ_id, expr_id, result.pos)
4964 }
4965 if result is ast.BasicLiteral {
4966 return out.emit_basic_literal_by_value(result.kind, result.value, result.pos)
4967 }
4968 if result is ast.IfExpr {
4969 cond_id := out.emit_expr(result.cond)
4970 else_id := out.emit_expr(result.else_expr)
4971 mut stmt_ids := []ast.FlatNodeId{cap: result.stmts.len}
4972 for stmt in result.stmts {
4973 stmt_ids << out.emit_stmt(stmt)
4974 }
4975 return out.emit_if_expr_by_ids(cond_id, else_id, stmt_ids, result.pos)
4976 }
4977 if result is ast.Ident {
4978 return out.emit_ident_by_name(result.name, result.pos)
4979 }
4980 if result is ast.InfixExpr {
4981 lhs_id := out.emit_expr(result.lhs)
4982 rhs_id := out.emit_expr(result.rhs)
4983 return out.emit_infix_expr_by_ids(result.op, lhs_id, rhs_id, result.pos)
4984 }
4985 if result is ast.IndexExpr {
4986 lhs_id := out.emit_expr(result.lhs)
4987 expr_id := out.emit_expr(result.expr)
4988 return out.emit_index_expr_by_ids(lhs_id, expr_id, result.is_gated, result.pos)
4989 }
4990 if result is ast.SelectorExpr {
4991 lhs_id := out.emit_expr(result.lhs)
4992 rhs_id := out.emit_expr(ast.Expr(result.rhs))
4993 return out.emit_selector_expr_by_ids(lhs_id, rhs_id, result.pos)
4994 }
4995 if result is ast.StringLiteral {
4996 return out.emit_string_literal_by_value(result.kind, result.value, result.pos)
4997 }
4998 if result is ast.PrefixExpr {
4999 inner_id := out.emit_expr(result.expr)
5000 return out.emit_prefix_expr_by_id(result.op, inner_id, result.pos)
5001 }
5002 if result is ast.PostfixExpr {
5003 inner_id := out.emit_expr(result.expr)
5004 return out.emit_postfix_expr_by_id(result.op, inner_id, result.pos)
5005 }
5006 if result is ast.SqlExpr {
5007 expr_id := out.emit_expr(result.expr)
5008 return out.emit_sql_expr_by_id(result.table_name, result.is_count, result.is_create,
5009 expr_id, result.pos)
5010 }
5011 if result is ast.UnsafeExpr {
5012 mut stmt_ids := []ast.FlatNodeId{cap: result.stmts.len}
5013 for stmt in result.stmts {
5014 stmt_ids << out.emit_stmt(stmt)
5015 }
5016 return out.emit_unsafe_expr_by_ids(stmt_ids, result.pos)
5017 }
5018 if result is ast.InitExpr {
5019 typ_id := out.emit_expr(result.typ)
5020 mut field_ids := []ast.FlatNodeId{cap: result.fields.len}
5021 for field in result.fields {
5022 value_id := out.emit_expr(field.value)
5023 field_ids << out.emit_field_init_by_id(field.name, value_id)
5024 }
5025 return out.emit_init_expr_by_ids(typ_id, field_ids, result.pos)
5026 }
5027 if result is ast.MapInitExpr {
5028 typ_id := out.emit_expr(result.typ)
5029 mut key_ids := []ast.FlatNodeId{cap: result.keys.len}
5030 for key in result.keys {
5031 key_ids << out.emit_expr(key)
5032 }
5033 mut val_ids := []ast.FlatNodeId{cap: result.vals.len}
5034 for val in result.vals {
5035 val_ids << out.emit_expr(val)
5036 }
5037 return out.emit_map_init_expr_by_ids(typ_id, key_ids, val_ids, result.pos)
5038 }
5039 if result is ast.ArrayInitExpr {
5040 typ_id := out.emit_expr(result.typ)
5041 init_id := out.emit_expr(result.init)
5042 cap_id := out.emit_expr(result.cap)
5043 len_id := out.emit_expr(result.len)
5044 update_expr_id := out.emit_expr(result.update_expr)
5045 mut expr_ids := []ast.FlatNodeId{cap: result.exprs.len}
5046 for expr in result.exprs {
5047 expr_ids << out.emit_expr(expr)
5048 }
5049 return out.emit_array_init_expr_by_ids(typ_id, init_id, cap_id, len_id, update_expr_id,
5050 expr_ids, result.pos)
5051 }
5052 return out.emit_expr(result)
5053}
5054
5055fn (mut t Transformer) transform_selector_cursor_special_to_flat(c ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5056 if c.edge_count() < 2 {
5057 return none
5058 }
5059 rhs_name := selector_rhs_name_cursor(c)
5060 if rhs_name == '' {
5061 return none
5062 }
5063 lhs := c.edge(0)
5064 if rhs_name in ['name', 'idx'] && lhs.kind() == .expr_keyword_operator {
5065 op := unsafe { token.Token(int(lhs.aux())) }
5066 if op == .key_typeof && lhs.edge_count() > 0 {
5067 type_name := t.resolve_typeof_expr_cursor(lhs.edge(0))
5068 if result_id := typeof_selector_result_to_flat(type_name, rhs_name, c.pos(), mut out) {
5069 return result_id
5070 }
5071 }
5072 }
5073 if rhs_name in ['name', 'idx'] && lhs.kind() == .expr_call {
5074 type_name := t.resolve_typeof_call_lhs_type_name_cursor(lhs.edge(0))
5075 if result_id := typeof_selector_result_to_flat(type_name, rhs_name, c.pos(), mut out) {
5076 return result_id
5077 }
5078 }
5079 if rhs_name in ['name', 'idx'] && lhs.kind() == .expr_call_or_cast {
5080 arg := lhs.edge(1)
5081 if !arg.is_valid() || arg.kind() == .expr_empty {
5082 type_name := t.resolve_typeof_call_lhs_type_name_cursor(lhs.edge(0))
5083 if result_id := typeof_selector_result_to_flat(type_name, rhs_name, c.pos(), mut out) {
5084 return result_id
5085 }
5086 }
5087 }
5088 if rhs_name in ['_tag', '_data']
5089 || (rhs_name.starts_with('_') && lhs.kind() == .expr_selector
5090 && selector_rhs_name_cursor(lhs) == '_data') {
5091 return out.copy_subtree_from(c.flat, c.id)
5092 }
5093 if t.has_active_smartcast() {
5094 full_str := expr_cursor_to_string(c)
5095 if full_str != '' {
5096 if direct_ctx := t.find_smartcast_for_expr(full_str) {
5097 if result_id := t.smartcast_direct_cursor_to_flat(c, direct_ctx, mut out) {
5098 return result_id
5099 }
5100 result := t.apply_smartcast_direct_ctx(selector_expr_from_cursor(c), direct_ctx)
5101 return t.emit_lowered_expr_result_to_flat(result, mut out)
5102 }
5103 }
5104 lhs_str := expr_cursor_to_string(lhs)
5105 if lhs_str != '' {
5106 if ctx := t.find_smartcast_for_expr(lhs_str) {
5107 if result_id := t.smartcast_field_access_cursor_to_flat(lhs, rhs_name, ctx, mut out) {
5108 return result_id
5109 }
5110 result := t.apply_smartcast_field_access_ctx(lhs.expr(), rhs_name, ctx)
5111 return t.emit_lowered_expr_result_to_flat(result, mut out)
5112 }
5113 }
5114 }
5115 if lhs.kind() == .expr_ident {
5116 lhs_name := lhs.name()
5117 if lhs_name == 'os' && rhs_name == 'args' {
5118 lhs_id := out.emit_ident_by_name('arguments', c.pos())
5119 return out.emit_call_expr_by_ids(lhs_id, []ast.FlatNodeId{}, c.pos())
5120 }
5121 qualified := if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin'
5122 && !lhs_name.contains('__') {
5123 '${t.cur_module}__${lhs_name}'
5124 } else {
5125 lhs_name
5126 }
5127 if typ := t.lookup_type(qualified) {
5128 if typ is types.Enum {
5129 t.register_synth_type(c.pos(), typ)
5130 return out.emit_ident_by_name(t.enum_member_ident_for_lookup(qualified, typ,
5131 rhs_name), c.pos())
5132 }
5133 }
5134 }
5135 if lhs.kind() == .expr_selector {
5136 lhs_lhs := lhs.edge(0)
5137 if lhs_lhs.kind() == .expr_ident {
5138 module_name := lhs_lhs.name()
5139 type_name := selector_rhs_name_cursor(lhs)
5140 qualified := '${module_name}__${type_name}'
5141 if typ := t.lookup_type(qualified) {
5142 if typ is types.Enum {
5143 t.register_synth_type(c.pos(), typ)
5144 return out.emit_ident_by_name(t.enum_member_ident_for_lookup(qualified, typ,
5145 rhs_name), c.pos())
5146 }
5147 }
5148 if t.is_module_ident(module_name) {
5149 sub_mod := type_name
5150 if t.get_module_scope(sub_mod) != none {
5151 return out.emit_ident_by_name('${sub_mod}__${rhs_name}', c.pos())
5152 }
5153 full_mod := '${module_name}__${sub_mod}'
5154 if t.get_module_scope(full_mod) != none {
5155 return out.emit_ident_by_name('${full_mod}__${rhs_name}', c.pos())
5156 }
5157 }
5158 }
5159 }
5160 return none
5161}
5162
5163struct SmartcastVariantAccessNames {
5164 variant_simple string
5165 mangled_variant string
5166}
5167
5168fn smartcast_builtin_variant_name(name string) bool {
5169 return name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'byte', 'rune',
5170 'f32', 'f64', 'usize', 'isize', 'bool', 'string', 'voidptr', 'charptr', 'byteptr']
5171}
5172
5173fn smartcast_direct_data_variant_name(name string) bool {
5174 return name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64',
5175 'bool', 'rune', 'byte', 'usize', 'isize', 'voidptr', 'charptr', 'byteptr']
5176}
5177
5178fn (t &Transformer) smartcast_variant_access_names(ctx SmartcastContext) SmartcastVariantAccessNames {
5179 variant_short := ctx.variant
5180 sumtype_module := if ctx.sumtype.contains('__') {
5181 ctx.sumtype.all_before_last('__')
5182 } else {
5183 ''
5184 }
5185 variant_simple := if variant_short.starts_with('Array_') || variant_short.starts_with('Map_') {
5186 variant_short
5187 } else if variant_short.contains('__') {
5188 mod_prefix := variant_short.all_before_last('__')
5189 if mod_prefix == sumtype_module {
5190 variant_short.all_after_last('__')
5191 } else {
5192 variant_short
5193 }
5194 } else {
5195 variant_short
5196 }
5197 mangled_variant := if ctx.variant_full != '' {
5198 ctx.variant_full
5199 } else if smartcast_builtin_variant_name(variant_short) {
5200 variant_short
5201 } else if variant_short.contains('__') {
5202 variant_short
5203 } else if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin' {
5204 '${t.cur_module}__${variant_short}'
5205 } else {
5206 variant_short
5207 }
5208 return SmartcastVariantAccessNames{
5209 variant_simple: variant_simple
5210 mangled_variant: mangled_variant
5211 }
5212}
5213
5214fn (mut t Transformer) smartcast_direct_cursor_to_flat(expr ast.Cursor, ctx SmartcastContext, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5215 names := t.smartcast_variant_access_names(ctx)
5216 removed_ctxs := t.remove_matching_smartcasts(ctx)
5217 if t.has_active_smartcast() {
5218 t.restore_smartcasts(removed_ctxs)
5219 return none
5220 }
5221 transformed_base_id := t.transform_expr_cursor_to_flat(expr, mut out)
5222 t.restore_smartcasts(removed_ctxs)
5223 if ctx.sumtype.starts_with('__iface__') {
5224 object_id := t.synth_selector_cursor_to_flat(transformed_base_id, '_object',
5225 types.Type(types.voidptr_), mut out)
5226 typ_id := out.emit_ident_by_name('${names.mangled_variant}*', token.Pos{})
5227 cast_id := out.emit_cast_expr_by_ids(typ_id, object_id, token.Pos{})
5228 paren_cast_id := out.emit_paren_expr_by_id(cast_id, token.Pos{})
5229 deref_id := out.emit_prefix_expr_by_id(.mul, paren_cast_id, token.Pos{})
5230 return out.emit_paren_expr_by_id(deref_id, token.Pos{})
5231 }
5232 data_id := t.synth_selector_cursor_to_flat(transformed_base_id, '_data',
5233 types.Type(types.voidptr_), mut out)
5234 is_native_backend := t.pref != unsafe { nil } && t.is_native_be
5235 variant_id := if is_native_backend {
5236 data_id
5237 } else {
5238 t.synth_selector_cursor_to_flat(data_id, '_${names.variant_simple}',
5239 types.Type(types.voidptr_), mut out)
5240 }
5241 if t.smartcast_variant_data_is_direct(ctx)
5242 || smartcast_direct_data_variant_name(names.variant_simple) {
5243 variant_typ_id := out.emit_ident_by_name(names.mangled_variant, token.Pos{})
5244 intptr_typ_id := out.emit_ident_by_name('intptr_t', token.Pos{})
5245 intptr_cast_id := out.emit_cast_expr_by_ids(intptr_typ_id, variant_id, token.Pos{})
5246 cast_id := out.emit_cast_expr_by_ids(variant_typ_id, intptr_cast_id, token.Pos{})
5247 return out.emit_paren_expr_by_id(cast_id, token.Pos{})
5248 }
5249 typ_id := out.emit_ident_by_name('${names.mangled_variant}*', token.Pos{})
5250 cast_id := out.emit_cast_expr_by_ids(typ_id, variant_id, token.Pos{})
5251 deref_id := out.emit_prefix_expr_by_id(.mul, cast_id, token.Pos{})
5252 return out.emit_paren_expr_by_id(deref_id, token.Pos{})
5253}
5254
5255fn (mut t Transformer) synth_selector_from_struct_to_flat(lhs_id ast.FlatNodeId, field_name string, struct_name string, mut out ast.FlatBuilder) ast.FlatNodeId {
5256 pos := t.next_synth_pos()
5257 if field_typ := t.lookup_struct_field_type(struct_name, field_name) {
5258 t.register_synth_type(pos, field_typ)
5259 }
5260 rhs_id := out.emit_ident_by_name(field_name, token.Pos{})
5261 return out.emit_selector_expr_by_ids(lhs_id, rhs_id, pos)
5262}
5263
5264fn (mut t Transformer) smartcast_field_access_base_to_flat(transformed_base_id ast.FlatNodeId, field_name string, ctx SmartcastContext, names SmartcastVariantAccessNames, mut out ast.FlatBuilder) ast.FlatNodeId {
5265 if ctx.sumtype.starts_with('__iface__') {
5266 object_id := t.synth_selector_cursor_to_flat(transformed_base_id, '_object',
5267 types.Type(types.voidptr_), mut out)
5268 typ_id := out.emit_ident_by_name('${names.mangled_variant}*', token.Pos{})
5269 cast_id := out.emit_cast_expr_by_ids(typ_id, object_id, token.Pos{})
5270 paren_id := out.emit_paren_expr_by_id(cast_id, token.Pos{})
5271 return t.synth_selector_from_struct_to_flat(paren_id, field_name, names.mangled_variant, mut
5272 out)
5273 }
5274 is_native_backend := t.pref != unsafe { nil } && t.is_native_be
5275 data_id := t.synth_selector_cursor_to_flat(transformed_base_id, '_data',
5276 types.Type(types.voidptr_), mut out)
5277 variant_id := if is_native_backend {
5278 data_id
5279 } else {
5280 t.synth_selector_cursor_to_flat(data_id, '_${names.variant_simple}',
5281 types.Type(types.voidptr_), mut out)
5282 }
5283 if t.is_eval_backend() {
5284 return t.synth_selector_from_struct_to_flat(variant_id, field_name, names.mangled_variant, mut
5285 out)
5286 }
5287 typ_id := out.emit_ident_by_name('${names.mangled_variant}*', token.Pos{})
5288 cast_id := out.emit_cast_expr_by_ids(typ_id, variant_id, token.Pos{})
5289 return t.synth_selector_from_struct_to_flat(cast_id, field_name, names.mangled_variant, mut out)
5290}
5291
5292fn (mut t Transformer) smartcast_field_access_cursor_to_flat(lhs ast.Cursor, field_name string, ctx SmartcastContext, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5293 if lhs.kind() == .expr_ident {
5294 return t.smartcast_ident_field_access_cursor_to_flat(lhs, field_name, ctx, mut out)
5295 }
5296 if lhs.kind() != .expr_selector {
5297 return none
5298 }
5299 names := t.smartcast_variant_access_names(ctx)
5300 removed_ctxs := t.remove_matching_smartcasts(ctx)
5301 if t.has_active_smartcast() {
5302 t.restore_smartcasts(removed_ctxs)
5303 return none
5304 }
5305 transformed_base_id := t.transform_expr_cursor_to_flat(lhs, mut out)
5306 t.restore_smartcasts(removed_ctxs)
5307 return t.smartcast_field_access_base_to_flat(transformed_base_id, field_name, ctx, names, mut
5308 out)
5309}
5310
5311fn (mut t Transformer) smartcast_ident_field_access_cursor_to_flat(lhs ast.Cursor, field_name string, ctx SmartcastContext, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5312 if lhs.kind() != .expr_ident {
5313 return none
5314 }
5315 names := t.smartcast_variant_access_names(ctx)
5316 removed_ctxs := t.remove_matching_smartcasts(ctx)
5317 if t.find_smartcast_for_expr(lhs.name()) != none {
5318 t.restore_smartcasts(removed_ctxs)
5319 return none
5320 }
5321 transformed_base_id := t.transform_expr_cursor_to_flat(lhs, mut out)
5322 t.restore_smartcasts(removed_ctxs)
5323 return t.smartcast_field_access_base_to_flat(transformed_base_id, field_name, ctx, names, mut
5324 out)
5325}
5326
5327fn (t &Transformer) resolve_typeof_generic_arg_type_name_cursor(arg ast.Cursor) string {
5328 type_name_from_expr := t.resolve_typeof_expr_cursor(arg)
5329 if type_name_from_expr != '' {
5330 return type_name_from_expr
5331 }
5332 mut type_name := t.expr_to_type_name_cursor_direct(arg)
5333 if type_name == '' {
5334 type_name = t.type_expr_name_full_cursor(arg)
5335 }
5336 if type_name == '' {
5337 return ''
5338 }
5339 return c_name_to_v_name(type_name)
5340}
5341
5342fn (t &Transformer) resolve_typeof_call_lhs_type_name_cursor(lhs ast.Cursor) string {
5343 match lhs.kind() {
5344 .expr_generic_args {
5345 if lhs.edge_count() > 1 && lhs.edge(0).kind() == .expr_ident
5346 && lhs.edge(0).name() == 'typeof' {
5347 return t.resolve_typeof_generic_arg_type_name_cursor(lhs.edge(1))
5348 }
5349 }
5350 .expr_generic_arg_or_index {
5351 if lhs.edge(0).kind() == .expr_ident && lhs.edge(0).name() == 'typeof' {
5352 return t.resolve_typeof_generic_arg_type_name_cursor(lhs.edge(1))
5353 }
5354 }
5355 .expr_ident {
5356 return t.resolve_specialized_typeof_call_type_name(lhs.name())
5357 }
5358 else {}
5359 }
5360
5361 return ''
5362}
5363
5364fn (t &Transformer) selector_cursor_can_transform_direct(c ast.Cursor) bool {
5365 if c.edge_count() < 2 {
5366 return false
5367 }
5368 if t.has_active_smartcast()
5369 && (expr_cursor_to_string(c) == '' || expr_cursor_to_string(c.edge(0)) == '') {
5370 return false
5371 }
5372 rhs_name := selector_rhs_name_cursor(c)
5373 if rhs_name == '' {
5374 return false
5375 }
5376 if rhs_name in ['_tag', '_data'] || rhs_name.starts_with('_') {
5377 return false
5378 }
5379 lhs := c.edge(0)
5380 if rhs_name in ['name', 'idx']
5381 && lhs.kind() in [.expr_keyword_operator, .expr_call, .expr_call_or_cast] {
5382 // typeof(x).name / typeof[T]().idx lower to literals in the legacy
5383 // pipeline; plain field accesses named `name`/`idx` are ordinary.
5384 return false
5385 }
5386 if lhs.kind() == .expr_ident {
5387 lhs_name := lhs.name()
5388 if lhs_name == 'os' && rhs_name == 'args' {
5389 return false
5390 }
5391 qualified := if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin'
5392 && !lhs_name.contains('__') {
5393 '${t.cur_module}__${lhs_name}'
5394 } else {
5395 lhs_name
5396 }
5397 if typ := t.lookup_type(qualified) {
5398 if typ is types.Enum {
5399 return false
5400 }
5401 }
5402 if t.is_module_ident(lhs_name) {
5403 return false
5404 }
5405 }
5406 if lhs.kind() == .expr_selector {
5407 lhs_lhs := lhs.edge(0)
5408 if lhs_lhs.kind() == .expr_ident {
5409 module_name := lhs_lhs.name()
5410 type_name := selector_rhs_name_cursor(lhs)
5411 qualified := '${module_name}__${type_name}'
5412 if typ := t.lookup_type(qualified) {
5413 if typ is types.Enum {
5414 return false
5415 }
5416 }
5417 if t.is_module_ident(module_name) {
5418 return false
5419 }
5420 }
5421 }
5422 return true
5423}
5424
5425fn cursor_unwraps_to_assoc_expr(expr ast.Cursor) bool {
5426 mut base := expr
5427 for base.is_valid() && base.kind() in [.expr_paren, .expr_modifier] {
5428 base = base.edge(0)
5429 }
5430 return base.is_valid() && base.kind() == .expr_assoc
5431}
5432
5433fn (mut t Transformer) try_transform_amp_type_cast_cursor_to_flat(expr ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5434 match expr.kind() {
5435 .expr_index {
5436 lhs := expr.edge(0)
5437 if lhs.kind() != .expr_call_or_cast || !t.call_or_cast_lhs_is_type_cursor(lhs.edge(0)) {
5438 return none
5439 }
5440 cast_id := t.amp_type_cast_lhs_cursor_to_flat(lhs, mut out)
5441 index_id := t.transform_expr_cursor_to_flat(expr.edge(1), mut out)
5442 return out.emit_index_expr_by_ids(cast_id, index_id, expr.flag(ast.flag_is_gated),
5443 expr.pos())
5444 }
5445 .expr_selector {
5446 lhs := expr.edge(0)
5447 if lhs.kind() != .expr_call_or_cast || !t.call_or_cast_lhs_is_type_cursor(lhs.edge(0)) {
5448 return none
5449 }
5450 cast_id := t.amp_type_cast_lhs_cursor_to_flat(lhs, mut out)
5451 rhs := expr.edge(1)
5452 rhs_id := out.copy_subtree_from(rhs.flat, rhs.id)
5453 return out.emit_selector_expr_by_ids(cast_id, rhs_id, expr.pos())
5454 }
5455 else {
5456 return none
5457 }
5458 }
5459}
5460
5461fn (mut t Transformer) amp_type_cast_lhs_cursor_to_flat(call_or_cast ast.Cursor, mut out ast.FlatBuilder) ast.FlatNodeId {
5462 typ_id := out.copy_subtree_from(call_or_cast.edge(0).flat, call_or_cast.edge(0).id)
5463 ptr_typ_id := out.emit_prefix_expr_by_id(.amp, typ_id, call_or_cast.pos())
5464 expr_id := t.transform_expr_cursor_to_flat(call_or_cast.edge(1), mut out)
5465 return out.emit_cast_expr_by_ids(ptr_typ_id, expr_id, call_or_cast.pos())
5466}
5467
5468fn (t &Transformer) index_cursor_can_transform_direct(c ast.Cursor) bool {
5469 if c.edge_count() < 2 || c.edge(1).kind() == .expr_range {
5470 return false
5471 }
5472 lhs_type := t.get_expr_type_cursor(c.edge(0)) or { return false }
5473 if _ := t.unwrap_map_type(lhs_type) {
5474 return t.is_eval_backend()
5475 }
5476 return true
5477}
5478
5479fn (t &Transformer) generic_index_cursor_can_transform_direct(lhs ast.Cursor, arg ast.Cursor) bool {
5480 if !lhs.is_valid() || !arg.is_valid() || arg.kind() == .expr_range {
5481 return false
5482 }
5483 lhs_type := t.get_expr_type_cursor(lhs) or { return false }
5484 if _ := t.unwrap_map_type(lhs_type) {
5485 return t.is_eval_backend()
5486 }
5487 return true
5488}
5489
5490fn (mut t Transformer) transform_generic_index_cursor_to_flat(lhs ast.Cursor, arg ast.Cursor, pos token.Pos, mut out ast.FlatBuilder) ast.FlatNodeId {
5491 lhs_id := t.transform_expr_cursor_to_flat(lhs, mut out)
5492 arg_id := t.transform_expr_cursor_to_flat(arg, mut out)
5493 return out.emit_index_expr_by_ids(lhs_id, arg_id, false, pos)
5494}
5495
5496fn (t &Transformer) postfix_cursor_needs_checked_substr(c ast.Cursor) bool {
5497 inner := c.edge(0)
5498 return inner.kind() == .expr_index && inner.edge(1).kind() == .expr_range
5499 && t.is_string_expr_cursor(inner.edge(0))
5500}
5501
5502fn (mut t Transformer) transform_result_postfix_cursor_to_flat(c ast.Cursor, op token.Token, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5503 if c.edge_count() == 0 || c.edge(0).kind() == .expr_empty {
5504 return none
5505 }
5506 if t.postfix_cursor_needs_checked_substr(c) {
5507 return none
5508 }
5509 inner_id := t.transform_expr_cursor_to_flat(c.edge(0), mut out)
5510 is_native_backend := t.pref != unsafe { nil } && t.is_native_be
5511 if is_native_backend {
5512 return out.emit_postfix_expr_by_id(op, inner_id, c.pos())
5513 }
5514 mut type_name := ''
5515 if inner_type := t.get_expr_type_cursor(c.edge(0)) {
5516 match inner_type {
5517 types.ResultType {
5518 type_name = t.type_to_c_name(inner_type.base_type)
5519 }
5520 types.OptionType {
5521 type_name = t.type_to_c_name(inner_type.base_type)
5522 }
5523 else {}
5524 }
5525 }
5526 if type_name == '' {
5527 if typ := t.get_expr_type_cursor(c) {
5528 type_name = t.type_to_c_name(typ)
5529 }
5530 }
5531 if type_name != '' {
5532 typ_id := out.emit_ident_by_name(type_name, token.Pos{})
5533 return out.emit_cast_expr_by_ids(typ_id, inner_id, c.pos())
5534 }
5535 return inner_id
5536}
5537
5538fn (t &Transformer) type_expr_uses_current_generic_param_cursor(typ ast.Cursor) bool {
5539 if !typ.is_valid() {
5540 return false
5541 }
5542 name := t.type_expr_name_cursor(typ)
5543 if name in t.generic_var_type_params || name in t.cur_fn_generic_params {
5544 return true
5545 }
5546 for i in 0 .. typ.edge_count() {
5547 if t.type_expr_uses_current_generic_param_cursor(typ.edge(i)) {
5548 return true
5549 }
5550 }
5551 return false
5552}
5553
5554fn (t &Transformer) expr_uses_current_generic_param_cursor(expr ast.Cursor) bool {
5555 if !expr.is_valid() {
5556 return false
5557 }
5558 match expr.kind() {
5559 .expr_ident {
5560 return expr.name() in t.generic_var_type_params
5561 }
5562 .expr_index {
5563 return t.expr_uses_current_generic_param_cursor(expr.edge(0))
5564 }
5565 .expr_paren, .expr_modifier {
5566 return t.expr_uses_current_generic_param_cursor(expr.edge(0))
5567 }
5568 .expr_cast {
5569 if t.type_expr_uses_current_generic_param_cursor(expr.edge(0)) {
5570 return true
5571 }
5572 return t.expr_uses_current_generic_param_cursor(expr.edge(1))
5573 }
5574 else {
5575 return false
5576 }
5577 }
5578}
5579
5580fn sumtype_expr_needs_variant_inference_cursor(value ast.Cursor) bool {
5581 return value.kind() in [.expr_init, .expr_array_init, .expr_map_init, .expr_basic_literal,
5582 .expr_string, .expr_string_inter, .expr_cast, .expr_as_cast]
5583}
5584
5585fn (t &Transformer) expr_type_name_hint_cursor(value ast.Cursor) string {
5586 if !value.is_valid() {
5587 return ''
5588 }
5589 match value.kind() {
5590 .expr_paren, .expr_modifier {
5591 return t.expr_type_name_hint_cursor(value.edge(0))
5592 }
5593 .expr_cast {
5594 return t.type_expr_name_full_cursor(value.edge(0))
5595 }
5596 .expr_as_cast {
5597 return t.type_expr_name_full_cursor(value.edge(1))
5598 }
5599 .expr_prefix {
5600 op := unsafe { token.Token(int(value.aux())) }
5601 if op == .mul {
5602 inner_type := t.expr_type_name_hint_cursor(value.edge(0))
5603 base_name := t.pointer_type_name_base(inner_type)
5604 if base_name != '' {
5605 return base_name
5606 }
5607 }
5608 return t.expr_type_name_hint_cursor(value.edge(0))
5609 }
5610 .expr_call_or_cast {
5611 if t.call_or_cast_lhs_is_type_cursor(value.edge(0)) {
5612 return t.type_expr_name_full_cursor(value.edge(0))
5613 }
5614 }
5615 else {}
5616 }
5617
5618 return ''
5619}
5620
5621fn (t &Transformer) init_expr_sumtype_variant_name_cursor(value ast.Cursor, variants []string, sumtype_name string) string {
5622 if !value.is_valid() || value.kind() != .expr_init {
5623 return ''
5624 }
5625 init_type_name := t.type_expr_name_full_cursor(value.edge(0))
5626 if init_type_name == '' || t.is_same_sumtype_name(init_type_name, sumtype_name) {
5627 return ''
5628 }
5629 if variant_name := t.match_variant(init_type_name, variants) {
5630 return variant_name
5631 }
5632 if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin' {
5633 mangled := '${t.cur_module}__${init_type_name}'
5634 if variant_name := t.match_variant(mangled, variants) {
5635 return variant_name
5636 }
5637 }
5638 return ''
5639}
5640
5641fn (t &Transformer) sumtype_variant_name_from_cursor(value ast.Cursor, sumtype_name string, variants []string) ?string {
5642 if t.expr_uses_current_generic_param_cursor(value) {
5643 return none
5644 }
5645 mut init_variant_name := ''
5646 if value.kind() == .expr_init {
5647 init_variant_name = t.init_expr_sumtype_variant_name_cursor(value, variants, sumtype_name)
5648 }
5649 if declared_type := t.declared_expr_type_for_method_receiver_cursor(value) {
5650 declared_c_name := t.type_to_c_name(declared_type)
5651 if t.is_same_sumtype_name(declared_c_name, sumtype_name) && init_variant_name == '' {
5652 return none
5653 }
5654 }
5655 typ := t.get_expr_type_cursor(value) or { return none }
5656 c_name := t.type_to_c_name(typ)
5657 input_is_target_sumtype := t.is_same_sumtype_name(c_name, sumtype_name)
5658 if input_is_target_sumtype && init_variant_name == ''
5659 && !sumtype_expr_needs_variant_inference_cursor(value) {
5660 return none
5661 }
5662 if value.kind() == .expr_ident {
5663 if var_type := t.lookup_var_type(value.name()) {
5664 var_c_name := t.type_to_c_name(var_type)
5665 if t.is_same_sumtype_name(var_c_name, sumtype_name) {
5666 return none
5667 }
5668 }
5669 }
5670 mut variant_name := init_variant_name
5671 if c_name != '' && c_name != 'void' && !input_is_target_sumtype {
5672 variant_name = t.match_variant(c_name, variants) or { '' }
5673 }
5674 if variant_name == '' && !input_is_target_sumtype {
5675 constructor_name := t.type_constructor_name(typ)
5676 if constructor_name != '' {
5677 variant_name = t.match_variant(constructor_name, variants) or { '' }
5678 }
5679 }
5680 if variant_name == '' {
5681 match value.kind() {
5682 .expr_basic_literal {
5683 lit_kind := unsafe { token.Token(int(value.aux())) }
5684 if lit_kind == .number {
5685 variant_name = if value.name().contains('.') {
5686 match_sumtype_variant_name('f64', variants)
5687 } else {
5688 match_sumtype_variant_name('int', variants)
5689 }
5690 } else if lit_kind == .string {
5691 variant_name = match_sumtype_variant_name('string', variants)
5692 }
5693 }
5694 .expr_string, .expr_string_inter {
5695 variant_name = match_sumtype_variant_name('string', variants)
5696 }
5697 .expr_ident {
5698 var_type_name := t.get_var_type_name(value.name())
5699 if var_type_name != '' {
5700 variant_name = t.match_variant(var_type_name, variants) or { '' }
5701 }
5702 }
5703 .expr_cast {
5704 variant_name = t.match_variant(t.type_expr_name_full_cursor(value.edge(0)),
5705 variants) or { '' }
5706 }
5707 .expr_as_cast {
5708 variant_name = t.match_variant(t.type_expr_name_full_cursor(value.edge(1)),
5709 variants) or { '' }
5710 }
5711 else {}
5712 }
5713 }
5714 if variant_name == '' {
5715 hint_type := t.expr_type_name_hint_cursor(value)
5716 if hint_type != '' {
5717 variant_name = t.match_variant(hint_type, variants) or { '' }
5718 }
5719 }
5720 if variant_name == '' {
5721 return none
5722 }
5723 return variant_name
5724}
5725
5726fn sumtype_payload_field_short_variant(variant_name string) string {
5727 if variant_name.starts_with('[]') {
5728 return 'Array_${variant_name[2..]}'
5729 }
5730 if variant_name.starts_with('map[') {
5731 inner := variant_name[4..]
5732 if bracket_idx := inner.index(']') {
5733 key := inner[..bracket_idx]
5734 val := inner[bracket_idx + 1..]
5735 return 'Map_${key}_${val}'
5736 }
5737 return variant_name
5738 }
5739 if variant_name.contains('__') {
5740 return variant_name.all_after_last('__')
5741 }
5742 return variant_name
5743}
5744
5745fn (mut t Transformer) build_sumtype_init_cursor_to_flat(value ast.Cursor, transformed_value_id ast.FlatNodeId, variant_name string, sumtype_name string, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5746 variants := t.get_sum_type_variants(sumtype_name)
5747 mut tag_value := -1
5748 for i, variant in variants {
5749 if variant == variant_name {
5750 tag_value = i
5751 break
5752 }
5753 }
5754 if tag_value < 0 {
5755 return none
5756 }
5757 boxed_value_id := if t.is_eval_backend() {
5758 transformed_value_id
5759 } else if t.sumtype_variant_init_data_is_direct(variant_name) {
5760 intptr_typ_id := out.emit_ident_by_name('intptr_t', token.Pos{})
5761 intptr_cast_id :=
5762 out.emit_cast_expr_by_ids(intptr_typ_id, transformed_value_id, token.Pos{})
5763 voidptr_typ_id := out.emit_ident_by_name('voidptr', token.Pos{})
5764 out.emit_cast_expr_by_ids(voidptr_typ_id, intptr_cast_id, token.Pos{})
5765 } else {
5766 payload_type_id := if value.kind() == .expr_init {
5767 out.copy_subtree_from(value.edge(0).flat, value.edge(0).id)
5768 } else {
5769 out.emit_ident_by_name(variant_name, token.Pos{})
5770 }
5771 amp_id := out.emit_prefix_expr_by_id(.amp, transformed_value_id, token.Pos{})
5772 sizeof_id := out.emit_keyword_operator_by_ids(.key_sizeof, [payload_type_id], token.Pos{})
5773 memdup_lhs_id := out.emit_ident_by_name('memdup', token.Pos{})
5774 memdup_id := out.emit_call_expr_by_ids(memdup_lhs_id, [amp_id, sizeof_id], token.Pos{})
5775 voidptr_typ_id := out.emit_ident_by_name('voidptr', token.Pos{})
5776 out.emit_cast_expr_by_ids(voidptr_typ_id, memdup_id, token.Pos{})
5777 }
5778 typ_id := out.emit_ident_by_name(sumtype_name, token.Pos{})
5779 tag_value_id := out.emit_basic_literal_by_value(.number, tag_value.str(), token.Pos{})
5780 tag_field_id := out.emit_field_init_by_id('_tag', tag_value_id)
5781 short_variant := sumtype_payload_field_short_variant(variant_name)
5782 data_field_id := out.emit_field_init_by_id('_data._${short_variant}', boxed_value_id)
5783 return out.emit_init_expr_by_ids(typ_id, [tag_field_id, data_field_id], token.Pos{})
5784}
5785
5786fn (mut t Transformer) wrap_sumtype_value_cursor_to_flat(value ast.Cursor, sumtype_name string, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5787 variants := t.get_sum_type_variants(sumtype_name)
5788 if variants.len == 0 {
5789 return none
5790 }
5791 variant_name := t.sumtype_variant_name_from_cursor(value, sumtype_name, variants) or {
5792 return none
5793 }
5794 transformed_value_id := t.transform_expr_cursor_to_flat(value, mut out)
5795 return t.build_sumtype_init_cursor_to_flat(value, transformed_value_id, variant_name,
5796 sumtype_name, mut out)
5797}
5798
5799fn (t &Transformer) resolve_go_fn_name_cursor(lhs ast.Cursor) string {
5800 match lhs.kind() {
5801 .expr_ident {
5802 if t.cur_module != '' {
5803 return '${t.cur_module}__${lhs.name()}'
5804 }
5805 return lhs.name()
5806 }
5807 .expr_selector {
5808 base := lhs.edge(0)
5809 if base.kind() == .expr_ident {
5810 return '${base.name()}__${lhs.edge(1).name()}'
5811 }
5812 }
5813 else {}
5814 }
5815
5816 return ''
5817}
5818
5819fn (mut t Transformer) lower_go_call_cursor_to_flat(keyword ast.Cursor, call ast.Cursor, mut out ast.FlatBuilder) ?ast.FlatNodeId {
5820 mut call_lhs := ast.Cursor{}
5821 mut arg_start := 0
5822 mut arg_count := 0
5823 match call.kind() {
5824 .expr_call {
5825 call_lhs = call.edge(0)
5826 arg_start = 1
5827 arg_count = if call.edge_count() > 1 { call.edge_count() - 1 } else { 0 }
5828 }
5829 .expr_call_or_cast {
5830 call_lhs = call.edge(0)
5831 arg_start = 1
5832 arg := call.edge(1)
5833 arg_count = if arg.is_valid() && arg.kind() != .expr_empty { 1 } else { 0 }
5834 }
5835 else {
5836 return none
5837 }
5838 }
5839
5840 fn_name := t.resolve_go_fn_name_cursor(call_lhs)
5841 if fn_name == '' {
5842 return none
5843 }
5844 mut transformed_arg_ids := []ast.FlatNodeId{cap: arg_count}
5845 mut arg_type_names := []string{cap: arg_count}
5846 for i in 0 .. arg_count {
5847 arg := call.edge(arg_start + i)
5848 transformed_arg_ids << t.transform_expr_cursor_to_flat(arg, mut out)
5849 if typ := t.get_expr_type_cursor(arg) {
5850 arg_type_names << t.type_to_c_name(typ)
5851 } else {
5852 arg_type_names << 'int'
5853 }
5854 }
5855 mut is_method := false
5856 mut receiver_id := ast.FlatNodeId(-1)
5857 mut receiver_type_name := ''