Ctrl K
ComunidadGuías…

src/analysis/typecheck_stmt.c

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// SPDX-License-Identifier: MIT
#include "typecheck_internal.h"

void check_block(TypeChecker *tc, ASTNode *block, int depth)
{
    tc_enter_scope(tc);
    ASTNode *stmt = block->block.statements;
    int seen_terminator = 0;
    Token terminator_token = {0};

    while (stmt)
    {
        // Warn if we see code after a terminating statement
        if (seen_terminator && stmt->kind != NODE_LABEL)
        {
            const char *rule = tc->pctx->config->misra_mode ? "MISRA Rule 2.1: " : "";
            const char *hints[] = {"Remove unreachable code or restructure control flow", NULL};
            char msg[256];
            snprintf(msg, sizeof(msg), "%sUnreachable code detected", rule);
            tc_error_with_hints(tc, stmt->token, msg, hints);
            seen_terminator = 0; // Only warn once per block
        }

        if (tc->pctx->config->misra_mode)
        {
            // Rule 2.2: There shall be no dead code (expressions with no effect)
            // We ignore call expressions here as they are handled by Rule 17.7
            if (stmt->kind >= NODE_EXPR_BINARY && stmt->kind <= NODE_EXPR_SLICE &&
                stmt->kind != NODE_EXPR_CALL)
            {
                if (!tc_expr_has_side_effects(stmt))
                {
                    tc_error(tc, stmt->token, "MISRA Rule 2.2: expression statement has no effect");
                }
            }
        }

        int old_stmt_ctx = tc->is_stmt_context;
        tc->is_stmt_context = 1;
        check_node(tc, stmt, depth + 1);
        tc->is_stmt_context = old_stmt_ctx;

        // Track terminating statements
        if (stmt->kind == NODE_RETURN || stmt->kind == NODE_BREAK || stmt->kind == NODE_CONTINUE ||
            stmt->kind == NODE_GOTO)
        {
            seen_terminator = 1;
            terminator_token = stmt->token;
        }

        stmt = stmt->next;
    }
    (void)terminator_token; // May be used for enhanced diagnostics later
    tc_exit_scope(tc);
}

