Skip to content

v2.transformer #

Phased refactor: monomorphization is moving from the cleanc backend into the transformer. This file holds the foundation primitives used by the future pre-pass that will iterate env.generic_types, clone each generic FnDecl with concrete types substituted in, rename it (e.g. foo -> foo_T_int), and append it to the output stmts. Phase 1 lands these primitives without wiring; Phase 2 turns them on behind V2_TRANSFORMER_MONOMORPH=1; Phase 3 deletes cleanc's discovery/scan/active_generic_types machinery (~2500 lines).

fn decl_generic_param_names #

fn decl_generic_param_names(decl ast.FnDecl) []string

decl_generic_param_names extracts the runtime (non-@'lifetime) generic parameter names from a FnDecl in declaration order.

fn substitute_type #

fn substitute_type(typ types.Type, bindings map[string]types.Type) types.Type

substitute_type returns typ with any NamedType placeholder appearing in bindings replaced by its concrete binding, recursively.

fn ArrayMethodKind.from #

fn ArrayMethodKind.from[W](input W) !ArrayMethodKind

fn Transformer.new_with_pref #

fn Transformer.new_with_pref(env &types.Environment, p &pref.Preferences) &Transformer

struct EmbedFileHelperParts #

struct EmbedFileHelperParts {
pub:
	builtin_idx int
	extra_stmts []ast.Stmt
}

EmbedFileHelperParts is the pure-computation bundle produced by inject_embed_file_helper_parts. builtin_idx is the index of the builtin module file in the caller's []ast.File; extra_stmts is the helper-decl set (StructDecl + 5 helper FnDecls) that should be appended to that file's stmts.

struct GeneratedFnsParts #

struct GeneratedFnsParts {
pub:
	core_fns   []ast.Stmt
	module_fns map[string][]ast.Stmt
	user_fns   []ast.Stmt
}

GeneratedFnsParts is the pure-computation bundle produced by generated_fns_parts. Holds the three already-routed buckets of auto-generated helper FnDecls: core_fns for the builtin module, module_fns keyed by source module name for module-prefixed helpers (e.g. time__FormatDate__str), and user_fns for everything else (typically main / user types).

struct LiveReloadFlatParts #

struct LiveReloadFlatParts {
pub:
	file_idx int
	parts    LiveReloadParts
}

LiveReloadFlatParts couples the locator output (file_idx of the main fn's file) with the pure-computation LiveReloadParts payload, so the splice step can run without re-locating.

struct LiveReloadParts #

struct LiveReloadParts {
pub:
	source_file  string
	c_decls      []ast.Stmt
	global_decls []ast.Stmt
	preamble     []ast.Stmt
	check_stmts  []ast.Stmt
}

LiveReloadParts is the pure-computation bundle produced by live_reload_parts.

struct MainRuntimeConstInitParts #

struct MainRuntimeConstInitParts {
pub:
	main_file_idx int
	main_stmt_idx int
	main_fn_id    ast.FlatNodeId
	init_calls    []ast.Stmt
}

MainRuntimeConstInitParts carries the locator (file_idx + stmt_idx of the top-level fn main() FnDecl, plus its current FlatNodeId) and the pre-built init-call ExprStmt list that will be prepended to main's body. Returned by inject_main_runtime_const_init_parts_from_flat; consumed by inject_main_runtime_const_init_to_flat.

struct TestMainParts #

struct TestMainParts {
pub:
	test_file_idx int
	main_fn       ast.Stmt
}

inject_test_main synthesizes a main() function for test files that have test_ functions but no explicit main. The generated main calls each test function and prints progress messages. This is needed for all backends (cleanc has its own synthesis in the C emitter, but native backends rely on the transformer to provide main). TestMainParts is the pure-computation bundle produced by inject_test_main_parts. main_fn is the synthesised ast.FnDecl for main; test_file_idx is the index into files of the first file that declares a test_* function (where the synthesised main is spliced).

struct Transformer #

struct Transformer {
mut:
	pref &pref.Preferences = unsafe { nil }
	env  &types.Environment
	// Current scope for type lookups (walks up scope chain)
	scope &types.Scope = unsafe { nil }
	// Function root scope for registering transformer-created temp variables
	// This allows cleanc to look up temp variable types from the environment
	fn_root_scope &types.Scope = unsafe { nil }
	// Current module for scope lookups
	cur_module string
	// Temp variable counter for desugaring
	temp_counter int
	// Counter for synthesized positions (uses negative values to avoid collision)
	synth_pos_counter int = -1
	// Track needed auto-generated str functions (type_name -> elem_type for arrays)
	needed_str_fns map[string]string
	// Track needed auto-generated clone functions (fn_name -> struct_name)
	needed_clone_fns map[string]string
	// Track needed auto-generated array helper functions
	needed_array_contains_fns   map[string]ArrayMethodInfo
	needed_array_index_fns      map[string]ArrayMethodInfo
	needed_array_last_index_fns map[string]ArrayMethodInfo
	// Current function's return type name (for sum type wrapping in returns)
	cur_fn_ret_type_name string
	// Tracks whether the current function returns Option/Result.
	// Some transformations use this to avoid wrapping plain sumtype returns.
	cur_fn_returns_option bool
	cur_fn_returns_result bool
	// When set, match branch values should be wrapped in this sum type
	// (used when a match expression is returned from a function with sum type return)
	sumtype_return_wrap string
	// When true, the last expression in each match branch is transformed as a
	// value. This is needed while expanding `return match`, before branch-local
	// returns are inserted.
	preserve_match_branch_value bool
	// Smart cast context stack - supports nested smart casts
	smartcast_stack       []SmartcastContext
	smartcast_expr_counts map[string]int
	// Functions that should be elided (conditional compilation, e.g. @[if verbose ?])
	elided_fns map[string]bool
	// Runtime const initializers grouped by module, preserving module discovery order.
	runtime_const_inits_by_mod  map[string][]RuntimeConstInit
	runtime_const_modules       []string
	runtime_const_init_fn_name  map[string]string
	runtime_const_known         map[string]bool
	runtime_const_storage_known map[string]bool
	// Resolved replacement for compile-time pseudo variable @VMODROOT.
	comptime_vmodroot string
	// Statements generated by expression-level expansions (e.g. filter/map)
	// that must be hoisted before the current statement in transform_stmts.
	pending_stmts []ast.Stmt
	// When true, skip lowering value-position IfExpr to temp variable.
	// Set during contexts that already handle IfExpr RHS (e.g. decl_assign).
	skip_if_value_lowering bool
	// For native backends: map interface variable names to their concrete type names.
	// When we see `shape1 := Shape(rect)`, record shape1 → "Rectangle".
	// Used to rewrite interface method calls to direct concrete calls.
	interface_concrete_types map[string]string
	// Track needed auto-generated sort comparator functions
	needed_sort_fns     map[string]SortComparatorInfo
	needed_enum_str_fns map[string]types.Enum
	// Track needed go wrapper functions (go call -> goroutine_create lowering)
	needed_go_wrappers map[string]GoWrapperInfo
	// Declared storage types for local variables in the current function.
	// These stay separate from checker/current expression types, which can be
	// narrowed by smartcasts while the variable is still stored as its declared type.
	local_decl_types              map[string]types.Type
	local_fn_pointer_return_types map[string]types.Type
	// Track whether post-pass should inject the synthetic embed_file helper type.
	needed_embed_file_helper bool
	// Override array element types for variables whose checker-inferred type is wrong
	// (e.g. .map(fn_name) typed as []voidptr instead of []ReturnType)
	array_elem_type_overrides map[string]string
	// File set for resolving positions to line numbers (for assert messages)
	file_set &token.FileSet = unsafe { nil }
	// Current file and function name (for assert messages)
	cur_file_name           string
	cur_fn_name_str         string
	cur_fn_recv_prefix      string // C prefix for current method's receiver type (e.g., "ui__Window")
	cur_fn_recv_param       string // Receiver parameter name (e.g., "w")
	cur_fn_recv_is_ptr      bool   // Receiver is passed as a C pointer in the current method
	cur_fn_generic_params   []string
	generic_var_type_params map[string]string
	generic_fn_decl_names   map[string]bool
	generic_fn_decl_stmts   map[string][]ast.Stmt
	generic_fn_value_names  map[string]bool
	// @[live] hot code reloading: function names and source file
	live_fns         []LiveFn
	live_source_file string
	// Cached scope/method/fn_scope snapshots for lock-free parallel access.
	// Populated once in pre_pass from the shared Environment fields.
	cached_scopes      map[string]&types.Scope
	cached_methods     map[string][]&types.Fn
	cached_method_keys []string
	cached_fn_scopes   map[string]&types.Scope
	// Accumulated synth types for deferred application (thread-safe).
	// Instead of writing directly to env.set_expr_type during parallel transform,
	// store here and apply after merge.
	synth_types map[int]types.Type
	// Phase-1 generic monomorphization (behind V2_TRANSFORMER_MONOMORPH=1).
	// When enabled, the transformer clones generic FnDecls per
	// env.generic_types binding before code generation. cleanc consults
	// monomorphized_specs to skip duplicate weak emission of the same names.
	monomorphize_enabled bool
	monomorphized_specs  map[string]bool
	// Cached at construction: avoids per-block t.pref nil/enum re-check in
	// hot loops (transform_stmts, IfGuardExpr handling, OrExpr expansion, etc).
	is_native_be bool
}

Transformer performs AST-level transformations to simplify and normalize code before codegen. This avoids duplicating transformation logic across multiple backends (SSA, cleanc, etc.)

fn (Transformer) append_transformed_stmt_to_flat #

fn (mut t Transformer) append_transformed_stmt_to_flat(mut ids []ast.FlatNodeId, stmt ast.Stmt, mut out ast.FlatBuilder)

append_transformed_stmt_to_flat is the flat-builder mirror of append_transformed_stmt (transformer.v:3061). Runs transform_stmt_to_flat on the input stmt (direct-emitting via the dispatch seam), then drains any t.pending_stmts produced by sub-transforms (e.g. or-block side effects pushed by expand_single_or_expr) by leaf-encoding them into out BEFORE the just-emitted main stmt — matching the ordering invariant of the legacy appender ("pending stmts hoist ahead of the transformed result").

Scaffolding for the transform_stmts body driver port (arc #1). Future sessions fork the legacy transform_stmts body into a direct-emit driver that drives the per-stmt loop via this appender, replacing the inner t.append_transformed_stmt(mut result, ast.Stmt(...)) calls with their _to_flat equivalents. Used today by transform_file_index_to_flat's top-level loop (replacing the previous push/pop/restore pattern).

fn (Transformer) apply_post_pass_tail #

fn (mut t Transformer) apply_post_pass_tail(files []ast.File)

apply_post_pass_tail runs the trailing portion of legacy post_pass that operates on t.env and []ast.File rather than on a FlatBuilder:

  1. Apply accumulated synth_types to the environment (via t.env.set_expr_type).2. Push cached_fn_scopes back to t.env.fn_scopes.
  2. Run propagate_types(files) for non-arm64 backends.

These three steps are NOT part of post_pass_to_flat because they don't touch the flat output. They are factored here so any caller routing through post_pass_to_flat (s162's transform_files_to_flat_via_driver and s163's parallel counterpart) can share a single tail implementation instead of duplicating private-field access at each call site.

fn (Transformer) apply_post_pass_tail_from_flat #

fn (mut t Transformer) apply_post_pass_tail_from_flat(flat &ast.FlatAst)

apply_post_pass_tail_from_flat is the FlatAst-input counterpart to apply_post_pass_tail. Same three steps (synth_types accumulator → t.env.set_expr_type, cached_fn_scopes propagation → t.env.fn_scopes, propagate_types_from_flat(flat) for non-arm64 backends), only the third step's input changes from []ast.File to &FlatAst. Steps 1+2 don't touch the file/flat input at all — they only mutate t.env from pre-accumulated state on t. Lets _via_driver wedges drop the apply_post_pass_tail(result) call site (last []ast.File consumer in the post_pass tail) in favour of apply_post_pass_tail_from_flat(&builder.flat).

fn (Transformer) clone_expr_with_bindings #

fn (mut t Transformer) clone_expr_with_bindings(expr ast.Expr, bindings map[string]types.Type) ast.Expr

clone_expr_with_bindings deep-clones an expression, substituting generic types in embedded type positions (CastExpr targets, ArrayType elem types, etc.) and recursing through container nodes.

fn (Transformer) clone_fn_decl_with_substitutions #

fn (mut t Transformer) clone_fn_decl_with_substitutions(decl ast.FnDecl, bindings map[string]types.Type, new_name string) ast.FnDecl

clone_fn_decl_with_substitutions returns a deep clone of decl with:- name replaced by new_name

  • generic_params cleared (the clone is concrete)
  • all type expressions in params + return + body substituted via bindings
  • the body deep-cloned so the clone is structurally independent

Sitting 1 scope: covers the common subset of stmt/expr variants. Unknown variants are returned shallow-copied (no substitution recurses into them). Sitting 2 extends coverage as real generic functions exercise more nodes.

fn (Transformer) clone_stmt_with_bindings #

fn (mut t Transformer) clone_stmt_with_bindings(stmt ast.Stmt, bindings map[string]types.Type) ast.Stmt

clone_stmt_with_bindings deep-clones a stmt, substituting generic types in any embedded type expressions and propagating into nested stmts/exprs.

fn (Transformer) expand_assert_stmt_to_flat #

fn (mut t Transformer) expand_assert_stmt_to_flat(stmt ast.AssertStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder)

expand_assert_stmt_to_flat is the flat-direct mirror of expand_assert_stmt (transformer.v:13931). It builds the same ast.ExprStmt{expr: ast.IfExpr{...}} lowering but pushes the resulting transformed stmt id directly onto ids via append_transformed_stmt_to_flat — skipping the outer []ast.Stmt{cap: 1} allocation that the legacy helper returns. Body stmts and the IfExpr wrapper are still built as legacy ast structs and routed through the appender; per- helper direct-emit of the body eprintln/exit calls remains as future work.

The shape mirror is exact: same header / expr_msg, same value-print branch gating (InfixExpr with comparison op and both operands simple), same exit(1) tail. Bit-equal with the legacy expand_assert_stmt + per-stmt loop pattern; pinned by fixture_assert_stmt in the harness across all 5 invariants.

fn (Transformer) expand_lock_expr_to_flat #

fn (mut t Transformer) expand_lock_expr_to_flat(expr ast.LockExpr, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder)

expand_lock_expr_to_flat is the flat-direct mirror of expand_lock_expr (transformer.v:7920). Mirrors the same shape exactly: [sync__RwMutex_lock(&mtx)..., sync__RwMutex_rlock(&mtx)..., , sync__RwMutex_unlock(&mtx)..., sync__RwMutex_runlock(&mtx)...] On native backends (arm64/x64), the lock/unlock/rlock/runlock calls are suppressed (sync module unavailable). The body is funneled through transform_stmts_to_flat_direct so the inner []ast.Stmt allocation disappears in addition to the outer []ast.Stmt{} result array.

The lock-call CallExpr / PrefixExpr wrappers (and the unlock/rlock/runlock equivalents) are still built as legacy ast structs and routed to out.emit_stmt(...) — matching the legacy site's out.emit_stmt(exp_stmt) loop (no further transformation). Per-helper direct-emit of those wrappers remains future work. Pinned by fixture_lock_stmt across all 5 invariants.

fn (Transformer) generated_fns_parts #

fn (mut t Transformer) generated_fns_parts(files []ast.File) ?GeneratedFnsParts

generated_fns_parts is the pure-computation extraction of the "collect + route" segment previously inline in post_pass. Runs each generate_*_functions helper that has a non-empty needed-list (str, clone, sort-comparator, array-method, go-wrapper), then routes each emitted FnDecl by name: is_core_generated_fncore_fns; otherwise generated_fn_module(name, files) non-empty → module_fns[mod]; otherwise → user_fns. Returns none when no generator fires.

Does NOT mutate files or any caller state (beyond the generators' own bookkeeping on t, which is part of their existing contract). Caller decides whether to splice the buckets into a legacy []ast.File (via post_pass) or into a FlatBuilder (future post_pass_to_flat).

Fifth Phase 5 (post_pass port) _parts extraction (completes the s144 punch list: runtime-const-init-fns, runtime-const-init-main- calls, live-reload, test-main, generated-fns split). Bit-equal: identical generator invocation order, identical per-stmt routing (FnDecl name → bucket), identical bucket-append order.

fn (Transformer) generated_fns_parts_from_flat #

fn (mut t Transformer) generated_fns_parts_from_flat(flat &ast.FlatAst) ?GeneratedFnsParts

generated_fns_parts_from_flat is the FlatAst-input counterpart to generated_fns_parts. Same generators, same bucket layout, same per-stmt routing — only the two []ast.File consumers change:- generate_str_functions(files) is replaced with generate_str_functions_with_explicit(explicit_str_fns) where explicit_str_fns comes from explicit_str_method_fn_names_from_flat (s165).- generated_fn_module(name, files) is replaced with generated_fn_module_from_flat(name, flat) (s164).

Lets _via_driver wedges compute GeneratedFnsParts from &builder.flat instead of result []ast.File, so the parts computation no longer pins the un-post_pass'd legacy file array.

fn (Transformer) inject_embed_file_helper_parts #

fn (mut t Transformer) inject_embed_file_helper_parts(files []ast.File) ?EmbedFileHelperParts

inject_embed_file_helper_parts is the pure-computation extraction of the pre-splice state previously inline in inject_embed_file_helper. Locates the builtin module file (returns none if not present), scans its existing stmts for a StructDecl already named embed_file_helper_type_name (returns none if the helper is already injected — re-entry guard), and otherwise pairs the file index with a fresh embed_file_helper_stmts() payload.

Does NOT mutate files or any caller state. Caller decides whether to splice extra_stmts into a legacy []ast.File (via inject_embed_file_helper) or into a FlatBuilder (future post_pass_to_flat).

Sixth Phase 5 (post_pass port) _parts extraction (closes the "every post_pass step has a _parts handle" symmetry — sibling to s144/s145/s146/s147/s148). Bit-equal: identical builtin-file lookup+ identical re-entry guard + identical embed_file_helper_stmts() payload.

fn (Transformer) inject_embed_file_helper_parts_from_flat #

fn (mut t Transformer) inject_embed_file_helper_parts_from_flat(flat &ast.FlatAst) ?EmbedFileHelperParts

inject_embed_file_helper_parts_from_flat is the flat-cursor sibling of inject_embed_file_helper_parts. Walks flat.files for the builtin module entry and scans its stmts via cursor for an existing StructDecl named embed_file_helper_type_name (re-entry guard). Returns the same EmbedFileHelperParts shape; the builtin_idx is into flat.files (which the legacy callers' []ast.File and the FlatBuilder's flat.files share by construction).

First flat-aware port of a post_pass _parts helper (s151, opens the per-step flat-aware sweep). Pinned by flat_append_file_stmts_test.v's signature-parity invariant when wired in via inject_embed_file_helper_to_flat.

fn (Transformer) inject_embed_file_helper_to_flat #

fn (mut t Transformer) inject_embed_file_helper_to_flat(mut out ast.FlatBuilder)

inject_embed_file_helper_to_flat is the FlatBuilder-side splice counterpart to inject_embed_file_helper (legacy []ast.File mutator). Emits each parts.extra_stmts into out via out.emit_stmt, then folds the resulting stmt ids into the file root via out.append_file_stmts(builtin_idx, extra_ids) (s150's seed primitive).

Bit-equal w.r.t. signature() to running legacy inject_embed_file_helper(mut files) followed by ast.flatten_files(files): same builtin-file routing, same helper stmt payload, same final file root edges (attrs / imports unchanged, stmts list extended by the same stmt ids).

fn (Transformer) inject_generated_fns_to_flat #

fn (mut t Transformer) inject_generated_fns_to_flat(mut out ast.FlatBuilder, parts GeneratedFnsParts)

inject_generated_fns_to_flat is the FlatBuilder-side splice counterpart to the 3-way generated_fns_parts routing previously inline in post_pass. Takes a pre-computed GeneratedFnsParts (caller still runs t.generated_fns_parts(files) against legacy []ast.File because the generators themselves haven't been ported to flat yet) and folds each bucket into the appropriate FlatBuilder file root via out.append_file_stmts (s150's seed primitive):

  1. core_fns → builtin file (fallback: append to user_fns if no builtin file registered, mirroring legacy user_fns << core_fns).2. module_fns[mod] → mod file (fallback: append to user_fns if no mod file registered).3. user_fns → main file → builtin file → last file (legacy 3-tier fallback ordering preserved).

Each splice emits its FnDecls via out.emit_stmt, collects the ids, then a single out.append_file_stmts(idx, ids) call rebuilds the file root. Map iteration over module_fns relies on V's insertion-ordered DenseArray semantics so bit-equality is preserved.

Third flat-aware port (s153, completes the 3 append-only post_pass steps from s150's punch list: embed_file_helper, test_main, generated_fns). Pinned by 3-case test in inject_generated_fns_to_flat_test.v.

fn (Transformer) inject_live_reload_parts_from_flat #

fn (mut t Transformer) inject_live_reload_parts_from_flat(flat &ast.FlatAst) ?LiveReloadFlatParts

inject_live_reload_parts_from_flat locates the FIRST file in the flat AST that contains a top-level non-method fn main() AND computes the LiveReloadParts payload. Returns none when no @[live] functions exist (live_reload_parts is empty) OR no main function is present in any file.

The "first match wins" semantics of the legacy loop are preserved: inject_live_reload breaks out of its file loop on the first file it changes (the one containing main).

fn (Transformer) inject_live_reload_to_flat #

fn (mut t Transformer) inject_live_reload_to_flat(mut out ast.FlatBuilder)

inject_live_reload_to_flat is the FlatBuilder-side splice counterpart to the legacy inject_live_reload(mut []ast.File). Locates the main file via inject_live_reload_parts_from_flat, rehydrates that single file's FnDecl bodies to legacy ast.Stmt (via to_files_range) so the legacy inject_live_into_stmts helper can do the per-stmt call rewriting, then uses the FlatBuilder primitives to splice the result back:- replace_fn_body_stmts(old_fn_id, new_body_ids) for each rewritten FnDecl

  • replace_file_stmt(file_idx, stmt_idx, new_fn_id) to rewire the file
  • prepend_file_stmts(file_idx, c_decl_ids + global_decl_ids) for the file-level C-extern + GlobalDecl prepend

Hybrid hydrate approach: the per-stmt call-rewriting logic (rewrite_live_call_in_stmt_b) stays legacy — porting the recursive CallExpr/Ident/SelectorExpr walk to cursors is a much bigger lift and outside the scope of inject_live_reload's purpose (the rewriting is a stable, well-tested helper). Only the FILE-LEVEL and FN-LEVEL splice (which is what the new primitives address) runs through the flat path.

Bit-equal w.r.t. signature() to running legacy inject_live_reload(mut files) followed by ast.flatten_files(files).

fn (Transformer) inject_main_runtime_const_init_parts_from_flat #

fn (mut t Transformer) inject_main_runtime_const_init_parts_from_flat(flat &ast.FlatAst) ?MainRuntimeConstInitParts

inject_main_runtime_const_init_parts_from_flat is the flat-cursor sibling of the implicit locator in inject_main_runtime_const_init_calls. Returns the (file_idx, stmt_idx, main_fn_id, init_calls) tuple needed to drive the flat-aware splice. Returns none when no init calls are needed OR no top-level fn main() is present in any file.

The "first match wins" semantics of the legacy loop are preserved: we scan files in declaration order and stop at the first non-method fn main() we encounter.

Fourth flat-aware port (s155, follows s151/s152/s153). Pinned by the 4-case test in inject_main_runtime_const_init_to_flat_test.v.

fn (Transformer) inject_main_runtime_const_init_to_flat #

fn (mut t Transformer) inject_main_runtime_const_init_to_flat(mut out ast.FlatBuilder)

inject_main_runtime_const_init_to_flat is the FlatBuilder-side splice counterpart to the legacy inject_main_runtime_const_init_calls(mut []ast.File). Locates the fn main() via inject_main_runtime_const_init_parts_from_flat, emits each init-call ExprStmt via out.emit_stmt, prepends them to main's body via out.prepend_to_fn_body(main_fn_id, init_call_ids) (s154's seed primitive, returns a NEW fn_decl_id), then rewires the enclosing file's stmts list via out.replace_file_stmt(file_idx, stmt_idx, new_fn_id).

Bit-equal w.r.t. signature() to running legacy inject_main_runtime_const_init_calls(mut files) followed by ast.flatten_files(files): same first-main routing, same init-call payload + ordering (init_calls first, original main body after), same final file root edges (attrs / imports unchanged, stmts list one entry swapped from old fn_decl_id to new fn_decl_id).

fn (Transformer) inject_runtime_const_init_fns_to_flat #

fn (mut t Transformer) inject_runtime_const_init_fns_to_flat(mut out ast.FlatBuilder)

inject_runtime_const_init_fns_to_flat is the FlatBuilder-side splice counterpart to inject_runtime_const_init_fns (legacy []ast.File mutator). For each (mod, fn_stmt) pair produced by runtime_const_init_fn_stmts_parts, scans out.flat.files for the FIRST file whose module matches mod, emits the fn_stmt via out.emit_stmt, then folds the resulting id into the file root via out.append_file_stmts(file_idx, [stmt_id]) (s150's seed primitive).

Map iteration order: runtime_const_init_fn_stmts_parts walks t.runtime_const_modules (a slice) and inserts each entry into the returned map[string]ast.Stmt in walk order, so V's insertion-preserving map iteration produces identical (mod, fn_stmt) sequences in both the legacy and flat splice paths.

Closes the 6-step post_pass port arc: every file-mutating step in legacy post_pass now has a flat-aware _to_flat variant. Sibling to s151's inject_embed_file_helper_to_flat (same "find file by mod / append fn_stmt" shape; s151 routes through builtin_idx instead of scanning by mod string).

fn (Transformer) inject_test_main_parts #

fn (mut t Transformer) inject_test_main_parts(files []ast.File) ?TestMainParts

inject_test_main_parts is the pure-computation extraction of the pre-splice state previously inline in inject_test_main. Walks files to (a) detect an existing user main (returns none if found), (b) collect every top-level test_* fn name in declaration order, and (c) synthesise the test-runner main body — per-test (println('Running test: <name>...'), <name>(), println(' OK')) followed by a single summary println('All N tests passed.'). Returns none when no test_* fns are found.

Does NOT mutate files or any caller state. Caller decides whether to splice the synthesised main_fn into a legacy []ast.File (via inject_test_main) or into a FlatBuilder (future post_pass_to_flat).

Fourth Phase 5 (post_pass port) _parts extraction (follows s144's runtime_const_init_fn_stmts_parts, s145's runtime_const_init_main_calls_parts, s146's live_reload_parts). Bit-equal: identical traversal order, identical quote_v_string_literal payloads, identical FnDecl shape (only name + stmts populated).

fn (Transformer) inject_test_main_parts_from_flat #

fn (mut t Transformer) inject_test_main_parts_from_flat(flat &ast.FlatAst) ?TestMainParts

inject_test_main_parts_from_flat is the flat-cursor sibling of inject_test_main_parts. Walks flat.files via FileCursor for (a) an existing top-level user main FnDecl (returns none if found) and (b) every top-level test_* FnDecl name in declaration order. Both scans use .stmt_fn_decl kind + flag_is_method filter + name() / .starts_with('test_'). Returns the same TestMainParts shape as the legacy _parts; reuses synthesise_test_main_fn for the body, so bit-equality is by-construction (same payload, same FnDecl shape).

Second flat-aware port (s152, follows s151's inject_embed_file_helper_parts_from_flat). Pinned by 3-case test in inject_test_main_to_flat_test.v.

fn (Transformer) inject_test_main_to_flat #

fn (mut t Transformer) inject_test_main_to_flat(mut out ast.FlatBuilder)

inject_test_main_to_flat is the FlatBuilder-side splice counterpart to the legacy inject_test_main(mut []ast.File) mutator. Emits the synthesised main FnDecl into out via out.emit_stmt, then folds the resulting stmt id into the test-file root via out.append_file_stmts(test_file_idx, [main_id]) (s150's seed primitive).

Bit-equal w.r.t. signature() to running legacy inject_test_main(mut files) followed by ast.flatten_files(files): same test-file routing, same synthesised main payload, same final file root edges (attrs / imports unchanged, stmts list extended by the same single stmt id).

fn (Transformer) live_reload_parts #

fn (mut t Transformer) live_reload_parts() ?LiveReloadParts

live_reload_parts is the pure-computation extraction of the pre-splice state previously inline in inject_live_reload. Returns the resolved source-file path plus the four stmt slices (C extern decls, GlobalDecls, main() preamble, and per-for-body reload-check stmts) that the caller splices into the file containing main(). Returns none when there are no @[live] functions (the legacy early-return).

Does NOT mutate files or any caller state. Caller decides whether to splice the result into a legacy []ast.File (via inject_live_reload) or into a FlatBuilder (future post_pass_to_flat).

Third Phase 5 (post_pass port) _parts extraction (follows s144's runtime_const_init_fn_stmts_parts and s145's runtime_const_init_main_calls_parts). Bit-equal: identical os.real_path resolution + identical builder-helper outputs.

fn (Transformer) merge_worker #

fn (mut t Transformer) merge_worker(w &Transformer)

merge_worker merges accumulated state from a worker transformer into this one.

fn (Transformer) monomorphize_pass #

fn (mut t Transformer) monomorphize_pass(files []ast.File) []ast.File

monomorphize_pass walks env.generic_types, clones each generic FnDecl per binding map with concrete types substituted, and appends the clones to the owning file's stmts. Cleanc consults monomorphized_specs to avoid double- emitting the same names through its weak-spec path.

Behind V2_TRANSFORMER_MONOMORPH=1. Returns a new []ast.File where each file containing a generic FnDecl has the clones appended.

fn (Transformer) new_worker_clone #

fn (t &Transformer) new_worker_clone(worker_idx int) &Transformer

new_worker_clone creates a lightweight Transformer that shares read-only state (env, pref, elided_fns, comptime_vmodroot, file_set) but has its own accumulator maps for thread-safe per-file transformation. worker_idx offsets synth_pos_counter so workers don't generate conflicting IDs.

fn (Transformer) post_pass #

fn (mut t Transformer) post_pass(mut result []ast.File)

post_pass runs the sequential post-pass: injects runtime const init fns, generated functions, test main, live reload, and propagates types.

fn (Transformer) post_pass_to_flat #

fn (mut t Transformer) post_pass_to_flat(mut out ast.FlatBuilder, generated_parts ?GeneratedFnsParts)

post_pass_to_flat is the FlatBuilder-side driver counterpart to post_pass. Runs the six file-mutating steps that legacy post_pass performs, in identical order, on a FlatBuilder instead of a legacy []ast.File. Closes the post_pass port arc opened in s150 and completed in s151/s152/s153/s155/s159/s160.

Order mirrors legacy post_pass:1. inject_runtime_const_init_fns_to_flat (skip on eval backend)2. inject_embed_file_helper_to_flat (gated by needed_embed_file_helper)3. inject_generated_fns_to_flat (gated by generated_parts != none)4. inject_test_main_to_flat (skip on cleanc backend)5. inject_main_runtime_const_init_to_flat (skip on eval backend)6. inject_live_reload_to_flat (only on native backend)

generated_parts is computed by the caller via t.generated_fns_parts against whatever file source it has (legacy []ast.File today). Once generated_fns_parts_from_flat lands, callers can drop the legacy file list entirely.

The non-file post_pass tail (synth_types accumulator, fn_scopes propagation, propagate_types) is NOT included here — it operates on t.env and legacy []ast.File respectively, not on out, so callers keep running it after this driver returns. propagate_types in particular still consumes []ast.File and is already gated to skip on the arm64 backend in legacy post_pass.

fn (Transformer) pre_pass #

fn (mut t Transformer) pre_pass(files []ast.File)

pre_pass runs the sequential pre-pass: builds elided_fns and collects runtime const inits.

fn (Transformer) pre_pass_from_flat #

fn (mut t Transformer) pre_pass_from_flat(flat &ast.FlatAst)

pre_pass_from_flat is the FlatAst-driven equivalent of pre_pass. Reads file-level stmts via flat.read_file_stmts(ff) so the transformer no longer depends on a pre-rehydrated []ast.File for its accumulators. The per-stmt walks reuse the existing recursive visitors, which still operate on legacy ast.Stmt/Expr — flat-native walks are a later step.

fn (Transformer) runtime_const_init_fn_stmts_parts #

fn (mut t Transformer) runtime_const_init_fn_stmts_parts() map[string]ast.Stmt

runtime_const_init_fn_stmts_parts is the pure-computation extraction of inject_runtime_const_init_fns's per-module work. Returns a (module name, fn_stmt) map for each module that has runtime const inits, after running the per-module side effects (runtime_const_init_fn_name[mod] = ...+ order_runtime_const_inits). Does NOT mutate any []ast.File — the caller decides whether to splice each (mod, fn_stmt) pair into a legacy []ast.File (via inject_runtime_const_init_fns) or into a FlatBuilder (future post_pass_to_flat).

First Phase 5 (post_pass port) scaffolding — establishes the "_parts" extraction pattern (precedent: transform_fn_decl_parts in session 4, transform_assign_stmt_parts in session 59). Future post_pass-port sessions extract _parts from inject_test_main, inject_embed_file_helper, inject_main_runtime_const_init_calls, inject_live_reload, and generate_*_functions to compose a FlatBuilder-aware post_pass_to_flat.

fn (Transformer) runtime_const_init_main_calls_parts #

fn (mut t Transformer) runtime_const_init_main_calls_parts(files []ast.File) []ast.Stmt

runtime_const_init_main_calls_parts is the pure-computation extraction of the per-main init-call list previously inline in inject_main_runtime_const_init_calls. Returns the ordered list of __v_init_consts_<mod>() + <mod>__init() call ExprStmts that should be prepended to main(): first the runtime-const init calls (one per t.runtime_const_modules entry that has a registered fn_name; gated by uses_minimal_windows_x64_runtime + imported_runtime_init_modules on minimal-windows-x64), then the module-level init() calls (one per module seen in files that declares a non-method init fn; gated by the same minimal-windows filter; de-duped per module).

Does NOT mutate files or any caller state. Caller decides whether to splice the result into a legacy []ast.File (via inject_main_runtime_const_init_calls) or into a FlatBuilder (future post_pass_to_flat).

Second Phase 5 (post_pass port) _parts extraction (follows s144's runtime_const_init_fn_stmts_parts). Bit-equal: same iteration order over runtime_const_modules + files, same de-dup map semantics.

fn (Transformer) runtime_const_init_main_calls_parts_from_flat #

fn (mut t Transformer) runtime_const_init_main_calls_parts_from_flat(flat &ast.FlatAst) []ast.Stmt

runtime_const_init_main_calls_parts_from_flat is the flat-cursor sibling of runtime_const_init_main_calls_parts. Builds the same ordered list of __v_init_consts_<mod>() + <mod>__init() ExprStmt calls, but reads the per-file init fn presence from flat.files via FileCursor (kind == .stmt_fn_decl, !flag(ast.flag_is_method), name() == 'init') instead of walking []ast.File. The runtime-const portion is unchanged — it iterates t.runtime_const_modules + t.runtime_const_init_fn_name (Transformer state, not file content).

Pure: does not mutate the flat AST or transformer state.

fn (Transformer) set_file_set #

fn (mut t Transformer) set_file_set(fs &token.FileSet)

fn (Transformer) set_synth_pos_counter #

fn (mut t Transformer) set_synth_pos_counter(val int)

fn (Transformer) specialized_fn_name #

fn (t &Transformer) specialized_fn_name(decl ast.FnDecl, bindings map[string]types.Type) string

specialized_fn_name returns the C-style specialized function name for a given generic FnDecl plus a concrete type bindings map, e.g. foo + {T:int} -> foo_T_int. Mirrors cleanc's specialized_fn_name so a future swap is name-compatible.

fn (Transformer) substitute_type_in_expr #

fn (mut t Transformer) substitute_type_in_expr(expr ast.Expr, bindings map[string]types.Type) ast.Expr

substitute_type_in_expr rewrites an ast.Expr that names a type by replacing placeholder Idents (T, U, ...) with the concrete type expression from bindings. Type-bearing nodes (ArrayType, MapType, PointerType, OptionType, ResultType, GenericType, FnType) recurse into their child type exprs. Non-type-bearing nodes are returned unchanged.

The receiver is mut because new synth positions are allocated for cloned nodes (so they do not collide with the originals' positions).

fn (Transformer) substitute_type_in_type_node #

fn (mut t Transformer) substitute_type_in_type_node(typ ast.Type, bindings map[string]types.Type) ast.Type

substitute_type_in_type_node handles the inner sum type variants of ast.Type. Split out because ast.Type is a nested sum type within ast.Expr; matching on ast.Expr only yields the ast.Type wrapper, never its inner variants directly.

fn (Transformer) track_interface_assign_to_flat #

fn (mut t Transformer) track_interface_assign_to_flat(assign_stmt ast.AssignStmt)

try_expand_or_expr_return_to_flat is the flat-direct mirror of try_expand_or_expr_return (transformer.v:6440). Thin-dispatcher port with last-stmt-interleave drain shape: legacy try_expand_or_expr_return returns [prefix_stmts..., final_return_stmt] and may have left pending_stmts populated from the final transform_return_stmt(...) call. Those pending stmts must be emitted BETWEEN the prefix stmts and the terminating return — emitting them after would put non-return stmts after the return; emitting them before would precede the prefix _or_tN := ... they depend on.

Site shape preserved exactly: when pending_stmts.len > 0, drain happens right before emitting the last expanded stmt; otherwise plain in-order emit. Output stmts are already transformed (legacy helper internally calls extract_or_expr + transform_return_stmt), so out.emit_stmt(...) is used (not the appender). Returns bool to signal whether the expansion fired.

Memory win: outer []ast.Stmt still allocated by legacy helper; site-level consolidation removes the unrolled if/else + for blocks (now one bool branch). Full direct-emit (eliminating the legacy outer alloc and the inner prefix_stmts accumulator) requires porting extract_or_expr + transform_return_stmt to flat-direct — deferred.

Bit-equal. Pinned by fixture_or_return across all 5 invariants. track_interface_assign_to_flat lifts the inline native-backend interface-concrete-type tracker (previously ~9 lines at flat_write.v:1800-1808) into a named side-effect helper. The original arm has no continue — it mutates t.interface_concrete_types based on the rhs shape for ALL 1:1 lhs/rhs assigns under the native backend, then falls through to the next arm. Guard chain: native-be + 1:1 lhs/rhs + non-empty lhs name. Behavior:- rhs assigns an interface-castable concrete type → record it.

  • else if op is plain = or := → clear any stale concrete-type mapping (so a re-assign with a non-concrete rhs doesn't leave stale dispatch info). No return value (side-effect only).

Memory win: zero. Value: completes driver-body uniformity for the interface family — both the tracker (this helper) and the emitter (s132's try_expand_interface_cast_assign_to_flat) live in named helpers; the driver's AssignStmt arm body is now five lines of dispatchers + one tracker call.

Bit-equal. Native-backend-only; production coverage via cmd/v2/v2 rebuild.

fn (Transformer) transform_expr_to_flat #

fn (mut t Transformer) transform_expr_to_flat(expr ast.Expr, mut out ast.FlatBuilder) ast.FlatNodeId

transform_expr_to_flat is the per-expr dispatch for phase 4 of the port. Leaf-arm expr variants (BasicLiteral, EmptyExpr, Keyword, LifetimeExpr, RangeExpr, SelectExpr, StringLiteral, Tuple, Type — all identity in transform_expr's else { expr } case) are direct-emitted into out, skipping the legacy transform_expr dispatch. Non-leaf variants take the legacy round-trip out.emit_expr(t.transform_expr(expr)). Phase 4 sessions replace one non-leaf arm at a time with direct-emit logic that bypasses the legacy ast construction at the rewrite site itself.

The 5th harness row pins bit-equality against the reference rehydrate+ transform+append loop; any per-arm divergence trips it.

fn (Transformer) transform_file_index_to_flat #

fn (mut t Transformer) transform_file_index_to_flat(input_flat &ast.FlatAst, fi int, mut out ast.FlatBuilder) ast.FlatNodeId

transform_file_index_to_flat is the per-file entry point for the multi- session port. It rehydrates a single file from input_flat, mirrors transform_file's prologue, and emits each top-level stmt through the transform_stmt_to_flat seam. Returns the FlatNodeId of the appended file root, or ast.invalid_flat_node_id for an empty / missing source file.

As of phase 3 the loop bypasses transform_stmts at the file level — top-level stmt variants don't trigger any of transform_stmts' multi-stmt expansions (AssignStmt / ExprStmt / ForStmt / AssertStmt expansions all live in function bodies, not at file scope) so per-stmt transform via the seam is equivalent. A defensive t.pending_stmts drain catches any leak from constructs that have not been audited yet.

Callers must invoke pre_pass_from_flat(input_flat) before the per-file loop and post_pass(mut collected_files) after, mirroring the wedge. The per-file API does not run those passes itself so future phases can interleave file emissions with pre/post bookkeeping without re-running it.

fn (Transformer) transform_file_pub #

fn (mut t Transformer) transform_file_pub(file ast.File) ast.File

transform_file_standalone transforms a single file, for use in parallel workers.

fn (Transformer) transform_files #

fn (mut t Transformer) transform_files(files []ast.File) []ast.File

transform_files transforms all files and returns transformed copies

fn (Transformer) transform_files_from_flat #

fn (mut t Transformer) transform_files_from_flat(flat &ast.FlatAst, files []ast.File) []ast.File

transform_files_from_flat drives the same per-file transform pipeline but seeds the pre-pass accumulators from FlatAst rather than from the legacy ast.File list.

When files is empty and monomorphization is disabled, the per-file transform streams: each file is rehydrated from flat one at a time just before transform_file consumes it, so the 120 MB bulk legacy array never exists. Peak memory during transform drops to (cumulative result + 1 in-flight source file) instead of (full source array + full result array).

Monomorphization needs the full file set up front (it builds a cross-file generic-decl index), so that path falls back to the non-streaming behavior — rehydrating internally when files is empty.

fn (Transformer) transform_files_to_flat #

fn (mut t Transformer) transform_files_to_flat(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File)

transform_files_to_flat is the transformer's flat-output entry point.

Today it wraps transform_files_from_flat + ast.flatten_files: the internal pipeline still produces []ast.File and the conversion happens at the boundary. Externally this exposes the API shape the future "transformer writes directly into a FlatBuilder" port will keep — so callers can switch to this entry now and get the eventual peak-memory win without further changes.

Callers that only need flat output (currently: the V2_MARKUSED_FLAT path in the builder, which re-flattens b.files itself today) should route through here. The returned []ast.File is kept alive only for the downstream consumers that still need legacy (SSA builder). Once the SSA builder consumes flat as well, this entry point will drop the legacy []ast.File entirely and the peak-memory win materializes.

fn (Transformer) transform_files_to_flat_via_driver #

fn (mut t Transformer) transform_files_to_flat_via_driver(flat &ast.FlatAst, files []ast.File) (ast.FlatAst, []ast.File)

transform_files_to_flat_via_driver is the post_pass_to_flat-based counterpart to transform_files_to_flat. Same external shape (returns (ast.FlatAst, []ast.File)) but uses the s161 driver instead of the legacy post_pass + flatten_files boundary:

  1. Per-file transform via transform_files_from_flat_no_post_pass (skips legacy post_pass).2. Compute generated_fns_parts against the un-post_pass'd result (the parts helper walks []ast.File to determine module routing for generated fns; the file set is the same as legacy at this point).3. Flatten the un-post_pass'd files into a fresh FlatBuilder.
  2. Run post_pass_to_flat(mut builder, generated_parts) — the s161 driver appends/prepends/replaces stmts on the flat directly.5. Apply the non-file post_pass tail (synth_types accumulator, fn_scopes propagation, propagate_types) which mutates t.env and result but not the flat output.

Bit-equivalent to legacy in TREE STRUCTURE (each file's stmts list holds the same content) but NOT bit-equal in signature(): the .file.extra slot stores intern(mod) as a raw intern index, and the two paths intern strings in different orders (legacy interns post-pass-added strings DURING flatten_files walk, leaving later files' mod strings at large indices; new path interns all bare files first then post-pass-added strings AFTER, leaving later files' mod strings at small indices). Compare via per-file subtree_signature of the stmts list (file root edge 2) to assert structural parity.

Memory: zero saving over transform_files_to_flat while the SSA builder still consumes the returned []ast.File (the alloc lives until SSA migration). Value: once SSA migrates to flat, this entry can drop the []ast.File return and the post-transform []ast.File allocation disappears — first measurable peak-memory win. Until then this is the migration scaffolding, pinned by per-file subtree parity tests.

fn (Transformer) transform_stmt_to_flat #

fn (mut t Transformer) transform_stmt_to_flat(stmt ast.Stmt, mut out ast.FlatBuilder) ast.FlatNodeId

transform_stmt_to_flat is the per-stmt dispatch for phase 3 of the port. Leaf-arm variants (those that fall into transform_stmt's else { stmt } case, i.e. identity in the legacy transform) are direct-emitted into out, skipping the transform_stmt dispatch and return-value construction. Non-leaf variants still take the legacy round-trip: out.emit_stmt(t.transform_stmt(stmt)). Phase 4 sessions replace one non-leaf arm at a time with direct flat-emit logic that bypasses the legacy ast construction at the rewrite site itself.

The harness 5th row pins bit-equality of this seam against a reference rehydrate+transform+append loop; any per-arm divergence trips it.

fn (Transformer) transform_stmts_to_flat #

fn (mut t Transformer) transform_stmts_to_flat(stmts []ast.Stmt, mut out ast.FlatBuilder) []ast.FlatNodeId

transform_stmts_to_flat is the consolidated seam for "transform a body stmt list, then encode each result into the flat builder" — the pattern repeated at every flat-write arm that wraps a body (BlockStmt, DeferStmt, ComptimeStmt $for body, UnsafeExpr body, FnLiteral body). Currently a literal pass-through over transform_stmts + leaf-encode loop, kept as a single function so future sessions can replace the body with direct-emit ports for transform_stmts's expansion sites (comptime $if assign, or- block assign, tuple if-assign, lock/rlock, for-in-map, assert, ...) in one place. The FnDecl arm has its own seam (transform_fn_decl_parts_to_flat) because the FnDecl body driver also runs lower_defer_stmts between transform_stmts and the flat encoding — porting that needs a separate thrust.

fn (Transformer) transform_stmts_to_flat_direct #

fn (mut t Transformer) transform_stmts_to_flat_direct(stmts []ast.Stmt, mut out ast.FlatBuilder) []ast.FlatNodeId

transform_stmts_to_flat_direct is the flat-direct mirror of transform_stmts (transformer.v:3072). Drives the per-stmt loop emitting FlatNodeIds straight into out instead of materialising a []ast.Stmt intermediate. Mirrors every expansion site (comptime $if assign, native interface cast/sincos, or-block assign, tuple if-assign, tuple call-assign, if-guard assign, if-expr assign, $if expr-stmt, or-expr stmt, if-guard stmt, flag-enum set/clear, return-match, or-return, return-if, lock/rlock, map-index push/postfix, native selector postfix, for-in-map, assert) and the default fall-through via append_transformed_stmt_to_flat.

Stmts produced by expansion helpers (legacy try_expand_* / expand_* helpers that still return ast.Stmt / []ast.Stmt) are leaf- encoded via out.emit_stmt(...) — these helpers transform the contained exprs internally, so leaf-encoding is bit-equal to the legacy result << expanded_X push. The transform_stmt(stmt) dispatch on the default fall-through routes through transform_stmt_to_flat's direct-emit arms via the appender, which skips the legacy stmt-wrapper allocation for the many already-ported variants.

Saves the outer []ast.Stmt allocation (one per call) and the stmt- wrapper allocations on the default path. Expansion sites still materialise their inner intermediates — those need per-helper _to_flat ports in future sessions to fully drop the legacy materialisation.

fn (Transformer) try_emit_flag_enum_set_clear_to_flat #

fn (mut t Transformer) try_emit_flag_enum_set_clear_to_flat(stmt ast.ExprStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_emit_flag_enum_set_clear_to_flat is the flat-direct mirror of the flag-enum set/clear single-emit arm in transform_stmts_to_flat_direct (flat_write.v:1836). Thin-dispatcher port — delegates to the legacy try_transform_flag_enum_set_clear Optional-returning rewriter, then leaf-encodes the result. Returns bool to signal whether the rewrite fired (driver continues on true).

Output stmt is already a transformed ast.AssignStmt (a |= / &= ~ flag-enum lowering), so out.emit_stmt(...) is used (no re-transform).

Memory win: zero. Value: driver-body uniformity — every arm body in transform_stmts_to_flat_direct is now either a one-line bool-dispatcher if t.X_to_flat(...) { continue } or a one-line void-helper call. The 3-line "lift result + emit + continue" inline form is gone from the driver.

Bit-equal. Production coverage via cmd/v2/v2 rebuild (no fixture; the flag-enum set/clear rewrite path triggers on flag.set(.x) / flag.clear(.x) expressions which production source uses but synthetic fixtures don't).

fn (Transformer) try_emit_map_index_postfix_to_flat #

fn (mut t Transformer) try_emit_map_index_postfix_to_flat(stmt ast.ExprStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_emit_map_index_postfix_to_flat is the flat-direct mirror of the map-index-postfix single-emit arm in transform_stmts_to_flat_direct (flat_write.v:1860). Thin-dispatcher port (s134 wrap pattern, third of four). Delegates to the legacy try_transform_map_index_postfix Optional-returning rewriter (lowers m[k]++ / m[k]-- to a fetch-mutate-set pattern that handles the unique map[idx] semantics — indexing returns a value, not an lvalue, so postfix needs explicit get+ arith + set). Returns bool.

Memory win: zero. Value: continued driver-body uniformity.

fn (Transformer) try_emit_map_index_push_to_flat #

fn (mut t Transformer) try_emit_map_index_push_to_flat(stmt ast.ExprStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_emit_map_index_push_to_flat is the flat-direct mirror of the map-index-push single-emit arm in transform_stmts_to_flat_direct (flat_write.v:1856). Thin-dispatcher port (s134 wrap pattern). Delegates to the legacy try_transform_map_index_push Optional-returning rewriter (lowers m[k] << v to a map__set / push-noscan call), then leaf-encodes the result. Returns bool to signal whether the rewrite fired.

Drops the redundant ast.Stmt(...) cast at the legacy site — the helper's return type is already ?ast.Stmt, so the cast was a no-op (likely an artifact of an earlier signature change). Bit-equal.

Memory win: zero. Value: continued driver-body uniformity (s134 pattern, second of four single-emit arm wraps).

fn (Transformer) try_emit_selector_postfix_to_flat #

fn (mut t Transformer) try_emit_selector_postfix_to_flat(stmt ast.ExprStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_emit_selector_postfix_to_flat is the flat-direct mirror of the native-backend selector-postfix single-emit arm in transform_stmts_to_flat_direct (flat_write.v:1862). Thin-dispatcher port (s134 wrap pattern, fourth/last of the series). Lowers obj.field++ / obj.field-- to a synthesised assign (obj.field = obj.field + 1) for native backends, which lack the in-place postfix codegen path. Absorbs the if t.is_native_be {} guard inside the helper — the driver site shrinks from 6 lines (incl. guard block) to 3 lines.

Returns bool. Returns false when native-be is off (no rewrite needed — C backend handles postfix on selectors natively) or when the legacy helper rejects the shape.

Memory win: zero. Value: completes the s134-s137 single-emit arm wrap series — every arm body in transform_stmts_to_flat_direct is now either a one-line bool-dispatcher (if t.X_to_flat(...) { continue }) or a one-line void-helper call. No inline if t.X {} guard blocks remain at the site level; backend-gating lives inside the dispatchers.

fn (Transformer) try_expand_comptime_if_assign_to_flat #

fn (mut t Transformer) try_expand_comptime_if_assign_to_flat(assign_stmt ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_comptime_if_assign_to_flat lifts the inline comptime-$if-assign rewrite (previously ~40 lines at flat_write.v:1795-1832) into a named helper. Like s129's sincos extraction, this site had no legacy try_expand_* helper — the entire rewrite logic was inline. The helper absorbs the AssignStmt+single-rhs+ComptimeExpr+IfExpr guard chain, the comptime-cond evaluability probe (can_eval_comptime_cond), the resolve_comptime_if_stmts lookup, and the last-stmt dispatch (ExprStmt vs AssignStmt vs neither). For the false-path (cond not evaluable at compile time) the original assign_stmt is emitted as-is. For the true- path: all-but-last selected stmts are routed through the appender, and the last stmt's rhs (ExprStmt.expr transformed via transform_expr, or AssignStmt.rhs untransformed) is grafted onto a fresh AssignStmt mirroring the original lhs/op/pos.

Returns bool to signal whether the rewrite fired. Returns true even on the unevaluable-cond branch (since that branch consumes the stmt via emit-as-is + continue at the site).

Memory win: zero — same number of synth AssignStmts emitted as before. The value is driver-body uniformity (continuation of s129's milestone): the largest remaining inline construction block in transform_stmts_to_flat_direct collapses into one bool branch, making the driver loop fully uniform.

Bit-equal. Exercised through the cmd/v2/v2 rebuild (comptime $if-assign is common in builtin/os/etc. for backend-conditional consts and inits); no harness fixture targets it directly since the rewrite logic depends on backend gates not exercised by the fixture set.

fn (Transformer) try_expand_comptime_if_stmt_to_flat #

fn (mut t Transformer) try_expand_comptime_if_stmt_to_flat(stmt ast.ExprStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_comptime_if_stmt_to_flat lifts the inline comptime-$if-stmt rewrite (previously ~20 lines at flat_write.v:1846-1865) into a named helper. Sibling of s130's try_expand_comptime_if_assign_to_flat for the ExprStmt+ComptimeExpr+IfExpr case (not AssignStmt). Same comptime- cond probe but different handling:- evaluable cond → recurse the selected stmts through transform_stmts_to_flat_direct and splice the resulting FlatNodeIds directly into ids (no synth AssignStmt wrapping).- non-evaluable cond → emit a synth ExprStmt wrapping a fresh ComptimeExpr around transform_comptime_if_bodies(if_expr) so the backend sees both branches with already-transformed bodies.

Returns bool to signal whether the rewrite fired. Returns true on both branches.

Memory win: zero — same emission shape. Value: completes driver-body uniformity for the comptime $if family.

Bit-equal. Exercised through the cmd/v2/v2 rebuild.

fn (Transformer) try_expand_for_in_map_to_flat #

fn (mut t Transformer) try_expand_for_in_map_to_flat(stmt ast.ForStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_for_in_map_to_flat is the flat-direct mirror of try_expand_for_in_map (for.v:183). Body-mirror port (s143, follows the s142 if-guard body-mirror; completes the punch list of "heavy remaining helpers" called out in s141). Replicates the legacy helper's body inline and emits each top-level produced stmt directly via the appender (the synth stmts inside still need a transform pass to handle smartcast/scope and pick up nested expansions — exactly like the s127 thin-dispatcher version, but without the outer slice).

Sequence:1. Skip on eval backend; bail if init is not ForInStmt; bail if iter type is not a map (after pointer/alias unwrap).2. Resolve key/value var names (Ident or ModifierExpr.expr).3. lvalue vs rvalue iter expr: lvalue uses iter directly; rvalue emits a map_tmp := <iter> decl (also registers temp var with iter_type).4. Emit mut _map_len := map_ref.key_values.len decl.5. Build the body loop_body []ast.Stmt (still legacy-struct since it's the ForStmt.stmts field): _map_delta decl, _map_len update, negative-delta guard, has_index guard, optional key binding + string clone, optional value binding, then the original body stmts.6. Emit the synth for _map_idx := 0; _map_idx < _map_len; _map_idx++ { <loop_body> }.

Memory win: outer 1-3 stmt accumulator slice eliminated per fired site. The inner loop_body slice (~8-12 stmts) is the ForStmt's stmts field and must stay legacy-shape until ForStmt itself is ported to flat-direct (multi-session future work). Side effects (register_temp_var, scope.insert) fire in the same order as legacy.

Bit-equal. The full for-in-map body is exercised through fixture_for_in_map (registered in s128) across all 5 invariants.

fn (Transformer) try_expand_if_expr_assign_stmts_to_flat #

fn (mut t Transformer) try_expand_if_expr_assign_stmts_to_flat(stmt ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_if_expr_assign_stmts_to_flat is the flat-direct mirror of try_expand_if_expr_assign_stmts (if.v:759). Mirrors the same guard chain (op == .assign, single lhs, single rhs that is an IfExpr with non-empty else branch) and delegates the recursive expansion to the legacy expand_assign_if_expr helper (which always returns a 1-stmt array [ExprStmt(IfExpr{...})]). The single returned stmt is pushed via append_transformed_stmt_to_flat directly, skipping the outer []ast.Stmt allocation in the legacy helper. Returns bool to signal whether the expansion fired.

Memory win: outer 1-element []ast.Stmt allocation from expand_assign_if_expr eliminated per call. The recursive helper still builds intermediate []ast.Stmt for then/else branches and nested else-if chains — those allocations remain future work. Pinned by fixture_if_expr_assign across all 5 invariants (covers plain x = if c { a } else { b } and chained x = if c1 { a } else if c2 { b } else { c }).

fn (Transformer) try_expand_if_guard_assign_stmts_to_flat #

fn (mut t Transformer) try_expand_if_guard_assign_stmts_to_flat(stmt ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_if_guard_assign_stmts_to_flat is the flat-direct mirror of try_expand_if_guard_assign_stmts (if.v:46). Mirrors the same guard chain (single decl assign with single IfExpr-with-IfGuardExpr rhs, non-empty guard.stmt.lhs and rhs, guard rhs not Result/Option/or-wrapped) and the same two output branches:- map branch: x := if key in map { guard_var := map[key]; <orig stmts> } else { default } (1 stmt total)- non-map branch: r := expr; x := if r { <orig stmts> } else { default } (2 stmts total) Side effects preserved exactly: one next_synth_pos() call, one register_synth_type(synth_pos, orig_type) per branch (only when get_expr_type finds a type), same make_infix_expr_at call for the map-in cond. Returns bool to signal whether the expansion fired.

Memory win: outer []ast.Stmt{} (non-map branch) + outer [ast.Stmt(...)] 1-element array (map branch) eliminated per call. Inner new_then_stmts allocation (map branch) and the AssignStmt/IfExpr/Ident wrappers are still built as legacy ast structs and routed through the appender — full elimination of those remains future work. Pinned by fixture_if_guard_assign across all 5 invariants (covers both map and non-map branches).

fn (Transformer) try_expand_if_guard_stmt_to_flat #

fn (mut t Transformer) try_expand_if_guard_stmt_to_flat(stmt ast.ExprStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_if_guard_stmt_to_flat is the flat-direct mirror of try_expand_if_guard_stmt (if.v:199). Body-mirror port (s142, follows the s138-s141 body-mirror sweep). Replicates the legacy helper's ~280- line body inline and emits each produced stmt directly into ids via out.emit_stmt(...) (outputs are already transformed). Four output branches preserved verbatim:1. Result/Option guard rhs: emit guard_prefix_stmts → drain pending_stmts (from transform_expr(rhs)) → tmp decl assign → terminating ExprStmt(modified_if).2. Map IndexExpr guard rhs: emit prefix_stmts (built up via addr_of_with_prefix_temp side effects) → temp_assign → terminating ExprStmt(modified_if).3. Array IndexExpr guard rhs: single terminating ExprStmt(modified_if) with bounds-check cond.4. Default: bounds/cond check; for map-returns-array, emit temp_assign then terminating ExprStmt(modified_if); otherwise single ExprStmt(modified_if).

Side effects (state save/restore for skip_if_value_lowering, the register_synth_type / register_temp_var calls, and the register_if_guard_lhs_payload_type calls in branch 1) preserved verbatim — the legacy defer-based restore of skip_if_value_lowering is replicated inline here, scoped to the helper's lifetime.

Memory win: outer []ast.Stmt accumulator (stmts in branch 1, prefix_stmts in branch 2, the trivial 1-element wrap in branch 3, and the 1-or-2-element wrap in branch 4) eliminated per fired site. Inner accumulators (if_stmts, new_stmts) that feed transform_stmts(...) to produce the IfExpr's inner stmts field remain — those are part of the IfExpr legacy struct shape and require porting IfExpr itself to flat-direct to eliminate. Real meaningful savings since the s138-s141 body-mirror sweep continued.

Bit-equal. Pinned by fixture_if_guard (Option-form). Result, map, and array branches exercised by cmd/v2/v2 self-host paths.

fn (Transformer) try_expand_interface_cast_assign_to_flat #

fn (mut t Transformer) try_expand_interface_cast_assign_to_flat(assign_stmt ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_interface_cast_assign_to_flat lifts the inline native-backend interface-cast assign rewrite (previously ~15 lines at flat_write.v:1810-1825) into a named helper. The site recognizes iface = ConcreteType(value) on the native backend (is_native_be), records the concrete type in t.interface_concrete_types[lhs_name] for later method dispatch, and emits a stripped synth AssignStmt where the rhs is the inner expression (cast peeled off — native backends don't box). Guard chain: native-be + 1:1 lhs/rhs + lhs ident + rhs interface cast. Returns bool.

Memory win: zero — same synth AssignStmt emitted. Value: driver-body uniformity continuation — another multi-line construction block lifted out of the driver loop.

Bit-equal. Exercised through the cmd/v2/v2 native-backend rebuild (interface assignments appear when implementing interface types like Walker/Drawer in the v2 self-host); no harness fixture targets it directly since the rewrite is native-backend-only.

fn (Transformer) try_expand_or_expr_assign_stmts_to_flat #

fn (mut t Transformer) try_expand_or_expr_assign_stmts_to_flat(stmt &ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_or_expr_assign_stmts_to_flat is the flat-direct mirror of try_expand_or_expr_assign_stmts (transformer.v:4375). Body-mirror port (s138 pattern, second of the body-mirror sweep). Replicates the legacy helper's body inline and emits each produced stmt directly into the FlatBuilder, skipping the outer []ast.Stmt return slice.

Shape:- Guard via stmt.rhs.len == 1 (legacy returns none otherwise).

  • If RHS is directly an OrExpr → delegate to expand_direct_or_expr_assign (still returns legacy []ast.Stmt; direct-emit via out.emit_stmt per item). Bit-equal.- Otherwise check expr_has_or_expr on RHS; bail if none.
  • extract_or_expr builds prefix_stmts via side effects; bail on empty (no or-expr extracted).- Optional EmptyExpr rewrite via or_payload_expr_from_prefix_stmts (recovers payload expression when the or-block has no value).- transform_expr(new_rhs) (no save/restore of pending_stmts here — the legacy helper doesn't either; pending_stmts produced inside transform_expr remain in t.pending_stmts and are drained at the site as the outer pending_stmts drain).- Tuple destructuring branch (a, b := call()?): build temp ident + decl_assign + per-arg selector assigns, all direct-emit.- Otherwise: build final AssignStmt via transform_stmt (so map index lowering / string compound assignment fire), direct-emit prefix_stmts then the final transformed assign.

Memory win: one []ast.Stmt outer slice per fired site (plus the tuple-destructuring branch saves a second allocation since prefix_stmts is no longer returned). Sets up the body-mirror pattern for the remaining or-block thin-dispatcher (try_expand_or_expr_return_to_flat in s140).

Bit-equal. Pinned by fixture_or_assign across all 5 invariants.

fn (Transformer) try_expand_or_expr_return_to_flat #

fn (mut t Transformer) try_expand_or_expr_return_to_flat(stmt ast.ReturnStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_or_expr_return_to_flat is the flat-direct mirror of try_expand_or_expr_return (transformer.v:6440). Body-mirror port (s138 pattern, third of the body-mirror sweep — completes the or-block trio). Replicates the legacy helper's body inline: scan return exprs for any OrExpr, extract via extract_or_expr (which pushes into prefix_stmts as a side effect), and emit prefix_stmts followed by the transformed final return stmt.

Shape:- Scan stmt.exprs for any expr_has_or_expr; return false if none.

  • For each expr: if it's an OrExpr returning none AND the function returns option, rewrite to return none-bodied OrExpr (matches legacy return_expr ternary at transformer.v:6456).- Call extract_or_expr per expr to build prefix_stmts and new_exprs. Bail if prefix_stmts is empty.- Build final ReturnStmt{exprs: new_exprs} and pass through transform_return_stmt (which may populate t.pending_stmts with _if_tN temps or similar).- Emit prefix_stmts directly, then drain t.pending_stmts (last-stmt-interleave: pending stmts go BETWEEN prefix and the terminating return — pending stmts depend on the prefix _or_tN decls, and the return must remain last in the block). Finally emit the transformed return stmt.

Memory win: outer []ast.Stmt return slice from legacy helper eliminated; new_exprs slice (sized to stmt.exprs.len) still allocated since transform_return_stmt needs the full list. Completes the or-block body-mirror trio (s138-s140).

Bit-equal. Pinned by fixture_or_return across all 5 invariants.

fn (Transformer) try_expand_or_expr_stmt_to_flat #

fn (mut t Transformer) try_expand_or_expr_stmt_to_flat(stmt ast.ExprStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_or_expr_stmt_to_flat is the flat-direct mirror of try_expand_or_expr_stmt (transformer.v:6273). Body-mirror port (s138 pattern, first of the body-mirror sweep): replicates the legacy helper's body inline and emits each produced stmt directly into the FlatBuilder, skipping the outer []ast.Stmt return slice that the legacy helper allocates. The inner extract_or_expr / transform_or_call_expr / transform_expr calls still produce legacy ast.Stmt / ast.Expr values — porting those to flat-direct is multi-session future work — but the outer return slice and the inner_pending merge loop are eliminated.

Shape:- Guard via expr_has_or_expr; return false if no or-expr/propagation.

  • If expr is a top-level OrExpr, try the "direct" fast path first (try_expand_direct_or_expr_stmt still returns a legacy slice; emit each item via out.emit_stmt). Bit-equal to the legacy if direct_stmts := ... branch.- Otherwise build prefix_stmts via extract_or_expr (side-effect pushes), bail if empty, then transform the resulting expression under saved-pending-stmts + saved-skip-if discipline (matches the legacy helper exactly — pending stmts produced by transform_expr must hoist AFTER the or-expr prefix stmts, not before).- Emit prefix_stmts → inner_pending → terminating ExprStmt directly into ids/out, with the outer site-level pending_stmts drain applied first (legacy site behavior).

Memory win: one []ast.Stmt outer slice per fired site (small but real). Sets up the body-mirror pattern for the remaining or-block thin-dispatchers (try_expand_or_expr_assign_stmts_to_flat, try_expand_or_expr_return_to_flat) in subsequent sessions.

Bit-equal. Pinned by fixture_or_stmt across all 5 invariants.

fn (Transformer) try_expand_return_if_expr_to_flat #

fn (mut t Transformer) try_expand_return_if_expr_to_flat(stmt ast.ReturnStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_return_if_expr_to_flat is the flat-direct mirror of try_expand_return_if_expr (if.v:587). Mirrors the same guard chain (single-expr ReturnStmt with IfExpr-with-non-empty-else expr) and delegates the recursive body expansion to the legacy expand_return_if_expr helper (which always returns a 1-stmt array wrapping the transformed if cond { return a } else { return b }). The single returned stmt is pushed via append_transformed_stmt_to_flat directly, skipping the outer []ast.Stmt allocation in the legacy helper. Returns bool to signal whether the expansion fired.

Memory win: outer 1-element []ast.Stmt allocation from expand_return_if_expr_with_options eliminated per call (one per matched return if c { a } else { b } site). The recursive helper still builds intermediate []ast.Stmt for then/else branches and nested else-if chains — those allocations remain future work. Pinned by fixture_return_if_expr across all 5 invariants (covers plain return if c { a } else { b } and chained return if c1 { a } else if c2 { b } else { c }).

fn (Transformer) try_expand_return_match_expr_to_flat #

fn (mut t Transformer) try_expand_return_match_expr_to_flat(stmt ast.ReturnStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_return_match_expr_to_flat is the flat-direct mirror of try_expand_return_match_expr (if.v:608). Body-mirror port (s141, body-mirror sweep continuation). Replicates the legacy helper's body inline: single-expr ReturnStmt + MatchExpr guard chain, sumtype-return- wrap state save/restore, transform_match_expr lowering to IfExpr, then expand_return_match_if_expr to build the stmt list.

Memory win: zero direct slice savings — expand_return_match_if_expr still allocates the result slice and the dispatcher iterates it. Body-mirror benefit is architectural: eliminates the legacy try_expand_return_match_expr function frame; centralises state management (sumtype_return_wrap, preserve_match_branch_value) at the flat-direct dispatch site; makes the dispatch surface uniform with the s138-s140 body-mirror trio. Real allocation reduction requires porting expand_return_match_if_expr / expand_return_if_expr_with_options to direct-emit — multi-session future work.

Bit-equal. Pinned by fixture_return_match across all 5 invariants.

fn (Transformer) try_expand_sincos_assign_to_flat #

fn (mut t Transformer) try_expand_sincos_assign_to_flat(assign_stmt ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_sincos_assign_to_flat lifts the inline sincos rewrite (previously ~40 lines at flat_write.v:1860) into a named helper. Unlike the s121-s128 thin dispatchers, this site had no legacy try_expand_* helper — the entire rewrite logic was inline. The new helper absorbs the is_native_be + lhs.len >= 2 + rhs.len == 1 guards and the try_extract_sincos_arg probe (returns false for any failure), then emits the two synth AssignStmts (sin + cos) via the s114 appender, honoring the _ blank-binding contract on either lhs slot.

Returns bool to signal whether the rewrite fired. Output stmts route through the appender because the synth ast.AssignStmts contain untransformed sub-exprs (the original lhs idents + freshly-built CallExpr around sincos_arg) that need a transform pass.

Memory win: zero — same number of synth AssignStmts emitted as before. The value is driver-body uniformity: every if ... { ... continue } arm in transform_stmts_to_flat_direct is now either a _to_flat helper call or a single-emit leaf, no more multi-line inline construction blocks embedded in the driver. Improves diffability of the driver and prevents future per-helper ports from accidentally diverging from this site's rewrite logic.

Bit-equal. Exercised through the cmd/v2/v2 rebuild (native backend emits sincos-fused trig for math fixtures); no harness fixture targets it directly since the rewrite only fires on the native-backend gate.

fn (Transformer) try_expand_tuple_call_assign_to_flat #

fn (mut t Transformer) try_expand_tuple_call_assign_to_flat(stmt ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_tuple_call_assign_to_flat is the flat-direct mirror of try_expand_tuple_call_assign (transformer.v:3489). Mirrors the same guard chain (tuple lhs with n>=2 + single non-IfExpr rhs that is a CallExpr or a PostfixExpr-of-Call with .not/.question op). On match, bumps temp_counter exactly once (matching the legacy ordering) and emits, in order, the _tuple_tN := <call> decl plus n per-slot lhs[i] = _tuple_tN.argI SelectorExpr assignments directly via append_transformed_stmt_to_flat — skipping the outer []ast.Stmt{cap: n + 1} allocation that the legacy helper returns. Returns true if the expansion was performed (caller should continue); false otherwise.

Bit-equal with the legacy try_expand_tuple_call_assign(...) + for-loop append_transformed_stmt_to_flat pattern at the site (must preserve the single temp_counter++ so _tuple_tN names stay aligned). Pinned by fixture_tuple_call_assign across all 5 invariants.

fn (Transformer) try_expand_tuple_if_assign_stmts_to_flat #

fn (mut t Transformer) try_expand_tuple_if_assign_stmts_to_flat(stmt ast.AssignStmt, mut ids []ast.FlatNodeId, mut out ast.FlatBuilder) bool

try_expand_tuple_if_assign_stmts_to_flat is the flat-direct mirror of try_expand_tuple_if_assign_stmts (if.v:888). Validates the same guard chain (decl_assign with tuple lhs + single IfExpr rhs + non-empty tuple + well-formed then/else branches). On match, mirrors the legacy result by pushing one decl-assign per tuple slot followed by the wrapping ExprStmt(IfExpr(...)) directly via append_transformed_stmt_to_flat — skipping the outer []ast.Stmt{cap: n + 1} allocation that the legacy helper returns. Returns true if the expansion was performed (caller should continue); false otherwise.

Bit-equal with the legacy try_expand_tuple_if_assign_stmts(...) + for-loop append_transformed_stmt_to_flat pattern at the site. Inner helpers (build_tuple_branch_assigns) still build their own []ast.Stmt for then/else branches; per-helper direct-emit of those branches remains future work. Pinned by fixture_tuple_if_assign across all 5 invariants.

fn (Transformer) type_to_ast_expr #

fn (mut t Transformer) type_to_ast_expr(typ types.Type, pos token.Pos) ast.Expr

type_to_ast_expr converts a types.Type back into an ast.Expr suitable for use in type positions (params, return types, casts). Returns a placeholder Ident with the type's name for primitives, plus structured nodes for compound types.