// Copyright (c) 2026 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module transformer import v2.ast import v2.pref import v2.token import v2.types fn (mut t Transformer) register_if_guard_lhs_payload_type(lhs []ast.Expr, payload_type types.Type) { for expr in lhs { match expr { ast.Ident { if expr.name != '_' { t.remember_local_decl_type(expr.name, payload_type) t.register_local_var_type(expr.name, payload_type) if expr.pos.is_valid() { t.register_synth_type(expr.pos, payload_type) } } } ast.ModifierExpr { if expr.expr is ast.Ident { ident := expr.expr as ast.Ident if ident.name != '_' { t.remember_local_decl_type(ident.name, payload_type) t.register_local_var_type(ident.name, payload_type) if ident.pos.is_valid() { t.register_synth_type(ident.pos, payload_type) } } } } else {} } } } // try_expand_if_guard_assign_stmts expands an if-guard assignment to multiple statements. // Transforms: x := if r := map[key] { r } else { default } // Into (for maps): // x := if key in map { map[key] } else { default } // Into (for other cases): // r := expr // x := if r { r } else { default } fn (mut t Transformer) try_expand_if_guard_assign_stmts(stmt ast.AssignStmt) ?[]ast.Stmt { // Check for single assignment with IfExpr RHS if stmt.rhs.len != 1 || stmt.lhs.len != 1 { return none } rhs_expr := stmt.rhs[0] // Check if RHS is an IfExpr with IfGuardExpr condition if rhs_expr !is ast.IfExpr { return none } if_expr := rhs_expr as ast.IfExpr if if_expr.cond !is ast.IfGuardExpr { return none } guard := if_expr.cond as ast.IfGuardExpr // Extract guard variable name from LHS of the guard assignment mut guard_var_name := '' for lhs_expr in guard.stmt.lhs { if lhs_expr is ast.Ident { guard_var_name = lhs_expr.name break } else if lhs_expr is ast.ModifierExpr { if lhs_expr.expr is ast.Ident { guard_var_name = lhs_expr.expr.name break } } } if guard_var_name == '' || guard.stmt.rhs.len == 0 { return none } guard_rhs := guard.stmt.rhs[0] if t.expr_wrapper_type_for_or(guard_rhs) != none || t.expr_returns_result(guard_rhs) || t.expr_returns_option(guard_rhs) { return none } synth_pos := t.next_synth_pos() // Check if RHS is a map index expression - use "key in map" condition if guard_rhs is ast.IndexExpr { if _ := t.get_map_type_for_expr(guard_rhs.lhs) { // This is a map access - transform using "key in map" check // x := if key in map { map[key] } else { default } key_in_map := t.make_infix_expr_at(.key_in, guard_rhs.expr, guard_rhs.lhs, guard_rhs.pos) // Build new stmts for the then-branch: guard_var := map[key]; mut new_then_stmts := []ast.Stmt{cap: if_expr.stmts.len + 1} new_then_stmts << ast.AssignStmt{ op: .decl_assign lhs: guard.stmt.lhs rhs: guard.stmt.rhs pos: guard.stmt.pos } for s in if_expr.stmts { new_then_stmts << s } modified_if := ast.IfExpr{ cond: key_in_map stmts: new_then_stmts else_expr: if_expr.else_expr pos: synth_pos } // Propagate the original IfExpr type to the synthesized node if orig_type := t.get_expr_type(ast.Expr(if_expr)) { t.register_synth_type(synth_pos, orig_type) } return [ ast.Stmt(ast.AssignStmt{ op: stmt.op lhs: stmt.lhs rhs: [ast.Expr(modified_if)] pos: stmt.pos }), ] } } // Non-map case: use original approach mut stmts := []ast.Stmt{} // 1. Guard variable declaration: r := expr stmts << ast.AssignStmt{ op: .decl_assign lhs: guard.stmt.lhs rhs: guard.stmt.rhs pos: guard.stmt.pos } // 2. Modified if expression with guard variable as condition // x := if r { r } else { default } // Use synthesized position to avoid inheriting wrong type from original IfGuardExpr guard_ident := ast.Ident{ name: guard_var_name pos: synth_pos } modified_if := ast.IfExpr{ cond: guard_ident stmts: if_expr.stmts else_expr: if_expr.else_expr pos: synth_pos } // Propagate the original IfExpr type to the synthesized node if orig_type := t.get_expr_type(ast.Expr(if_expr)) { t.register_synth_type(synth_pos, orig_type) } stmts << ast.AssignStmt{ op: stmt.op lhs: stmt.lhs rhs: [ast.Expr(modified_if)] pos: stmt.pos } return stmts } // try_expand_if_guard_stmt expands a statement-level if-guard. // Transforms: if attr := table[name] { use(attr) } // Into: if (table[name]) { attr := table[name]; use(attr) } // For Result types: if attr := fn_call() { use(attr) } // Into: { _tmp := fn_call(); if (!_tmp.is_error) { attr := *(_tmp.data); use(attr) } else { else_body } } fn (mut t Transformer) if_guard_else_expr_with_err(else_expr ast.Expr, temp_ident ast.Ident) ast.Expr { if else_expr is ast.IfExpr { else_if := else_expr as ast.IfExpr if else_if.cond is ast.EmptyExpr { mut else_stmts := []ast.Stmt{} else_stmts << ast.AssignStmt{ op: .decl_assign lhs: [ast.Expr(ast.Ident{ name: 'err' })] rhs: [ t.synth_selector(temp_ident, 'err', types.Type(types.Struct{ name: 'IError' })), ] } else_stmts << else_if.stmts return ast.IfExpr{ cond: else_if.cond stmts: t.transform_stmts(else_stmts) else_expr: t.transform_expr(else_if.else_expr) pos: else_if.pos } } } return t.transform_expr(else_expr) } fn (mut t Transformer) try_expand_if_guard_stmt(stmt ast.ExprStmt) ?[]ast.Stmt { // Check if this is an IfExpr with IfGuardExpr condition if stmt.expr !is ast.IfExpr { return none } if_expr := stmt.expr as ast.IfExpr if if_expr.cond !is ast.IfGuardExpr { return none } guard := if_expr.cond as ast.IfGuardExpr // The entire if-guard expansion is at statement position. Suppress value- // position IfExpr hoisting (`_if_t := if ...`) for nested else-if chains // so they don't generate mistyped temps for what is really a statement. saved_skip := t.skip_if_value_lowering t.skip_if_value_lowering = true defer { t.skip_if_value_lowering = saved_skip } if guard.stmt.rhs.len == 0 { return none } mut rhs := guard.stmt.rhs[0] mut guard_prefix_stmts := []ast.Stmt{} if t.expr_has_or_expr(rhs) { rhs = t.extract_or_expr(rhs, mut guard_prefix_stmts) } synth_pos := t.next_synth_pos() // Check if RHS is a call that returns Result/Option // First try expression-based lookup (works for both function and method calls) mut is_result := t.expr_returns_result(rhs) mut is_option := t.expr_returns_option(rhs) // Fallback to function name lookup for simple function calls if !is_result && !is_option { fn_name := t.get_call_fn_name(rhs) is_result = fn_name != '' && t.fn_returns_result(fn_name) is_option = fn_name != '' && t.fn_returns_option(fn_name) } if !is_result && !is_option { if ret_type := t.fn_pointer_call_return_type(rhs) { is_result = ret_type is types.ResultType || ret_type.name().starts_with('!') is_option = ret_type is types.OptionType || ret_type.name().starts_with('?') } } if is_result || is_option { // Handle Result/Option if-guard // Generate: { _tmp := call(); if (!_tmp.is_error) { attr := extractValue(_tmp); body } else { else } } temp_name := t.gen_temp_name() temp_pos := synth_pos if_pos := t.next_synth_pos() temp_ident := ast.Ident{ name: temp_name pos: temp_pos } mut stmts := []ast.Stmt{} mut data_type := types.Type(types.voidptr_) for prefix_stmt in guard_prefix_stmts { stmts << prefix_stmt } // Register temp variable type so cleanc can look up its type for .data unwrapping if wrapper_type := t.expr_wrapper_type_for_or(rhs) { t.register_temp_var(temp_name, wrapper_type) t.register_synth_type(temp_pos, wrapper_type) if wrapper_type is types.ResultType { data_type = wrapper_type.base_type is_result = true is_option = false } else if wrapper_type is types.OptionType { data_type = wrapper_type.base_type is_result = false is_option = true } } else if wrapper_type := t.get_expr_type(rhs) { t.register_temp_var(temp_name, wrapper_type) t.register_synth_type(temp_pos, wrapper_type) if wrapper_type is types.ResultType { data_type = wrapper_type.base_type } else if wrapper_type is types.OptionType { data_type = wrapper_type.base_type } } else if wrapper_type := t.fn_pointer_call_return_type(rhs) { t.register_temp_var(temp_name, wrapper_type) t.register_synth_type(temp_pos, wrapper_type) if wrapper_type is types.ResultType { data_type = wrapper_type.base_type } else if wrapper_type is types.OptionType { data_type = wrapper_type.base_type } } // 1. _tmp := call() transformed_rhs := t.transform_expr(rhs) if t.pending_stmts.len > 0 { for ps in t.pending_stmts { stmts << ps } t.pending_stmts.clear() } stmts << ast.AssignStmt{ op: .decl_assign lhs: [ast.Expr(temp_ident)] rhs: [transformed_rhs] pos: temp_pos } // 2. Build condition: !_tmp.is_error (for Result) or _tmp.state == 0 (for Option) success_cond := if is_result { ast.Expr(ast.PrefixExpr{ op: .not expr: t.synth_selector(temp_ident, 'is_error', types.Type(types.bool_)) }) } else { t.make_infix_expr(.eq, t.synth_selector(temp_ident, 'state', types.Type(types.int_)), t.make_number_expr('0')) } // 3. Build if-body: attr := _tmp.data; original_body // cleanc handles the cast and dereference when it sees .data on Result type mut if_stmts := []ast.Stmt{} data_access := t.synth_selector(temp_ident, 'data', data_type) t.register_if_guard_lhs_payload_type(guard.stmt.lhs, data_type) if_stmts << ast.AssignStmt{ op: .decl_assign lhs: guard.stmt.lhs rhs: [data_access] pos: guard.stmt.pos } for s in if_expr.stmts { if_stmts << s } // 4. Build the if expression transformed_if_stmts := t.transform_stmts(if_stmts) modified_if := ast.IfExpr{ cond: success_cond stmts: transformed_if_stmts else_expr: t.if_guard_else_expr_with_err(if_expr.else_expr, temp_ident) pos: if_pos } // Propagate the original IfExpr type to the synthesized node if orig_type := t.get_expr_type(ast.Expr(if_expr)) { t.register_synth_type(if_pos, orig_type) } stmts << ast.ExprStmt{ expr: modified_if } return stmts } // Non-Result/Option if-guard // Check if RHS is an index expression (map or array lookup) // For map lookups: if x := map[key] { use(x) } // Transform to: { _tmp := map__get_check(&map, &key); if (_tmp != nil) { x := *_tmp; use(x) } } // For array lookups: if x := arr[i] { use(x) } // Transform to: if (i < arr.len) { x := arr[i]; use(x) } if rhs is ast.IndexExpr { if map_expr_typ := t.get_expr_type(rhs.lhs) { if map_type := t.unwrap_map_type(map_expr_typ) { // This is a map lookup - use map__get_check pattern temp_pos := synth_pos if_pos := t.next_synth_pos() temp_name := t.gen_temp_name() temp_ident := ast.Ident{ name: temp_name pos: temp_pos } // Register temp variable type: map__get_check returns pointer to value type pointer_type := types.Type(types.Pointer{ base_type: map_type.value_type }) t.register_temp_var(temp_name, pointer_type) t.register_synth_type(temp_pos, pointer_type) mut prefix_stmts := []ast.Stmt{} map_arg := if t.is_pointer_type(map_expr_typ) { t.transform_expr(rhs.lhs) } else { t.addr_of_with_prefix_temp(rhs.lhs, map_expr_typ, mut prefix_stmts) } key_arg := t.addr_of_with_prefix_temp(rhs.expr, map_type.key_type, mut prefix_stmts) get_check_call := ast.CallExpr{ lhs: ast.Ident{ name: 'map__get_check' } args: [ map_arg, t.voidptr_cast(key_arg), ] } temp_assign := ast.AssignStmt{ op: .decl_assign lhs: [ast.Expr(temp_ident)] rhs: [ast.Expr(get_check_call)] pos: synth_pos } // Build if body: guard_var := *_tmp; original_body // Use typed_deref to cast voidptr to correct pointer type // before dereference (map__get_check returns voidptr) mut if_stmts := []ast.Stmt{} deref_tmp := t.typed_deref(temp_ident, map_type.value_type) if_stmts << ast.AssignStmt{ op: .decl_assign lhs: guard.stmt.lhs rhs: [deref_tmp] pos: guard.stmt.pos } for s in if_expr.stmts { if_stmts << s } // Build condition: _tmp != nil null_check := t.make_infix_expr(.ne, ast.Expr(temp_ident), ast.Expr(ast.Ident{ name: 'nil' })) // Build the if expression modified_if := ast.IfExpr{ cond: null_check stmts: t.transform_stmts(if_stmts) else_expr: t.transform_expr(if_expr.else_expr) pos: if_pos } // Propagate the original IfExpr type to the synthesized node if orig_type := t.get_expr_type(ast.Expr(if_expr)) { t.register_synth_type(if_pos, orig_type) } prefix_stmts << ast.Stmt(temp_assign) prefix_stmts << ast.Stmt(ast.ExprStmt{ expr: modified_if }) return prefix_stmts } } // This is an array lookup - generate bounds check: index < array.len bounds_check := t.make_infix_expr_at(.lt, t.transform_expr(rhs.expr), t.synth_selector(t.transform_expr(rhs.lhs), 'len', types.Type(types.int_)), rhs.pos) // Build if body: guard_var := arr[i]; original_body mut if_stmts := []ast.Stmt{} if_stmts << ast.AssignStmt{ op: .decl_assign lhs: guard.stmt.lhs rhs: guard.stmt.rhs pos: guard.stmt.pos } for s in if_expr.stmts { if_stmts << s } // Build the if expression modified_if := ast.IfExpr{ cond: bounds_check stmts: t.transform_stmts(if_stmts) else_expr: t.transform_expr(if_expr.else_expr) pos: synth_pos } // Propagate the original IfExpr type to the synthesized node if orig_type := t.get_expr_type(ast.Expr(if_expr)) { t.register_synth_type(synth_pos, orig_type) } return [ ast.Stmt(ast.ExprStmt{ expr: modified_if }), ] } rhs_expr := t.transform_expr(rhs) // For map lookups returning arrays, generate: // { arr := map[key]; if (arr.data != nil) { ... } } // This handles the case where "key exists" = "non-nil data" map_returns_array := t.is_map_lookup_returning_array(rhs) // Prepend guard variable assignment to stmts (inside the if body) guard_assign := ast.AssignStmt{ op: .decl_assign lhs: guard.stmt.lhs rhs: guard.stmt.rhs pos: guard.stmt.pos } mut new_stmts := []ast.Stmt{cap: if_expr.stmts.len + 1} new_stmts << guard_assign for s in if_expr.stmts { new_stmts << s } // Determine the condition to use mut cond_expr := ast.Expr(rhs_expr) if map_returns_array { // For arrays, check .data != nil (indicates key existed in map) // Extract guard variable name mut guard_var_name := '' for lhs_expr in guard.stmt.lhs { if lhs_expr is ast.Ident { guard_var_name = lhs_expr.name break } } // Check if it's a blank identifier - if so, use a temp variable is_blank := guard_var_name == '_' if is_blank { guard_var_name = t.gen_temp_name() } if guard_var_name != '' { // Generate: guard_var.data != nil // But we need to declare the variable first, so we generate: // { arr := map[key]; if (arr.data) { ... } } // Put assignment before the if, then use arr.data as condition // When blank, use temp variable instead of _ temp_lhs := if is_blank { [ ast.Expr(ast.Ident{ name: guard_var_name pos: synth_pos }), ] } else { guard.stmt.lhs } temp_assign := ast.AssignStmt{ op: .decl_assign lhs: temp_lhs rhs: guard.stmt.rhs pos: guard.stmt.pos } // Remove the guard_assign from new_stmts since we're putting it before the if new_stmts = []ast.Stmt{cap: if_expr.stmts.len} for s in if_expr.stmts { new_stmts << s } // Use arr.data as condition cond_expr = t.synth_selector(ast.Ident{ name: guard_var_name pos: synth_pos }, 'data', types.Type(types.voidptr_)) modified_if := ast.IfExpr{ cond: cond_expr stmts: t.transform_stmts(new_stmts) else_expr: t.transform_expr(if_expr.else_expr) pos: synth_pos } // Propagate the original IfExpr type to the synthesized node if orig_type := t.get_expr_type(ast.Expr(if_expr)) { t.register_synth_type(synth_pos, orig_type) } return [ ast.Stmt(temp_assign), ast.Stmt(ast.ExprStmt{ expr: modified_if }), ] } } modified_if := ast.IfExpr{ cond: cond_expr stmts: t.transform_stmts(new_stmts) else_expr: t.transform_expr(if_expr.else_expr) pos: synth_pos } // Propagate the original IfExpr type to the synthesized node if orig_type := t.get_expr_type(ast.Expr(if_expr)) { t.register_synth_type(synth_pos, orig_type) } return [ast.Stmt(ast.ExprStmt{ expr: modified_if })] } // try_expand_return_if_expr handles IfExpr in return statements // Transforms: return if cond { a } else { b } // Into: if cond { return a } else { return b } fn (mut t Transformer) try_expand_return_if_expr(stmt ast.ReturnStmt) ?[]ast.Stmt { // Only handle single-expression returns with IfExpr if stmt.exprs.len != 1 { return none } if_expr := stmt.exprs[0] if if_expr !is ast.IfExpr { return none } ie := if_expr as ast.IfExpr // Must have an else branch to be a valid expression form if ie.else_expr is ast.EmptyExpr { return none } // Transform into if-statement with return in each branch return t.expand_return_if_expr(ie) } // try_expand_return_match_expr handles MatchExpr in return statements. // It first lowers the match to an if-expression, then emits branch-local returns so // optional/result and aggregate return ABI handling is applied independently per branch. fn (mut t Transformer) try_expand_return_match_expr(stmt ast.ReturnStmt) ?[]ast.Stmt { if stmt.exprs.len != 1 { return none } match_expr := stmt.exprs[0] if match_expr !is ast.MatchExpr { return none } match_expr_ast := match_expr as ast.MatchExpr return_sumtype_info := t.current_return_sumtype_wrap_info() or { ConcreteSumtypeWrapInfo{} } should_wrap_return_sumtype := return_sumtype_info.name != '' skip_return_sumtype_wrap := (t.cur_fn_returns_option || t.cur_fn_returns_result) && t.return_expr_should_skip_sumtype_wrap(match_expr) old_wrap := t.sumtype_return_wrap if should_wrap_return_sumtype && !skip_return_sumtype_wrap { t.sumtype_return_wrap = return_sumtype_info.name } old_preserve_match_branch_value := t.preserve_match_branch_value t.preserve_match_branch_value = true transformed := t.transform_match_expr(match_expr_ast) t.preserve_match_branch_value = old_preserve_match_branch_value t.sumtype_return_wrap = old_wrap if transformed is ast.IfExpr { ie := transformed as ast.IfExpr return t.expand_return_match_if_expr(ie) } return none } // expand_return_if_expr recursively expands an if-expression into if-statements with returns fn (mut t Transformer) expand_return_if_expr(ie ast.IfExpr) []ast.Stmt { return t.expand_return_if_expr_with_options(ie, false) } fn (mut t Transformer) expand_return_match_if_expr(ie ast.IfExpr) []ast.Stmt { return t.expand_return_if_expr_with_options(ie, true) } fn (mut t Transformer) return_stmts_for_branch_expr(expr ast.Expr, allow_empty_else bool) []ast.Stmt { if expr is ast.IfExpr { return t.expand_return_if_expr_with_options(expr, allow_empty_else) } if t.is_void_call_expr(expr) { return [ ast.Stmt(ast.ExprStmt{ expr: expr }), ] } if expr is ast.UnsafeExpr && expr.stmts.len > 0 { last := expr.stmts[expr.stmts.len - 1] if last is ast.ExprStmt && last.expr is ast.IfExpr { mut stmts := []ast.Stmt{cap: expr.stmts.len} if expr.stmts.len > 1 { for stmt in expr.stmts[..expr.stmts.len - 1] { stmts << stmt } } stmts << t.expand_return_if_expr_with_options(last.expr, allow_empty_else) return stmts } } return_expr := if info := t.current_return_sumtype_wrap_info() { t.wrap_sumtype_value_transformed_with_variants(expr, info.name, info.variants) or { expr } } else { expr } return [ ast.Stmt(ast.ReturnStmt{ exprs: [return_expr] }), ] } fn (mut t Transformer) expand_return_if_expr_with_options(ie ast.IfExpr, allow_empty_else bool) []ast.Stmt { // Build the then-branch statements with return mut then_stmts := []ast.Stmt{} for i, s in ie.stmts { if i == ie.stmts.len - 1 { // Last statement - wrap in return if it's an expression if s is ast.ExprStmt { then_stmts << t.return_stmts_for_branch_expr(s.expr, allow_empty_else) } else { then_stmts << s } } else { then_stmts << s } } // Build the else-branch mut else_expr := ast.empty_expr if ie.else_expr is ast.IfExpr { else_ie := ie.else_expr as ast.IfExpr // Check if this is a pure else block (no condition) if else_ie.cond is ast.EmptyExpr { // Pure else - create an if block with just the else stmts that include return mut else_stmts := []ast.Stmt{} for i, s in else_ie.stmts { if i == else_ie.stmts.len - 1 { if s is ast.ExprStmt { else_stmts << t.return_stmts_for_branch_expr(s.expr, allow_empty_else) } else { else_stmts << s } } else { else_stmts << s } } else_expr = ast.Expr(ast.IfExpr{ cond: ast.empty_expr stmts: else_stmts else_expr: ast.empty_expr }) } else { // else-if chain - recursively expand expanded_else := t.expand_return_if_expr_with_options(else_ie, allow_empty_else) // The expanded result should be an ExprStmt containing an IfExpr if expanded_else.len > 0 && expanded_else[0] is ast.ExprStmt { expr_stmt := expanded_else[0] as ast.ExprStmt else_expr = expr_stmt.expr } else { // Fallback: wrap in else block else_expr = ast.Expr(ast.IfExpr{ cond: ast.empty_expr stmts: expanded_else else_expr: ast.empty_expr }) } } } else if ie.else_expr is ast.EmptyExpr && allow_empty_else { else_expr = ast.empty_expr } else { // Simple else - wrap the expression in return else_expr = ast.Expr(ast.IfExpr{ cond: ast.empty_expr stmts: t.return_stmts_for_branch_expr(ie.else_expr, allow_empty_else) else_expr: ast.empty_expr }) } // Create the transformed if expression (used as statement) transformed_if := ast.IfExpr{ cond: ie.cond stmts: then_stmts else_expr: else_expr } // Wrap in ExprStmt to make it a valid statement return [ast.Stmt(ast.ExprStmt{ expr: transformed_if })] } // try_expand_if_expr_assign_stmts handles assignment with IfExpr RHS. // Transforms: lhs = if cond { a } else { b } // Into: if cond { lhs = a } else { lhs = b } fn (mut t Transformer) try_expand_if_expr_assign_stmts(stmt ast.AssignStmt) ?[]ast.Stmt { // Only handle simple assignment for now. if stmt.op != .assign || stmt.lhs.len != 1 || stmt.rhs.len != 1 { return none } rhs := stmt.rhs[0] if rhs !is ast.IfExpr { return none } ie := rhs as ast.IfExpr // Expression-form if must have else branch. if ie.else_expr is ast.EmptyExpr { return none } return t.expand_assign_if_expr(stmt.lhs[0], ie) } // expand_assign_if_expr recursively expands an if-expression into if-statements // that perform assignment in each branch. fn (mut t Transformer) expand_assign_if_expr(lhs ast.Expr, ie ast.IfExpr) []ast.Stmt { mut then_stmts := []ast.Stmt{} for i, s in ie.stmts { if i == ie.stmts.len - 1 && s is ast.ExprStmt { then_stmts << ast.AssignStmt{ op: .assign lhs: [lhs] rhs: [s.expr] } } else { then_stmts << s } } mut else_expr := ast.empty_expr if ie.else_expr is ast.IfExpr { else_ie := ie.else_expr as ast.IfExpr // else { ... } if else_ie.cond is ast.EmptyExpr { mut else_stmts := []ast.Stmt{} for i, s in else_ie.stmts { if i == else_ie.stmts.len - 1 && s is ast.ExprStmt { else_stmts << ast.AssignStmt{ op: .assign lhs: [lhs] rhs: [s.expr] } } else { else_stmts << s } } else_expr = ast.Expr(ast.IfExpr{ cond: ast.empty_expr stmts: else_stmts else_expr: ast.empty_expr }) } else { expanded_else := t.expand_assign_if_expr(lhs, else_ie) if expanded_else.len > 0 { first_else_stmt := expanded_else[0] if first_else_stmt is ast.ExprStmt { else_expr = (first_else_stmt as ast.ExprStmt).expr } else { else_expr = ast.Expr(ast.IfExpr{ cond: ast.empty_expr stmts: expanded_else else_expr: ast.empty_expr }) } } else { else_expr = ast.Expr(ast.IfExpr{ cond: ast.empty_expr stmts: expanded_else else_expr: ast.empty_expr }) } } } else { else_expr = ast.Expr(ast.IfExpr{ cond: ast.empty_expr stmts: [ ast.Stmt(ast.AssignStmt{ op: .assign lhs: [lhs] rhs: [ie.else_expr] }), ] else_expr: ast.empty_expr }) } transformed_if := ast.IfExpr{ cond: ie.cond stmts: then_stmts else_expr: else_expr } return [ast.Stmt(ast.ExprStmt{ expr: transformed_if })] } // if_expr_is_value returns true if the IfExpr produces a value (i.e., the body's // last statement is an ExprStmt whose expression has a non-void type). // This distinguishes value-position ifs from statement-position ifs that happen // to have an else branch. fn (t &Transformer) if_expr_is_value(ie ast.IfExpr) bool { if ie.stmts.len == 0 { return false } last := ie.stmts[ie.stmts.len - 1] if last !is ast.ExprStmt { return false } // Check that the expression actually produces a non-void value. // Statement-form ifs may end with void function calls or postfix ops // used for side effects (i++, println(...), etc). last_expr := (last as ast.ExprStmt).expr if typ := t.get_expr_type(last_expr) { type_name := t.type_to_c_name(typ) if type_name == '' || type_name == 'void' { return false } } return true } // try_expand_tuple_if_assign_stmts expands `x, y, w, h := if cond { a, b, c, d } else { e, f, g, h }` // into individual declarations + if-statement with assignments: // x := 0; y := 0; w := 0; h := 0; // if cond { x = a; y = b; w = c; h = d } else { x = e; y = f; w = g; h = h2 } fn (mut t Transformer) try_expand_tuple_if_assign_stmts(stmt ast.AssignStmt) ?[]ast.Stmt { if stmt.op != .decl_assign { return none } // Must have tuple LHS is_tuple_lhs := stmt.lhs.len > 1 || (stmt.lhs.len == 1 && stmt.lhs[0] is ast.Tuple) if !is_tuple_lhs { return none } // Must have single IfExpr RHS if stmt.rhs.len != 1 { return none } if stmt.rhs[0] !is ast.IfExpr { // Check if RHS might be Tuple containing an IfExpr return none } tuple_lhs := if stmt.lhs.len > 1 { stmt.lhs } else if stmt.lhs[0] is ast.Tuple { (stmt.lhs[0] as ast.Tuple).exprs } else { stmt.lhs } n := tuple_lhs.len if n == 0 { return none } if_expr := stmt.rhs[0] as ast.IfExpr // Extract tuple values from then branch then_stmts := t.build_tuple_branch_assigns(if_expr.stmts, tuple_lhs, n, stmt.pos) or { return none } // Process else branch mut else_expr := ast.Expr(ast.empty_expr) if if_expr.else_expr is ast.IfExpr { else_if := if_expr.else_expr as ast.IfExpr if else_if.cond is ast.EmptyExpr { // Plain else block else_stmts := t.build_tuple_branch_assigns(else_if.stmts, tuple_lhs, n, stmt.pos) or { return none } else_expr = ast.IfExpr{ stmts: else_stmts pos: else_if.pos } } else { // else-if chain else_if_stmts := t.build_tuple_branch_assigns(else_if.stmts, tuple_lhs, n, stmt.pos) or { return none } else_expr = ast.IfExpr{ cond: else_if.cond stmts: else_if_stmts else_expr: else_if.else_expr pos: else_if.pos } } } // Build result: declarations for each variable, then the if-statement mut result := []ast.Stmt{cap: n + 1} for lhs_expr in tuple_lhs { result << ast.Stmt(ast.AssignStmt{ op: .decl_assign lhs: [lhs_expr] rhs: [ast.Expr(ast.BasicLiteral{ kind: .number value: '0' })] pos: stmt.pos }) } result << ast.Stmt(ast.ExprStmt{ expr: ast.IfExpr{ cond: if_expr.cond stmts: then_stmts else_expr: else_expr pos: if_expr.pos } }) return result } // extract_branch_tuple_values extracts N values from a branch's last statement. // The last statement should be an ExprStmt containing either a Tuple or a single expression. fn (t &Transformer) extract_branch_tuple_values(stmts []ast.Stmt, n int) ?[]ast.Expr { if stmts.len == 0 { return none } // Find last non-empty statement (skip trailing EmptyStmt) mut last_idx := stmts.len - 1 for last_idx >= 0 && stmts[last_idx] is ast.EmptyStmt { last_idx-- } if last_idx < 0 { return none } last := stmts[last_idx] if last !is ast.ExprStmt { return none } last_expr := (last as ast.ExprStmt).expr if last_expr is ast.Tuple { if last_expr.exprs.len == n { return last_expr.exprs } return none } // Single expression, only valid for n == 1 if n == 1 { return [last_expr] } return none } // build_tuple_branch_assigns builds assignment statements for a tuple if-expression branch. // If the branch ends with a Tuple literal (a, b, c), it assigns each element directly. // If the branch ends with a single call expression returning a tuple, it assigns the call // to a temp variable and extracts .arg0, .arg1, etc. fn (mut t Transformer) build_tuple_branch_assigns(stmts []ast.Stmt, tuple_lhs []ast.Expr, n int, pos token.Pos) ?[]ast.Stmt { if tuple_values := t.extract_branch_tuple_values(stmts, n) { // Branch has explicit tuple values — assign each one directly mut result := []ast.Stmt{cap: n} for i in 0 .. n { result << ast.Stmt(ast.AssignStmt{ op: .assign lhs: [tuple_lhs[i]] rhs: [tuple_values[i]] pos: pos }) } return result } // Branch has a single non-tuple expression (e.g. a function call returning a tuple). // Extract it and assign via temp: _tuple_tN = call(); x = _tuple_tN.arg0; y = _tuple_tN.arg1 if stmts.len == 0 { return none } mut last_idx := stmts.len - 1 for last_idx >= 0 && stmts[last_idx] is ast.EmptyStmt { last_idx-- } if last_idx < 0 { return none } last := stmts[last_idx] if last !is ast.ExprStmt { return none } last_expr := (last as ast.ExprStmt).expr if last_expr is ast.Tuple { return none // Tuple case was already handled above } // Single expression returning a tuple (e.g. function call) t.temp_counter++ tmp_name := '_tuple_t${t.temp_counter}' tmp_ident := ast.Ident{ name: tmp_name } mut result := []ast.Stmt{cap: n + 1} // Include any preceding statements from the branch for i in 0 .. last_idx { if stmts[i] !is ast.EmptyStmt { result << stmts[i] } } // Assign call result to temp result << ast.Stmt(ast.AssignStmt{ op: .decl_assign lhs: [ast.Expr(tmp_ident)] rhs: [last_expr] pos: pos }) // Extract .arg0, .arg1, etc. for i in 0 .. n { result << ast.Stmt(ast.AssignStmt{ op: .assign lhs: [tuple_lhs[i]] rhs: [ ast.Expr(ast.SelectorExpr{ lhs: tmp_ident rhs: ast.Ident{ name: 'arg${i}' } }), ] pos: pos }) } return result } // lower_if_expr_value lowers a value-position IfExpr into a temp variable + statement-form if. // Generates: _if_t := if cond { a } else { b } // Hoists the decl_assign via pending_stmts and returns the temp ident as replacement. fn (mut t Transformer) lower_if_expr_value(ie ast.IfExpr) ast.Expr { t.temp_counter++ tmp_name := '_if_t${t.temp_counter}' tmp_ident := ast.Ident{ name: tmp_name } // Register temp variable type so cleanc can resolve it from scope if typ := t.get_expr_type(ast.Expr(ie)) { t.register_temp_var(tmp_name, typ) } // Hoist the decl_assign with the IfExpr as RHS. // cleanc's gen_assign_stmt already handles decl_assign + IfExpr RHS // by emitting: Type tmp; if (cond) { tmp = a; } else { tmp = b; } t.pending_stmts << ast.Stmt(ast.AssignStmt{ op: .decl_assign lhs: [ast.Expr(tmp_ident)] rhs: [ast.Expr(ie)] pos: ie.pos }) return ast.Expr(tmp_ident) } fn (mut t Transformer) collect_defers_in_if(node ast.IfExpr, mut defer_bodies [][]ast.Stmt) ast.Expr { new_else := t.collect_defers_in_else(node.else_expr, mut defer_bodies) return ast.IfExpr{ cond: node.cond stmts: t.collect_and_remove_defers(node.stmts, mut defer_bodies) else_expr: new_else } } fn (mut t Transformer) collect_defers_in_else(else_expr ast.Expr, mut defer_bodies [][]ast.Stmt) ast.Expr { if else_expr is ast.IfExpr { return t.collect_defers_in_if(else_expr, mut defer_bodies) } return else_expr } // inject_defer_before_returns walks the statement list and replaces return statements // with: { defer_body; return expr; } — saving the return value in a temp var if needed. fn (mut t Transformer) inject_defer_before_returns(stmts []ast.Stmt, defer_stmts []ast.Stmt, has_return_type bool) []ast.Stmt { mut result := []ast.Stmt{cap: stmts.len} for stmt in stmts { match stmt { ast.ReturnStmt { if has_return_type && stmt.exprs.len > 0 { // Save return value to temp, run defers, return temp t.temp_counter++ temp_name := '_defer_t${t.temp_counter}' if expr_type := t.get_expr_type(stmt.exprs[0]) { t.register_temp_var(temp_name, expr_type) } ret_expr := ast.Expr(stmt.exprs[0]) result << ast.Stmt(ast.AssignStmt{ op: .decl_assign lhs: [ast.Expr(ast.Ident{ name: temp_name })] rhs: [ret_expr] }) result << defer_stmts result << ast.Stmt(ast.ReturnStmt{ exprs: [ast.Expr(ast.Ident{ name: temp_name })] }) } else { result << defer_stmts result << ast.Stmt(stmt) } } ast.ExprStmt { expr := stmt.expr if expr is ast.IfExpr { result << ast.Stmt(ast.ExprStmt{ expr: t.inject_defer_in_if_expr(expr, defer_stmts, has_return_type) }) } else { result << ast.Stmt(stmt) } } ast.ForStmt { result << ast.Stmt(ast.ForStmt{ init: stmt.init cond: stmt.cond post: stmt.post stmts: t.inject_defer_before_returns(stmt.stmts, defer_stmts, has_return_type) }) } ast.BlockStmt { result << ast.Stmt(ast.BlockStmt{ stmts: t.inject_defer_before_returns(stmt.stmts, defer_stmts, has_return_type) }) } else { result << stmt } } } return result } fn (mut t Transformer) inject_defer_in_if_expr(node ast.IfExpr, defer_stmts []ast.Stmt, has_return_type bool) ast.Expr { new_else := t.inject_defer_in_else(node.else_expr, defer_stmts, has_return_type) return ast.IfExpr{ cond: node.cond stmts: t.inject_defer_before_returns(node.stmts, defer_stmts, has_return_type) else_expr: new_else } } fn (mut t Transformer) inject_defer_in_else(else_expr ast.Expr, defer_stmts []ast.Stmt, has_return_type bool) ast.Expr { match else_expr { ast.IfExpr { // else if: recurse return t.inject_defer_in_if_expr(else_expr, defer_stmts, has_return_type) } ast.EmptyExpr { return else_expr } else { return else_expr } } } // transform_comptime_expr evaluates compile-time conditionals and returns the selected branch fn (mut t Transformer) eval_comptime_if(node ast.IfExpr) ast.Expr { cond_result := t.eval_comptime_cond(node.cond) if cond_result { // Condition is true - return the then branch with transformed statements if node.stmts.len == 1 { stmt := node.stmts[0] if stmt is ast.ExprStmt { return t.transform_expr(stmt.expr) } } // Multi-statement branch at expression level can't be represented; // statement-level expansion handles these properly return ast.empty_expr } else { // Condition is false - evaluate else branch else_e := node.else_expr if else_e !is ast.EmptyExpr { if else_e is ast.IfExpr { if else_e.cond is ast.EmptyExpr { // Plain $else block if else_e.stmts.len == 1 { stmt := else_e.stmts[0] if stmt is ast.ExprStmt { return t.transform_expr(stmt.expr) } } // Multi-statement $else at expression level return ast.empty_expr } else { // $else $if - recursive evaluation return t.eval_comptime_if(else_e) } } } } // Condition is false and no else branch - return empty (comptime block is skipped) return ast.empty_expr } // resolve_comptime_if_stmts evaluates a compile-time $if condition and returns // the selected branch's statements, fully resolving the comptime at statement level. fn (mut t Transformer) resolve_comptime_if_stmts(node ast.IfExpr) []ast.Stmt { cond_result := t.eval_comptime_cond(node.cond) if cond_result { return node.stmts } // Condition is false - evaluate else branch else_e := node.else_expr if else_e is ast.IfExpr { if else_e.cond is ast.EmptyExpr { // Plain $else block return else_e.stmts } // $else $if - recursive evaluation return t.resolve_comptime_if_stmts(else_e) } return [] } // can_eval_comptime_cond returns true if the transformer can evaluate this // comptime condition. Returns false for conditions involving generic type // checks (key_is/not_is) that need to be resolved by the backend. fn (t &Transformer) can_eval_comptime_cond(cond ast.Expr) bool { match cond { ast.Ident { return true } ast.ComptimeExpr { return t.can_eval_comptime_cond(cond.expr) } ast.CallExpr { return transformer_pkgconfig_call_name(cond) != none } ast.CallOrCastExpr { return transformer_pkgconfig_call_name(cond) != none } ast.PrefixExpr { return t.can_eval_comptime_cond(cond.expr) } ast.InfixExpr { if cond.op == .key_is || cond.op == .not_is { return false } return t.can_eval_comptime_cond(cond.lhs) && t.can_eval_comptime_cond(cond.rhs) } ast.PostfixExpr { return true } ast.ParenExpr { return t.can_eval_comptime_cond(cond.expr) } else { return false } } } fn (t &Transformer) can_eval_comptime_cond_cursor(cond ast.Cursor) bool { if !cond.is_valid() { return false } match cond.kind() { .expr_ident { return true } .expr_comptime, .expr_paren { return t.can_eval_comptime_cond_cursor(cond.edge(0)) } .expr_call, .expr_call_or_cast { return transformer_pkgconfig_call_name_cursor(cond) != none } .expr_prefix { return t.can_eval_comptime_cond_cursor(cond.edge(0)) } .expr_infix { op := unsafe { token.Token(int(cond.aux())) } if op == .key_is || op == .not_is { return false } return t.can_eval_comptime_cond_cursor(cond.edge(0)) && t.can_eval_comptime_cond_cursor(cond.edge(1)) } .expr_postfix { return true } else { return false } } } // transform_comptime_if_bodies recursively transforms the body stmts of each // branch in a comptime $if, without evaluating the condition. This is used when // the condition can't be evaluated at transform time (e.g., generic type checks). fn (mut t Transformer) transform_comptime_if_bodies(node ast.IfExpr) ast.IfExpr { transformed_stmts := t.transform_stmts(node.stmts) mut transformed_else := node.else_expr if node.else_expr is ast.IfExpr { else_if := node.else_expr as ast.IfExpr transformed_else_if := t.transform_comptime_if_bodies(else_if) transformed_else = ast.Expr(transformed_else_if) } return ast.IfExpr{ cond: node.cond stmts: transformed_stmts else_expr: transformed_else } } // eval_comptime_cond evaluates a compile-time condition expression fn (t &Transformer) eval_comptime_cond(cond ast.Expr) bool { match cond { ast.Ident { return t.eval_comptime_flag(cond.name) } ast.ComptimeExpr { return t.eval_comptime_cond(cond.expr) } ast.CallExpr { if pkg_name := transformer_pkgconfig_call_name(cond) { return t.eval_pkgconfig_cond(pkg_name) } } ast.CallOrCastExpr { if pkg_name := transformer_pkgconfig_call_name(cond) { return t.eval_pkgconfig_cond(pkg_name) } } ast.PrefixExpr { if cond.op == .not { return !t.eval_comptime_cond(cond.expr) } } ast.InfixExpr { if cond.op == .and { return t.eval_comptime_cond(cond.lhs) && t.eval_comptime_cond(cond.rhs) } if cond.op == .logical_or { return t.eval_comptime_cond(cond.lhs) || t.eval_comptime_cond(cond.rhs) } } ast.PostfixExpr { // Handle optional feature check: feature? if cond.op == .question { inner := cond.expr if inner is ast.Ident { return pref.comptime_optional_flag_value(t.pref, inner.name) } } } ast.ParenExpr { return t.eval_comptime_cond(cond.expr) } else {} } return false } fn (t &Transformer) eval_comptime_cond_cursor(cond ast.Cursor) bool { if !cond.is_valid() { return false } match cond.kind() { .expr_ident { return t.eval_comptime_flag(cond.name()) } .expr_comptime { return t.eval_comptime_cond_cursor(cond.edge(0)) } .expr_call, .expr_call_or_cast { if pkg_name := transformer_pkgconfig_call_name_cursor(cond) { return t.eval_pkgconfig_cond(pkg_name) } } .expr_prefix { op := unsafe { token.Token(int(cond.aux())) } if op == .not { return !t.eval_comptime_cond_cursor(cond.edge(0)) } } .expr_infix { op := unsafe { token.Token(int(cond.aux())) } if op == .and { return t.eval_comptime_cond_cursor(cond.edge(0)) && t.eval_comptime_cond_cursor(cond.edge(1)) } if op == .logical_or { return t.eval_comptime_cond_cursor(cond.edge(0)) || t.eval_comptime_cond_cursor(cond.edge(1)) } } .expr_postfix { op := unsafe { token.Token(int(cond.aux())) } inner := cond.edge(0) if op == .question && inner.is_valid() && inner.kind() == .expr_ident { return pref.comptime_optional_flag_value(t.pref, inner.name()) } } .expr_paren { return t.eval_comptime_cond_cursor(cond.edge(0)) } else {} } return false } fn (t &Transformer) eval_pkgconfig_cond(pkg_name string) bool { if t.pref.is_cross_target() { return false } return pref.comptime_pkgconfig_value(pkg_name) } fn transformer_pkgconfig_call_name(expr ast.Expr) ?string { match expr { ast.CallExpr { if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' && expr.args.len == 1 { return transformer_string_literal_value(expr.args[0]) } } ast.CallOrCastExpr { if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' { return transformer_string_literal_value(expr.expr) } } else {} } return none } fn transformer_pkgconfig_call_name_cursor(expr ast.Cursor) ?string { match expr.kind() { .expr_call { lhs := expr.edge(0) if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' && expr.edge_count() == 2 { return transformer_string_literal_value_cursor(expr.edge(1)) } } .expr_call_or_cast { lhs := expr.edge(0) if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' { return transformer_string_literal_value_cursor(expr.edge(1)) } } else {} } return none } fn transformer_string_literal_value(expr ast.Expr) ?string { if expr is ast.StringLiteral { return transformer_unquote_string_literal_value(expr.value) } return none } fn transformer_string_literal_value_cursor(expr ast.Cursor) ?string { if !expr.is_valid() || expr.kind() != .expr_string { return none } return transformer_unquote_string_literal_value(expr.name()) } fn transformer_unquote_string_literal_value(value string) string { if value.len >= 2 && ((value[0] == `"` && value[value.len - 1] == `"`) || (value[0] == `'` && value[value.len - 1] == `'`)) { return value[1..value.len - 1] } return value } // eval_comptime_flag delegates to the shared `pref.comptime_flag_value` so // the parser and the transformer recognize exactly the same flag names. fn (t &Transformer) eval_comptime_flag(name string) bool { // Fast path: the three native-backend gated flags hit the cached bool // rather than the helper's repeated pref/backend comparisons. if name == 'native' || name == 'builtin_write_buf_to_fd_should_use_c_write' || name == 'tinyc' { return t.is_native_be } return pref.comptime_flag_value(t.pref, name) }