void check_var_decl(TypeChecker *tc, ASTNode *node, int depth)
{
    if (node->var_decl.type_info)
    {
        misra_check_pointer_nesting(tc->pctx, node->var_decl.type_info, node->token);
    }

    // MISRA: Mark type as used
    mark_type_as_used(tc, node->var_decl.type_info);

    // Initialize lifetime depth for escape analysis if this is a local variable
    if (node->var_decl.type_info && tc->current_func)
    {
        node->var_decl.type_info->lifetime_depth = tc->current_depth;
    }

    if (node->var_decl.init_expr)
    {
        if (node->type_info)
        {
            node->var_decl.init_expr->type_info = node->type_info;
        }
        int old_stmt_ctx = tc->is_stmt_context;
        tc->is_stmt_context = 0;
        check_node(tc, node->var_decl.init_expr, depth + 1);
        tc->is_stmt_context = old_stmt_ctx;

        Type *decl_type = node->var_decl.type_info;
        Type *init_type = node->var_decl.init_expr->type_info;

        if (decl_type && init_type)
        {
            apply_implicit_struct_pointer_conversions(tc, &node->var_decl.init_expr, decl_type);
            init_type = node->var_decl.init_expr->type_info;

            // The declared type retains its original lifetime metadata (from init or explicit decl)

            // If initialization exists, check compatibility
            if (node->var_decl.type_info)
            {
                check_type_compatibility(tc, node->var_decl.type_info, init_type, node->token,
                                         node->var_decl.init_expr, 0);
            }

            if (tc->pctx->config->misra_mode)
            {
                misra_check_pointer_conversion(tc->pctx, decl_type, init_type, node->token);

                // Rule 9.3: Arrays shall not be partially initialized.
                if (decl_type->kind == TYPE_ARRAY && init_type->kind == TYPE_ARRAY)
                {
                    if (node->var_decl.init_expr->kind == NODE_EXPR_ARRAY_LITERAL)
                    {
                        if (decl_type->array_size != init_type->array_size)
                        {
                            tc_error(tc, node->token, "MISRA Rule 9.3");
                        }
                    }
                }
            }
        }

        // Move Analysis: Check if the initializer moves a non-copy value.
        check_move_for_rvalue(tc, node->var_decl.init_expr);
    }

    if (node->type_info)
    {
        misra_check_pointer_nesting(tc->pctx, node->var_decl.type_info, node->token);
    }

    // If type is not explicit, we should ideally infer it from init_expr.
    Type *t = node->type_info;
    if (!t && node->var_decl.init_expr)
    {
        t = type_clone(node->var_decl.init_expr->type_info);
        // Ensure inferred local variables get the correct scope depth for escape analysis
        if (t && tc->current_func)
        {
            t->lifetime_depth = tc->current_depth;
        }
        node->type_info = t;
        node->var_decl.type_info = t;
    }

    misra_check_reserved_identifier(tc->pctx, node->var_decl.name, node->token);
    tc_add_symbol(tc, node->var_decl.name, t, node->token, 0);
    ZenSymbol *new_sym = tc_lookup(tc, node->var_decl.name);
    if (new_sym)
    {
        new_sym->is_static = node->var_decl.is_static;
        mark_symbol_valid(tc->pctx, new_sym, node);
    }

    if (tc->pctx->config->misra_mode && t && t->kind == TYPE_ARRAY)
    {
        // Rule 8.11: Array with external linkage shall have explicit size
        ZenSymbol *existing = tc_lookup(tc, node->var_decl.name);
        int is_static = (existing && existing->is_static) || (node->var_decl.is_static);
        int is_local = (existing && existing->is_local) || (tc->current_func != NULL);

        misra_check_external_array_size(tc->pctx, t, node->token, is_static, is_local);

        // Rule 18.8: No variable length arrays
        // In Zen C, all [T; N] arrays have constant size N, so Rule 18.8 is satisfied.
        // We only report if Zen somehow allowed non-constant sizes (which it doesn't in fixed-size
        // arrays).
    }
}

static ASTNode *loop_body(ASTNode *loop)
{
    switch (loop->kind)
    {
    case NODE_LOOP:
        return loop->loop_stmt.body;
    case NODE_WHILE:
        return loop->while_stmt.body;
    case NODE_FOR:
        return loop->for_stmt.body;
    case NODE_FOR_RANGE:
        return loop->for_range.body;
    default:
        return NULL;
    }
}

// Returns 1 if `node` contains a `break` that can exit the loop whose label is
// `loop_label` (NULL = unlabeled). `nested` is 1 while inside a loop nested
// within the loop we are testing, so unlabeled breaks there do not count.
static int stmt_has_exit_break(ASTNode *node, const char *loop_label, int nested)
{
    if (!node)
    {
        return 0;
    }

    switch (node->kind)
    {
    case NODE_BREAK:
        if (node->break_stmt.target_label)
        {
            return loop_label != NULL && strcmp(node->break_stmt.target_label, loop_label) == 0;
        }
        return !nested;

    case NODE_BLOCK:
        for (ASTNode *s = node->block.statements; s; s = s->next)
        {
            if (stmt_has_exit_break(s, loop_label, nested))
            {
                return 1;
            }
        }
        return 0;

    case NODE_IF:
        return stmt_has_exit_break(node->if_stmt.then_body, loop_label, nested) ||
               stmt_has_exit_break(node->if_stmt.else_body, loop_label, nested);

    case NODE_MATCH:
        for (ASTNode *c = node->match_stmt.cases; c; c = c->next)
        {
            if (stmt_has_exit_break(c, loop_label, nested))
            {
                return 1;
            }
        }
        return 0;

    case NODE_MATCH_CASE:
        return stmt_has_exit_break(node->match_case.body, loop_label, nested);

    case NODE_DEFER:
        return stmt_has_exit_break(node->defer_stmt.stmt, loop_label, nested);

    case NODE_LOOP:
    case NODE_WHILE:
    case NODE_FOR:
    case NODE_FOR_RANGE:
        // Nested loop: unlabeled breaks here target the nested loop, but a
        // labeled break naming our loop still exits ours.
        return stmt_has_exit_break(loop_body(node), loop_label, 1);

    case NODE_RETURN:
    case NODE_CONTINUE:
    default:
        return 0;
    }
}

// Returns 1 if `loop` (a NODE_LOOP) contains a `break` that can exit it.
static int stmt_loop_has_exit_break(ASTNode *loop)
{
    return stmt_has_exit_break(loop->loop_stmt.body, loop->loop_stmt.loop_label, 0);
}

int stmt_always_returns(ASTNode *stmt)
{
    if (!stmt)
    {
        return 0;
    }

    switch (stmt->kind)
    {
    case NODE_RETURN:
        return 1;

    case NODE_BLOCK:
        return block_always_returns(stmt);

    case NODE_IF:
        // Both branches must return for if to always return
        if (stmt->if_stmt.then_body && stmt->if_stmt.else_body)
        {
            return stmt_always_returns(stmt->if_stmt.then_body) &&
                   stmt_always_returns(stmt->if_stmt.else_body);
        }
        return 0;

    case NODE_MATCH:
    {
        if (!stmt->match_stmt.cases)
        {
            return 0;
        }

        int has_default = 0;
        ASTNode *case_node = stmt->match_stmt.cases;
        while (case_node)
        {
            if (case_node->kind == NODE_MATCH_CASE)
            {
                if (!stmt_always_returns(case_node->match_case.body))
                {
                    return 0;
                }
                if (case_node->match_case.is_default)
                {
                    has_default = 1;
                }
            }
            case_node = case_node->next;
        }

        return has_default;
    }

    case NODE_LOOP:
        // An infinite `loop` only leaves via `break` or `return`. With no
        // exit-break it can never fall through (it returns from within or
        // diverges), so it satisfies the always-returns requirement. This
        // mirrors the move analysis, which already treats a break-less
        // infinite loop as unreachable-after.
        return !stmt_loop_has_exit_break(stmt);

    default:
        return 0;
    }
}

int block_always_returns(ASTNode *block)
{
    if (!block || block->kind != NODE_BLOCK)
    {
        return 0;
    }

    ASTNode *stmt = block->block.statements;
    if (!stmt)
    {
        return 0;
    }

    // Walk all statements except the last
    while (stmt->next)
    {
        if (stmt_always_returns(stmt))
        {
            return 1;
        }
        stmt = stmt->next;
    }

    // The last statement is the implicit return value.
    // Check if it always returns (RETURN, IF with both branches, MATCH with default),
    // or if it's an expression (which serves as the function's return value).
    if (stmt_always_returns(stmt))
    {
        return 1;
    }
    if (stmt->kind == NODE_BLOCK)
    {
        return block_always_returns(stmt);
    }
    // Any expression type can serve as an implicit return value
    if (stmt->kind == NODE_EXPR_BINARY || stmt->kind == NODE_EXPR_UNARY ||
        stmt->kind == NODE_EXPR_LITERAL || stmt->kind == NODE_EXPR_VAR ||
        stmt->kind == NODE_EXPR_CALL || stmt->kind == NODE_EXPR_MEMBER ||
        stmt->kind == NODE_EXPR_INDEX || stmt->kind == NODE_EXPR_CAST ||
        stmt->kind == NODE_EXPR_SIZEOF || stmt->kind == NODE_EXPR_STRUCT_INIT ||
        stmt->kind == NODE_EXPR_ARRAY_LITERAL || stmt->kind == NODE_EXPR_TUPLE_LITERAL ||
        stmt->kind == NODE_EXPR_SLICE || stmt->kind == NODE_TERNARY || stmt->kind == NODE_MATCH)
    {
        return 1;
    }
    return 0;
}

void check_function(TypeChecker *tc, ASTNode *node, int depth)
{
    if (!node)
    {
        return;
    }
    misra_check_param_nesting(tc->pctx, node);
    // Mark arg types as used
    for (int i = 0; i < node->func.count; i++)
    {
        mark_type_as_used(tc, node->func.arg_types[i]);
    }

    // Mark return type as used
    mark_type_as_used(tc, node->func.ret_type_info);

    // Rule Zen 1.4: Reserved identifiers
    misra_check_reserved_identifier(tc->pctx, node->func.name, node->token);

    tc->current_func = node;
    tc_enter_scope(tc);

    int prev_unreachable = tc->is_unreachable;
    tc->is_unreachable = 0;
    tc->func_return_count = 0;

    MoveState *prev_move_state = tc->pctx->move_state;
    tc->pctx->move_state = move_state_create(NULL);

    for (int i = 0; i < node->func.count; i++)
    {
        if (node->func.param_names && node->func.param_names[i])
        {
            Type *param_type =
                (node->func.arg_types && node->func.arg_types[i]) ? node->func.arg_types[i] : NULL;

            misra_check_tuple_size(tc->pctx, param_type, node->token);
            misra_check_pointer_nesting(tc->pctx, param_type, node->token);
            misra_check_reserved_identifier(tc->pctx, node->func.param_names[i], node->token);
            tc_add_symbol(tc, node->func.param_names[i], param_type, node->token,
                          tc->pctx->config->misra_mode);
        }
    }

    if (node->func.ret_type_info)
    {
        misra_check_tuple_size(tc->pctx, node->func.ret_type_info, node->token);
        misra_check_pointer_nesting(tc->pctx, node->func.ret_type_info, node->token);

        // Lifetime Elision result was already computed in pre-pass for named functions.
        // For lambdas or if it somehow missed the pre-pass, ensure it's set.
        if (node->func.elide_from_idx == -1)
        {
            // Simple re-run if needed
        }
    }

    check_node(tc, node->func.body, depth + 1);

    // Control flow analysis: Check if non-void function always returns
    const char *ret_type = node->func.ret_type;
    int is_void = !ret_type || strcmp(ret_type, "void") == 0;

    // Special case: 'main' is allowed to fall off the end (C99 implicit return 0)
    int is_main = node->func.name && strcmp(node->func.name, "main") == 0;

    if (is_main && is_void)
    {
        warn_void_main(node->token);
    }

    if (!is_void && !is_main && node->func.body)
    {
        if (!block_always_returns(node->func.body))
        {
            char msg[MAX_SHORT_MSG_LEN];
            snprintf(msg, sizeof(msg), "Function '%s' may not return a value on all code paths",
                     node->func.name);

            const char *hints[] = {"Ensure all execution paths return a value",
                                   "Consider adding a default return at the end of the function",
                                   NULL};
            tc_ctrl_flow_error(tc, node->token, msg, hints);
        }
    }

    move_state_free(tc->pctx->move_state);
    tc->pctx->move_state = prev_move_state;

    // MISRA audits before leaving function scope
    if (tc->pctx->config->misra_mode && tc->pctx->current_scope)
    {
        for (int i = 0; i < node->func.count; i++)
        {
            if (node->func.param_names && node->func.param_names[i])
            {
                ZenSymbol *psym =
                    symbol_lookup_local(tc->pctx->current_scope, node->func.param_names[i]);
                if (psym)
                {
                    // Rule 2.7: Unused parameter
                    if (!psym->is_used)
                    {
                        misra_check_unused_param(tc->pctx, psym->name, psym->decl_token);
                    }
                    // Rule 8.13: Pointer to const
                    if (psym->type_info && psym->type_info->kind == TYPE_POINTER)
                    {
                        Type *inner = resolve_alias(psym->type_info->inner);
                        if (inner && !inner->is_const && !psym->type_info->is_const &&
                            !psym->is_written_to)
                        {
                            misra_check_const_ptr_param(tc->pctx, psym->name, psym->decl_token);
                        }
                    }
                }
            }
        }
    }

    // MISRA Rule 15.5: A function shall have a single point of exit at the end.
    if (tc->pctx->config->misra_mode && tc->func_return_count > 1)
    {
        char msg[MAX_ERROR_MSG_LEN];
        snprintf(msg, sizeof(msg),
                 "MISRA Rule 15.5: function '%s' has %d return points (must have 1)",
                 node->func.name ? node->func.name : "anonymous", tc->func_return_count);
        tc_error(tc, node->token, msg);
    }

    tc->is_unreachable = prev_unreachable;
    tc_exit_scope(tc);
    tc->current_func = NULL;
}

void tc_check_trait(TypeChecker *tc, ASTNode *node, int depth)
{
    ASTNode *method = node->trait.methods;
    while (method)
    {
        check_node(tc, method, depth + 1);
        method = method->next;
    }
}

void tc_check_impl(TypeChecker *tc, ASTNode *node, int depth)
{
    // Skip templates
    if (node->impl.struct_name && strchr(node->impl.struct_name, '<'))
    {
        return;
    }

    ASTNode *method = node->impl.methods;
    while (method)
    {
        check_node(tc, method, depth + 1);
        method = method->next;
    }
}

void tc_check_impl_trait(TypeChecker *tc, ASTNode *node, int depth)
{
    // Skip templates
    if (node->impl_trait.target_type && strchr(node->impl_trait.target_type, '<'))
    {
        return;
    }

    ASTNode *method = node->impl_trait.methods;
    while (method)
    {
        check_node(tc, method, depth + 1);
        method = method->next;
    }
}

void check_loop_passes(TypeChecker *tc, ASTNode *node, int depth)
{
    if (!node)
    {
        return;
    }
    RECURSION_GUARD_TOKEN(tc->pctx, node->token, );
    if (depth > 1024)
    {
        tc_error(tc, node->token, "Expression too deep");
        RECURSION_EXIT(tc->pctx);
        return;
    }

    MoveState *prev_break = tc->loop_break_state;
    MoveState *prev_cont = tc->loop_continue_state;
    tc->loop_break_state = NULL;
    tc->loop_continue_state = NULL;

    MoveState *initial_state = tc->pctx->move_state;
    MoveState *loop_start = initial_state ? move_state_clone(initial_state) : NULL;
    MoveState *outer_start_state = tc->loop_start_state;
    tc->loop_start_state = loop_start;

    int outer_in_pass2 = tc->in_loop_pass2;
    tc->in_loop_pass2 = 0;

    int outer_break_count = tc->loop_break_count;
    tc->loop_break_count = 0;

    int initial_unreachable = tc->is_unreachable;

    // Pass 1: standard typecheck and move check
    tc->is_unreachable = 0; // The loop start is assumed reachable if we got here

    switch (node->kind)
    {
    case NODE_WHILE:
    {
        misra_check_compound_body(tc->pctx, node->while_stmt.body, "while");
        int old_stmt_ctx = tc->is_stmt_context;
        tc->is_stmt_context = 0;
        check_node(tc, node->while_stmt.condition, depth + 1);
        tc->is_stmt_context = old_stmt_ctx;

        if (node->while_stmt.condition && node->while_stmt.condition->type_info)
        {
            Type *cond_type = resolve_alias(node->while_stmt.condition->type_info);
            if (tc->pctx->config->misra_mode)
            {
                if (cond_type->kind != TYPE_BOOL)
                {
                    misra_check_condition_boolean(tc->pctx, node->while_stmt.condition->type_info,
                                                  node->while_stmt.condition->token);
                }
                int inv;
                if (is_expression_invariant(tc, node->while_stmt.condition, &inv))
                {
                    misra_check_invariant_condition(tc->pctx, node->while_stmt.condition->token);
                }
            }
            else if (cond_type->kind != TYPE_BOOL && !is_integer_type(cond_type) &&
                     cond_type->kind != TYPE_POINTER && cond_type->kind != TYPE_STRING)
            {
                const char *hints[] = {"While conditions must be boolean, integer, or pointer",
                                       NULL};
                tc_error_with_hints(tc, node->while_stmt.condition->token,
                                    "Condition must be a truthy type", hints);
            }
        }
        check_node(tc, node->while_stmt.body, depth + 1);
        break;
    }

    case NODE_FOR:
        misra_check_compound_body(tc->pctx, node->for_stmt.body, "for");
        tc_enter_scope(tc); // For loop init variable is scoped
        check_node(tc, node->for_stmt.init, depth + 1);

        // Loop start is conceptually here for FOR
        if (loop_start)
        {
            move_state_free(loop_start);
        }
        loop_start = tc->pctx->move_state ? move_state_clone(tc->pctx->move_state) : NULL;
        tc->loop_start_state = loop_start;

        check_node(tc, node->for_stmt.condition, depth + 1);
        if (node->for_stmt.condition && node->for_stmt.condition->type_info)
        {
            Type *cond_type = resolve_alias(node->for_stmt.condition->type_info);
            if (tc->pctx->config->misra_mode)
            {
                if (cond_type->kind != TYPE_BOOL)
                {
                    misra_check_condition_boolean(tc->pctx, node->for_stmt.condition->type_info,
                                                  node->for_stmt.condition->token);
                }
                int inv;
                if (is_expression_invariant(tc, node->for_stmt.condition, &inv))
                {
                    misra_check_invariant_condition(tc->pctx, node->for_stmt.condition->token);
                }
            }
            else if (cond_type->kind != TYPE_BOOL && !is_integer_type(cond_type) &&
                     cond_type->kind != TYPE_POINTER && cond_type->kind != TYPE_STRING)
            {
                const char *hints[] = {"For conditions must be boolean, integer, or pointer", NULL};
                tc_error_with_hints(tc, node->for_stmt.condition->token,
                                    "Condition must be a truthy type", hints);
            }
        }
        check_node(tc, node->for_stmt.body, depth + 1);
        check_node(tc, node->for_stmt.step, depth + 1); // step happens after body

        if (tc->pctx->config->misra_mode && node->for_stmt.step)
        {
            if (node->for_stmt.step->kind == NODE_EXPR_BINARY)
            {
                const char *step_op = node->for_stmt.step->binary.op;
                if (strstr(step_op, "=") && node->for_stmt.step->binary.left->type_info)
                {
                    misra_check_loop_counter_float(tc->pctx,
                                                   node->for_stmt.step->binary.left->type_info,
                                                   node->for_stmt.step->token);
                }
            }
        }
        break;

    case NODE_FOR_RANGE:
        check_node(tc, node->for_range.start, depth + 1);
        check_node(tc, node->for_range.end, depth + 1);

        // Loop start conceptually here
        if (loop_start)
        {
            move_state_free(loop_start);
        }
        loop_start = tc->pctx->move_state ? move_state_clone(tc->pctx->move_state) : NULL;
        tc->loop_start_state = loop_start;

        check_node(tc, node->for_range.body, depth + 1);
        break;

    case NODE_LOOP:
        check_node(tc, node->loop_stmt.body, depth + 1);
        break;

    default:
        break;
    }

    tc->loop_break_count = 0; // Reset for pass 2 (avoid double counts)

    // Determine next iter state based on continue and fallthrough
    MoveState *fallthrough_state = tc->pctx->move_state;
    int fallthrough_unreachable = tc->is_unreachable;

    MoveState *next_iter_state = NULL;
    if (!fallthrough_unreachable && fallthrough_state)
    {
        move_state_merge_into(&next_iter_state, fallthrough_state);
    }
    if (tc->loop_continue_state)
    {
        move_state_merge_into(&next_iter_state, tc->loop_continue_state);
    }

    // Pass 2: Re-run with next_iter_state to catch use-after-move across iterations
    if (next_iter_state)
    {
        int prev_move_checks_only = tc->move_checks_only;
        tc->move_checks_only = 1; // suppress type errors
        tc->in_loop_pass2 = 1;

        tc->pctx->move_state = move_state_clone(next_iter_state);
        tc->is_unreachable = 0;

        tc->loop_break_state = NULL;
        tc->loop_continue_state = NULL;

        // Re-run appropriate parts
        switch (node->kind)
        {
        case NODE_WHILE:
            check_node(tc, node->while_stmt.condition, depth + 1);
            check_node(tc, node->while_stmt.body, depth + 1);
            break;
        case NODE_FOR:
            check_node(tc, node->for_stmt.condition, depth + 1);
            check_node(tc, node->for_stmt.body, depth + 1);
            check_node(tc, node->for_stmt.step, depth + 1);
            break;
        case NODE_FOR_RANGE:
            check_node(tc, node->for_range.body, depth + 1);
            break;
        case NODE_LOOP:
            check_node(tc, node->loop_stmt.body, depth + 1);
            break;
        default:
            break;
        }

        if (tc->pctx->move_state)
        {
            move_state_free(tc->pctx->move_state);
        }
        if (tc->loop_break_state)
        {
            move_state_free(tc->loop_break_state);
        }
        if (tc->loop_continue_state)
        {
            move_state_free(tc->loop_continue_state);
        }

        tc->move_checks_only = prev_move_checks_only;
    }

    // Compute final move state exiting the loop
    MoveState *final_state = NULL;
    // Loops can exit via condition falsification (next_iter_state) or breaks
    if (next_iter_state)
    {
        // Assume infinite loops (like NODE_LOOP) don't exit naturally unless broken,
        // but for safety we'll merge next_iter_state for all, treating condition as maybe false.
        if (node->kind != NODE_LOOP)
        {
            move_state_merge_into(&final_state, next_iter_state);
        }
    }
    if (tc->loop_break_state)
    {
        move_state_merge_into(&final_state, tc->loop_break_state);
    }

    // Cleanup Pass 1 states
    if (tc->loop_break_state)
    {
        move_state_free(tc->loop_break_state);
    }
    if (tc->loop_continue_state)
    {
        move_state_free(tc->loop_continue_state);
    }

    tc->loop_break_count = outer_break_count;
    if (next_iter_state)
    {
        move_state_free(next_iter_state);
    }
    if (loop_start)
    {
        move_state_free(loop_start);
    }

    // Restore outer context
    if (node->kind == NODE_FOR)
    {
        tc_exit_scope(tc);
    }

    // If the loop is an infinite loop and has no breaks, it is unconditionally unreachable after.
    if (node->kind == NODE_LOOP && !final_state)
    {
        tc->is_unreachable = 1;
    }
    else if (final_state)
    {
        tc->is_unreachable = 0;
    }
    else
    {
        tc->is_unreachable = initial_unreachable;
    }

    if (tc->pctx->move_state)
    {
        move_state_free(tc->pctx->move_state);
    }
    tc->pctx->move_state = final_state ? final_state : initial_state;

    tc->loop_break_state = prev_break;
    tc->loop_continue_state = prev_cont;
    tc->loop_start_state = outer_start_state;
    tc->in_loop_pass2 = outer_in_pass2;
    RECURSION_EXIT(tc->pctx);
}

// MAIN DISPATCH: check_node