Ctrl K
ComunidadGuías…

src/analysis/typecheck_expr.c

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

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

void check_move_for_rvalue(TypeChecker *tc, ASTNode *rvalue)
{
    if (!rvalue || !rvalue->type_info)
    {
        return;
    }

    if (is_type_copy(tc->pctx, rvalue->type_info))
    {
        return;
    }

    if (rvalue->kind == NODE_EXPR_VAR)
    {
        ZenSymbol *sym = tc_lookup(tc, rvalue->var_ref.name);
        if (sym)
        {
            mark_symbol_moved(tc->pctx, sym, rvalue);
        }
    }
    else if (rvalue->kind == NODE_EXPR_UNARY && strcmp(rvalue->unary.op, "*") == 0)
    {
        const char *hints[] = {"This type owns resources and cannot be implicitly copied",
                               "Consider borrowing value via references or implementing Copy",
                               NULL};
        tc_move_error_with_hints(tc, rvalue->token, "Cannot move out of a borrowed reference",
                                 hints);
    }
    else if (rvalue->kind == NODE_EXPR_MEMBER)
    {
        // Now allowed, but will be tracked by path
        mark_symbol_moved(tc->pctx, NULL, rvalue);
    }
    else if (rvalue->kind == NODE_EXPR_INDEX)
    {
        const char *hints[] = {"Cannot move an element out of an array or slice.", NULL};
        tc_move_error_with_hints(tc, rvalue->token, "Cannot move out of an index expression",
                                 hints);
    }
}

// Follows `alias A = B;` by name until it reaches a name that is not an alias
// (the parser stores the target already mangled: `Handle<K>` -> `Handle__K`).
// Opaque aliases stop the chain: they must stay distinct types.
static const char *resolve_alias_name(TypeChecker *tc, const char *name)
{
    for (int depth = 0; name && depth < 16; depth++)
    {
        TypeAlias *ta = find_type_alias_node(tc->pctx, name);
        if (ta && ta->is_opaque)
        {
            return name;
        }
        const char *next = find_type_alias(tc->pctx, name);
        if (!next || strcmp(next, name) == 0)
        {
            return name;
        }
        name = next;
    }
    return name;
}

// Canonical spelling of a type for alias-insensitive comparison: aliases
// resolved by name, pointers spelled outside (`alias P = T*;` makes `P*` and
// `T**` the same). Returns an allocated string, or NULL when there is nothing
// to resolve.
static char *canonical_type_name(TypeChecker *tc, Type *t, int depth)
{
    if (!t || depth > 16)
    {
        return NULL;
    }
    t = resolve_alias(t);
    if (t->kind == TYPE_POINTER)
    {
        char *inner = canonical_type_name(tc, t->inner, depth + 1);
        if (!inner)
        {
            return NULL;
        }
        size_t len = strlen(inner);
        char *out = xmalloc(len + 2);
        memcpy(out, inner, len);
        out[len] = '*';
        out[len + 1] = 0;
        zfree(inner);
        return out;
    }
    if (!t->name)
    {
        return NULL;
    }
    const char *name = resolve_alias_name(tc, t->name);
    // Template instantiation spells a pointer argument as `<Name>Ptr`
    // (`Vec<DirEntry*>` substitutes T by `DirEntryPtr`): read it back as
    // `<Name>*` when `<Name>Ptr` is not a type of its own.
    size_t name_len = strlen(name);
    if (name_len > 3 && strcmp(name + name_len - 3, "Ptr") == 0 &&
        !find_struct_def(tc->pctx, name) && !find_type_alias(tc->pctx, name))
    {
        char *base_name = xstrdup(name);
        base_name[name_len - 3] = 0;
        Type base = {0};
        base.kind = TYPE_STRUCT;
        base.name = base_name;
        char *inner = canonical_type_name(tc, &base, depth + 1);
        zfree(base_name);
        if (inner)
        {
            size_t len = strlen(inner);
            char *out = xmalloc(len + 2);
            memcpy(out, inner, len);
            out[len] = '*';
            out[len + 1] = 0;
            zfree(inner);
            return out;
        }
    }
    // Drop spaces so "DirEntry *" and "DirEntry*" compare equal.
    char *out = xmalloc(strlen(name) + 1);
    char *w = out;
    for (const char *r = name; *r; r++)
    {
        if (*r != ' ')
        {
            *w++ = *r;
        }
    }
    *w = 0;
    return out;
}

Type *resolve_alias(Type *t)
{
    while (t && t->kind == TYPE_ALIAS && t->inner)
    {
        t = t->inner;
    }
    return t;
}

void check_expr_unary(TypeChecker *tc, ASTNode *node, int depth)
{
    check_node(tc, node->unary.operand, depth + 1);

    Type *operand_type = node->unary.operand->type_info;
    const char *op = node->unary.op;

    if (!operand_type)
    {
        return;
    }

    // Logical NOT: !
    if (strcmp(op, "!") == 0)
    {
        node->type_info = type_new(TYPE_BOOL);
        return;
    }

    // Numeric negation: -
    if (strcmp(op, "-") == 0)
    {
        if (!is_integer_type(operand_type) && !is_float_type(operand_type))
        {
            const char *hints[] = {"Negation requires a numeric operand", NULL};
            tc_error_with_hints(tc, node->token, "Cannot negate non-numeric type", hints);
        }
        else
        {
            node->type_info = operand_type;
        }
        return;
    }

    // Dereference: *
    if (strcmp(op, "*") == 0)
    {
        if (operand_type->kind == TYPE_UNKNOWN)
        {
            node->type_info = type_new(TYPE_UNKNOWN);
            return;
        }

        Type *resolved = resolve_alias(operand_type);
        if (resolved->kind != TYPE_POINTER && resolved->kind != TYPE_STRING)
        {
            const char *hints[] = {"Only pointers can be dereferenced", NULL};
            tc_error_with_hints(tc, node->token, "Cannot dereference non-pointer type", hints);
        }
        else if (resolved->kind == TYPE_STRING)
        {
            node->type_info = type_new(TYPE_CHAR);
        }
        else if (resolved->inner)
        {
            node->type_info = resolved->inner;
            CompilerConfig *cfg = &tc->pctx->compiler->config;
            if (cfg->misra_mode)
            {
                misra_check_file_dereference(tc->pctx, operand_type, node->token);
            }
        }
        return;
    }

    // Bitwise NOT: ~
    if (strcmp(op, "~") == 0)
    {
        if (!is_integer_type(operand_type))
        {
            const char *hints[] = {"Bitwise NOT requires an integer operand", NULL};
            tc_error_with_hints(tc, node->token, "Cannot apply ~ to non-integer type", hints);
        }
        else
        {
            misra_check_bitwise_operand(tc->pctx, node->unary.operand->type_info, node->token);
            node->type_info = operand_type;
        }
        return;
    }

    if (strcmp(op, "++") == 0 || strcmp(op, "--") == 0 || strcmp(op, "_post++") == 0 ||
        strcmp(op, "_post--") == 0)
    {
        misra_check_inc_dec_result_used(tc->pctx, node->token);
        // Track as a write
        if (node->unary.operand->kind == NODE_EXPR_VAR)
        {
            ZenSymbol *s = tc_lookup(tc, node->unary.operand->var_ref.name);
            if (s)
            {
                s->is_written_to = 1;
            }
        }
        else if (node->unary.operand->kind == NODE_EXPR_UNARY &&
                 strcmp(node->unary.operand->unary.op, "*") == 0)
        {
            ASTNode *inner = node->unary.operand->unary.operand;
            if (inner->kind == NODE_EXPR_VAR)
            {
                ZenSymbol *s = tc_lookup(tc, inner->var_ref.name);
                if (s)
                {
                    s->is_written_to = 1;
                }
            }
        }
        if (!is_integer_type(operand_type) && !is_float_type(operand_type) &&
            operand_type->kind != TYPE_POINTER)
        {
            tc_error(tc, node->token, "Increment/decrement requires a numeric or pointer operand");
        }
        node->type_info = operand_type;
        return;
    }

    // Address-of: &
    if (strcmp(op, "&") == 0 || strcmp(op, "&_rval") == 0)
    {
        node->type_info = type_new_ptr(operand_type);
        // Record provenance depth for escape analysis
        if (node->unary.operand->kind == NODE_EXPR_VAR)
        {
            ZenSymbol *sym = tc_lookup(tc, node->unary.operand->var_ref.name);
            if (sym)
            {
                node->type_info->lifetime_depth = sym->scope_depth;
            }
        }
        else if (strcmp(op, "&_rval") == 0)
        {
            // R-values are temporaries, their life is the current code block
            if (node->unary.operand->type_info)
            {
                node->type_info->lifetime_depth = node->unary.operand->type_info->lifetime_depth;
            }
            else
            {
                node->type_info->lifetime_depth = tc->current_depth;
            }
        }
        else
        {
            // For complex targets (e.g. &array[0]), inherit from the operand if it has a lifetime
            if (operand_type)
            {
                node->type_info->lifetime_depth = operand_type->lifetime_depth;
            }
        }
        return;
    }
}

void check_expr_binary(TypeChecker *tc, ASTNode *node, int depth)
{
    const char *op = node->binary.op;
    Type *contextual_type = node->type_info;

    if (strcmp(op, "=") == 0 ||
        (strlen(op) > 1 && op[strlen(op) - 1] == '=' && strcmp(op, "==") != 0 &&
         strcmp(op, "!=") != 0 && strcmp(op, "<=") != 0 && strcmp(op, ">=") != 0))
    {
        if (!tc->is_stmt_context)
        {
            misra_check_assignment_result_used(tc->pctx, node->token);
        }

        int old_is_assign_lhs = tc->is_assign_lhs;
        int old_stmt_ctx = tc->is_stmt_context;
        tc->is_assign_lhs = 1;
        tc->is_stmt_context = 0;
        check_node(tc, node->binary.left, depth + 1);
        tc->is_assign_lhs = old_is_assign_lhs;

        if (node->binary.left->type_info && node->binary.right->kind == NODE_LAMBDA)
        {
            node->binary.right->type_info = node->binary.left->type_info;
        }
        check_node(tc, node->binary.right, depth + 1);
        tc->is_stmt_context = old_stmt_ctx;

        // Rule 13.2: Side effect collision detection for assignments
        if (tc->pctx->config->misra_mode)
        {
            SymbolSet l_reads = {0}, l_writes = {0};
            SymbolSet r_reads = {0}, r_writes = {0};
            collect_symbols(node->binary.left, &l_reads, &l_writes);
            collect_symbols(node->binary.right, &r_reads, &r_writes);

            // Treat LHS of assignment as a write
            if (node->binary.left->kind == NODE_EXPR_VAR)
            {
                int already_in = 0;
                for (int k = 0; k < l_writes.count; k++)
                {
                    if (l_writes.syms[k] == node->binary.left->var_ref.symbol)
                    {
                        already_in = 1;
                        break;
                    }
                }
                if (!already_in && l_writes.count < 32)
                {
                    l_writes.syms[l_writes.count++] = node->binary.left->var_ref.symbol;
                }
            }

            // Double write collision: i = i++
            int match = 0;
            for (int i = 0; i < l_writes.count; i++)
            {
                ZenSymbol *s = l_writes.syms[i];
                if (!s)
                {
                    continue;
                }
                for (int j = 0; j < r_writes.count; j++)
                {
                    if (s == r_writes.syms[j])
                    {
                        tc_error(tc, node->token,
                                 "MISRA Rule 13.2: symbol modified multiple times in assignment");
                        match = 1;
                        break;
                    }
                }
                if (match)
                {
                    break;
                }

                // Also check if LHS write conflicts with RHS reads (if RHS also writes it)
                // e.g. i = i++ is already covered by double write.
                // But what about i = i + (i++)?
                // r_writes={i}, r_reads={i, i}.
            }
        }
    }
    else
    {
        int old_stmt_ctx = tc->is_stmt_context;
        tc->is_stmt_context = 0;
        check_node(tc, node->binary.left, depth + 1);
        check_node(tc, node->binary.right, depth + 1);
        tc->is_stmt_context = old_stmt_ctx;

        // Rule 13.2: Side effect collision detection for binary operators
        if (strcmp(op, "&&") != 0 && strcmp(op, "||") != 0 && strcmp(op, ",") != 0)
        {
            check_side_effect_collision(tc, node->binary.left, node->binary.right, node->token);
        }
        else if (tc->pctx->config->misra_mode && (strcmp(op, "&&") == 0 || strcmp(op, "||") == 0))
        {
            // Rule 13.5: The RHS of && or || shall not contain persistent side effects
            SymbolSet r_reads = {0}, r_writes = {0};
            collect_symbols(node->binary.right, &r_reads, &r_writes);
            if (r_writes.count > 0)
            {
                tc_error(tc, node->binary.right->token,
                         "MISRA Rule 13.5: persistent side effect in logical RHS");
            }
        }
    }

    // Rule 19.1: Check for self-assignment (x = x)
    if (tc->pctx->config->misra_mode && strcmp(op, "=") == 0)
    {
        misra_check_assignment_overlap(tc->pctx, node->binary.left, node->binary.right,
                                       node->token);
    }

    // Malformed input can produce binary expressions with NULL children
    if (!node->binary.left || !node->binary.right)
    {
        return;
    }
    Type *left_type = node->binary.left->type_info;
    Type *right_type = node->binary.right->type_info;

    // Assignment Logic for Moves (and type compatibility)
    if (strcmp(op, "=") == 0)
    {
        // Check type compatibility for assignment
        if (left_type && right_type)
        {
            apply_implicit_struct_pointer_conversions(tc, &node->binary.right, left_type);
            right_type = node->binary.right->type_info;
            check_type_compatibility(tc, left_type, right_type, node->binary.right->token,
                                     node->binary.right, 0);

            // Mark LHS as written to
            if (node->binary.left->kind == NODE_EXPR_VAR)
            {
                ZenSymbol *s = tc_lookup(tc, node->binary.left->var_ref.name);
                if (s)
                {
                    s->is_written_to = 1;
                }
            }
            else if (node->binary.left->kind == NODE_EXPR_UNARY &&
                     strcmp(node->binary.left->unary.op, "*") == 0)
            {
                ASTNode *inner = node->binary.left->unary.operand;
                if (inner->kind == NODE_EXPR_VAR)
                {
                    ZenSymbol *s = tc_lookup(tc, inner->var_ref.name);
                    if (s)
                    {
                        s->is_written_to = 1;
                    }
                }
            }
            // Also handle array indexing as write
            else if (node->binary.left->kind == NODE_EXPR_INDEX)
            {
                ASTNode *arr = node->binary.left->index.array;
                if (arr->kind == NODE_EXPR_VAR)
                {
                    ZenSymbol *s = tc_lookup(tc, arr->var_ref.name);
                    if (s)
                    {
                        s->is_written_to = 1;
                    }
                }
            }
            // Also handle member access as write
            else if (node->binary.left->kind == NODE_EXPR_MEMBER)
            {
                ASTNode *target = node->binary.left->member.target;
                // Follow the member chain to the base variable
                while (target && target->kind == NODE_EXPR_MEMBER)
                {
                    target = target->member.target;
                }
                if (target && target->kind == NODE_EXPR_VAR)
                {
                    ZenSymbol *s = tc_lookup(tc, target->var_ref.name);
                    if (s)
                    {
                        s->is_written_to = 1;
                    }
                }
            }
        }

        // If RHS is moving a non-copy value, check validity and mark moved
        check_move_for_rvalue(tc, node->binary.right);

        // LHS is being (re-)initialized, so it becomes Valid.
        if (node->binary.left->kind == NODE_EXPR_VAR)
        {
            ZenSymbol *lhs_sym = tc_lookup(tc, node->binary.left->var_ref.name);
            if (lhs_sym)
            {
                if (tc->pctx->config->misra_mode && node->binary.left &&
                    node->binary.left->kind == NODE_EXPR_VAR)
                {
                    misra_check_param_modified(tc->current_func, node->binary.left,
                                               node->binary.left->token);
                }
                if (lhs_sym->is_immutable)
                {
                    tc_error(tc, node->binary.left->token, "Cannot assign to immutable variable");
                }

                if (tc->pctx->config->misra_mode)
                {
                    misra_check_pointer_conversion(tc->pctx, left_type, right_type, node->token);
                }
                mark_symbol_valid(tc->pctx, lhs_sym, node->binary.left);
            }
        }

        // Result type is same as LHS
        node->type_info = left_type;
        return;
    }

    // Arithmetic operators: +, -, *, /, %
    if (strcmp(op, "+") == 0 || strcmp(op, "-") == 0 || strcmp(op, "*") == 0 ||
        strcmp(op, "/") == 0 || strcmp(op, "%") == 0)
    {
        // Division by zero detection for / and %
        if ((strcmp(op, "/") == 0 || strcmp(op, "%") == 0) && node->binary.right &&
            node->binary.right->kind == NODE_EXPR_LITERAL)
        {
            LiteralKind kind = node->binary.right->literal.kind;
            if (kind == LITERAL_INT && node->binary.right->literal.int_val == 0)
            {
                const char *hints[] = {"Division by zero is undefined behavior", NULL};
                tc_error_with_hints(tc, node->binary.right->token, "Division by zero detected",
                                    hints);
            }
            else if (kind == LITERAL_FLOAT && node->binary.right->literal.float_val >= -0.0 &&
                     node->binary.right->literal.float_val <= 0.0)
            {
                const char *hints[] = {"Division by zero results in infinity or NaN", NULL};
                tc_error_with_hints(tc, node->binary.right->token, "Division by zero detected",
                                    hints);
            }
        }

        if (left_type && right_type)
        {
            Type *lhs_resolved = resolve_alias(left_type);
            Type *rhs_resolved = resolve_alias(right_type);

            // Pointer Arithmetic
            if (lhs_resolved->kind == TYPE_POINTER || lhs_resolved->kind == TYPE_STRING)
            {
                misra_check_pointer_arithmetic(tc->pctx, lhs_resolved, node->token);

                // Ptr - Ptr -> isize
                if (strcmp(op, "-") == 0 &&
                    (rhs_resolved->kind == TYPE_POINTER || rhs_resolved->kind == TYPE_STRING))
                {
                    node->type_info = type_new(TYPE_ISIZE);
                    return;
                }
                // Ptr + Int -> Ptr
                // Ptr - Int -> Ptr
                if ((strcmp(op, "+") == 0 || strcmp(op, "-") == 0) && is_integer_type(rhs_resolved))
                {
                    node->type_info = left_type;
                    return;
                }
            }
            // Int + Ptr -> Ptr
            if (strcmp(op, "+") == 0 && is_integer_type(lhs_resolved) &&
                (rhs_resolved->kind == TYPE_POINTER || rhs_resolved->kind == TYPE_STRING))
            {
                misra_check_pointer_arithmetic(tc->pctx, rhs_resolved, node->token);
                node->type_info = right_type;
                return;
            }

            int left_numeric = is_integer_type(left_type) || is_float_type(left_type) ||
                               left_type->kind == TYPE_VECTOR;
            int right_numeric = is_integer_type(right_type) || is_float_type(right_type) ||
                                right_type->kind == TYPE_VECTOR;

            if (!left_numeric || !right_numeric)
            {
                if (left_type->kind == TYPE_UNKNOWN || right_type->kind == TYPE_UNKNOWN)
                {
                    node->type_info = type_new(TYPE_UNKNOWN);
                    return;
                }

                char msg[MAX_SHORT_MSG_LEN];
                snprintf(msg, sizeof(msg), "Operator '%s' requires numeric operands", op);
                const char *hints[] = {
                    "Arithmetic operators can only be used with integer, float, or vector types",
                    NULL};
                tc_error_with_hints(tc, node->token, msg, hints);
            }
            else if (left_type->kind == TYPE_VECTOR || right_type->kind == TYPE_VECTOR)
            {
                if (left_type->kind != right_type->kind || !type_eq(left_type, right_type))
                {
                    tc_error(tc, node->token,
                             "Vector operation requires operands of same vector type");
                }
                node->type_info = left_type;
                return;
            }
            else
            {
                // Rule 10.4: Balancing
                misra_check_binary_op_essential_types(tc->pctx, node->binary.left,
                                                      node->binary.right, node->token);

                // MISRA Rule 12.4: Evaluation of constant expressions shall not lead to unsigned
                // wrap Use the contextually pushed down type (stored in node->type_info before this
                // function) or fall back to the inferred operand type.
                Type *target_type = contextual_type ? contextual_type : left_type;

                if (tc->pctx->config->misra_mode && target_type && is_integer_type(target_type))
                {
                    long long lval, rval;
                    if (eval_const_int_expr(node->binary.left, tc->pctx, &lval) &&
                        eval_const_int_expr(node->binary.right, tc->pctx, &rval))
                    {
                        long long res = 0;
                        if (strcmp(op, "+") == 0)
                        {
                            res = lval + rval;
                        }
                        else if (strcmp(op, "-") == 0)
                        {
                            res = lval - rval;
                        }
                        else if (strcmp(op, "*") == 0)
                        {
                            res = lval * rval;
                        }

                        if (strcmp(op, "+") == 0 || strcmp(op, "-") == 0 || strcmp(op, "*") == 0)
                        {
                            misra_check_unsigned_wrap(tc->pctx, op, lval, rval, res, target_type,
                                                      node->token);
                        }
                    }
                }

                // Rule 10.2: Character arithmetic
                misra_check_char_arithmetic(tc->pctx, left_type, right_type, op, node->token);

                // Result type: Only infer if not already set by context
                if (!node->type_info)
                {
                    if (is_float_type(left_type) || is_float_type(right_type))
                    {
                        node->type_info = type_new(TYPE_F64);
                    }
                    else
                    {
                        node->type_info = left_type;
                    }
                }
            }
        }
        return;
    }

    // Comparison operators: ==, !=, <, >, <=, >=
    if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0 || strcmp(op, "<") == 0 ||
        strcmp(op, ">") == 0 || strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0)
    {
        // Result is always bool
        node->type_info = type_new(TYPE_BOOL);

        // Operands should be comparable
        if (left_type && right_type)
        {
            // Rule 10.4: Balancing
            misra_check_binary_op_essential_types(tc->pctx, node->binary.left, node->binary.right,
                                                  node->token);

            // Rule 18.3: Relational operators on pointers (>, <, >=, <=)
            if (tc->pctx->config->misra_mode && (strcmp(op, "<") == 0 || strcmp(op, ">") == 0 ||
                                                 strcmp(op, "<=") == 0 || strcmp(op, ">=") == 0))
            {
                Type *l_resolved = resolve_alias(left_type);
                Type *r_resolved = resolve_alias(right_type);
                if (l_resolved->kind == TYPE_POINTER || r_resolved->kind == TYPE_POINTER)
                {
                    tc_error(
                        tc, node->token,
                        "MISRA Rule 18.3: relational operator shall not be applied to pointers");
                }
            }

            misra_check_string_compare(tc->pctx, left_type, right_type, node->token);

            if (!type_eq(left_type, right_type))
            {
                // Allow comparison between numeric types
                int left_numeric = is_integer_type(left_type) || is_float_type(left_type);
                int right_numeric = is_integer_type(right_type) || is_float_type(right_type);

                if (!left_numeric || !right_numeric)
                {
                    if ((left_type && left_type->kind == TYPE_UNKNOWN) ||
                        (right_type && right_type->kind == TYPE_UNKNOWN))
                    {
                        node->type_info = type_new(TYPE_BOOL);
                        return;
                    }
                    char msg[MAX_SHORT_MSG_LEN];
                    snprintf(msg, sizeof(msg), "Cannot compare '%s' with incompatible types", op);
                    const char *hints[] = {"Ensure both operands have the same or compatible types",
                                           NULL};
                    tc_error_with_hints(tc, node->token, msg, hints);
                }
            }
        }
        return;
    }

    // Logical operators: &&, ||
    if (strcmp(op, "&&") == 0 || strcmp(op, "||") == 0)
    {
        node->type_info = type_new(TYPE_BOOL);
        // Could validate that operands are boolean-like, but C is lax here
        return;
    }

    // Bitwise operators: &, |, ^, <<, >>
    if (strcmp(op, "&") == 0 || strcmp(op, "|") == 0 || strcmp(op, "^") == 0 ||
        strcmp(op, "<<") == 0 || strcmp(op, ">>") == 0)
    {
        // MISRA Rule 12.2: Shift amount validation for << and >>
        if (tc->pctx->config->misra_mode && (strcmp(op, "<<") == 0 || strcmp(op, ">>") == 0))
        {
            long long shift_amt;
            if (eval_const_int_expr(node->binary.right, tc->pctx, &shift_amt))
            {
                int width = integer_type_width(left_type);
                misra_check_shift_amount(tc->pctx, shift_amt, width, node->token);
            }
        }
        else if ((strcmp(op, "<<") == 0 || strcmp(op, ">>") == 0) && node->binary.right &&
                 node->binary.right->kind == NODE_EXPR_LITERAL &&
                 node->binary.right->literal.kind == LITERAL_INT)
        {
            // Legacy/Non-MISRA warnings
            unsigned long long shift_amt = node->binary.right->literal.int_val;
            if (shift_amt >= 64)
            {
                const char *hints[] = {"Shift amount exceeds bit width, result is undefined", NULL};
                tc_error_with_hints(tc, node->binary.right->token, "Shift amount too large", hints);
            }
            else if (shift_amt >= 32 && left_type &&
                     (left_type->kind == TYPE_INT || left_type->kind == TYPE_UINT ||
                      left_type->kind == TYPE_I32 || left_type->kind == TYPE_U32 ||
                      left_type->kind == TYPE_C_INT || left_type->kind == TYPE_C_UINT))
            {
                const char *hints[] = {
                    "Shift amount >= 32 is undefined behavior for 32-bit integers", NULL};
                tc_error_with_hints(tc, node->binary.right->token,
                                    "Shift amount exceeds 32-bit type width", hints);
            }
        }

        if (left_type && right_type)
        {
            if ((!is_integer_type(left_type) && left_type->kind != TYPE_VECTOR) ||
                (!is_integer_type(right_type) && right_type->kind != TYPE_VECTOR))
            {
                char msg[MAX_SHORT_MSG_LEN];
                snprintf(msg, sizeof(msg),
                         "Bitwise operator '%s' requires integer or vector operands", op);
                const char *hints[] = {"Bitwise operators only work on integer or vector types",
                                       NULL};
                tc_error_with_hints(tc, node->token, msg, hints);
            }
            else if (left_type->kind == TYPE_VECTOR || right_type->kind == TYPE_VECTOR)
            {
                if (left_type->kind != right_type->kind || !type_eq(left_type, right_type))
                {
                    tc_error(tc, node->token, "Vector bitwise operation requires same vector type");
                }
                node->type_info = left_type;
            }
            else
            {
                if (tc->pctx->config->misra_mode)
                {
                    misra_check_bitwise_operand(tc->pctx, left_type, node->token);
                    misra_check_bitwise_operand(tc->pctx, right_type, node->token);
                    // Rule 10.4: Balancing for &, |, ^
                    if (strcmp(op, "&") == 0 || strcmp(op, "|") == 0 || strcmp(op, "^") == 0)
                    {
                        misra_check_binary_op_essential_types(tc->pctx, node->binary.left,
                                                              node->binary.right, node->token);
                    }
                }
                node->type_info = left_type;
            }
        }
        return;
    }
}

void extract_base_name(const char *full_name, char *base_buf, size_t max_len)
{
    if (!full_name)
    {
        base_buf[0] = '\0';
        return;
    }
    size_t i = 0;
    while (full_name[i] && full_name[i] != '<' && full_name[i] != '_' && i < max_len - 1)
    {
        base_buf[i] = full_name[i];
        i++;
    }
    base_buf[i] = '\0';
}

int is_struct_base_match(Type *base, Type *instantiated)
{
    if (!base || !base->name || !instantiated || !instantiated->name)
    {
        return 0;
    }
    if (strcmp(base->name, instantiated->name) == 0)
    {
        return 1;
    }

    char base_str[MAX_TYPE_NAME_LEN];
    char inst_str[MAX_TYPE_NAME_LEN];
    extract_base_name(base->name, base_str, sizeof(base_str));
    extract_base_name(instantiated->name, inst_str, sizeof(inst_str));

    if (base_str[0] != '\0' && strcmp(base_str, inst_str) == 0)
    {
        return 1;
    }
    return 0;
}

void apply_implicit_struct_pointer_conversions(TypeChecker *tc, ASTNode **expr_ptr,
                                               Type *expected_type)
{
    if (!expr_ptr || !*expr_ptr || !expected_type)
    {
        return;
    }
    ASTNode *expr = *expr_ptr;
    Type *actual_type = expr->type_info;
    if (!actual_type)
    {
        return;
    }

    Type *e_res = get_inner_type(expected_type);
    Type *a_res = get_inner_type(actual_type);

    // T* (actual) -> T (expected) => Implicit Dereference *
    // This allows `return self` to return the struct value when self is a pointer.
    // We only do this if the type is Copy, otherwise it would trigger a
    // move-from-borrowed-reference error.
    if (a_res->kind == TYPE_POINTER && a_res->inner &&
        (a_res->inner->kind == TYPE_STRUCT || a_res->inner->kind == TYPE_ENUM) &&
        (type_eq(a_res->inner, e_res) || is_struct_base_match(a_res->inner, e_res)) &&
        is_type_copy(tc->pctx, a_res->inner))

    {
        ASTNode *deref = ast_create(NODE_EXPR_UNARY);
        deref->unary.op = xstrdup("*");
        deref->unary.operand = expr;
        deref->type_info = a_res->inner;
        deref->token = expr->token;
        deref->next = expr->next;
        expr->next = NULL;
        *expr_ptr = deref;
    }
    // T (actual) -> T* (expected) => Implicit Address-Of &
    else if (e_res->kind == TYPE_POINTER && e_res->inner &&
             (a_res->kind == TYPE_STRUCT || a_res->kind == TYPE_ENUM) &&
             (type_eq(a_res, e_res->inner) || is_struct_base_match(a_res, e_res->inner)))
    {
        ASTNode *addr = ast_create(NODE_EXPR_UNARY);
        int is_rvalue = expr_is_rvalue(tc->pctx, expr);
        addr->unary.op = is_rvalue ? xstrdup("&_rval") : xstrdup("&");
        addr->unary.operand = expr;
        addr->type_info = e_res;
        addr->token = expr->token;
        addr->next = expr->next;
        expr->next = NULL;
        *expr_ptr = addr;
    }
}

int check_type_compatibility(TypeChecker *tc, Type *target, Type *value, Token t,
                             ASTNode *value_node, int is_call_arg)
{
    if (!target || !value)
    {
        return 1; // Can't check incomplete types
    }

    Type *resolved_target = resolve_alias(target);
    Type *resolved_value = resolve_alias(value);

    // MISRA Pointer & Constant Checks (Rules 11.5, 11.9, etc.)
    if (tc->pctx->config->misra_mode && resolved_target->kind == TYPE_POINTER)
    {
        misra_check_null_pointer_constant(tc->pctx, value_node, t);
        misra_check_void_ptr_cast(tc->pctx, target, value, t);
        misra_check_pointer_conversion(tc->pctx, target, value, t);
    }

    // A literal 0 is a null pointer constant, as in C. MISRA mode reports it
    // above (Rule 11.9); otherwise it is accepted for any pointer.
    if (resolved_target->kind == TYPE_POINTER && value_node)
    {
        ASTNode *expr = value_node;
        while (expr && expr->kind == NODE_EXPR_CAST)
        {
            expr = expr->cast.expr;
        }
        if (expr && expr->kind == NODE_EXPR_LITERAL && expr->literal.kind == LITERAL_INT &&
            expr->literal.int_val == 0)
        {
            return 1;
        }
    }

    // Resolution of Integer compatibility (Rule 10.3)
    // This MUST happen before type_eq fast-path because type_eq is lax for integers.
    if (is_integer_type(resolved_target) && is_integer_type(resolved_value))
    {
        int target_width = integer_type_width(resolved_target);
        int value_width = integer_type_width(resolved_value);

        if (tc->pctx->config->misra_mode)
        {
            misra_check_implicit_conversion(tc->pctx, target, value, value_node, t);
        }
        else
        {
            // Warn on narrowing conversions in non-MISRA mode
            if (target_width > 0 && value_width > 0 && target_width < value_width)
            {
                char *t_str = type_to_string(target);
                char *v_str = type_to_string(value);
                char msg[MAX_SHORT_MSG_LEN];
                snprintf(msg, sizeof(msg),
                         "Implicit narrowing conversion from '%s' (%d-bit) to '%s' (%d-bit)", v_str,
                         value_width, t_str, target_width);
                zwarn_at(t, "%s", msg);
                if (tc)
                {
                    tc->warning_count++;
                }
                zfree(t_str);
                zfree(v_str);
            }
        }
        return 1; // All integer pairs compatible (modulo MISRA/Warning checks above)
    }

    if (!is_call_arg && resolved_target->kind == TYPE_POINTER &&
        resolved_value->kind == TYPE_POINTER)
    {
        // Higher depth = shorter scope.
        if (resolved_target->lifetime_depth < resolved_value->lifetime_depth)
        {
            char msg[MAX_ERROR_MSG_LEN];
            snprintf(msg, sizeof(msg),
                     "Escape analysis error: pointer assigned to a location that outlives it");
            const char *hints[] = {"The source pointer belongs to a child scope and will become "
                                   "invalid when that scope ends.",
                                   "Consider copying the value instead of taking a reference.",
                                   NULL};
            tc_error_with_hints(tc, t, msg, hints);
            return 0;
        }
    }

    // Types that are aliases of one another (`alias UserId = Handle<UserKind>`,
    // `alias EntryPtr = Entry*` making `EntryPtr*` and `Entry**` equal): an alias
    // may also reach here as a struct named after it.
    {
        char *target_name = canonical_type_name(tc, target, 0);
        char *value_name = canonical_type_name(tc, value, 0);
        int same = target_name && value_name && strcmp(target_name, value_name) == 0;
        zfree(target_name);
        zfree(value_name);
        if (same)
        {
            return 1;
        }
    }

    // Fast path: exact match
    if (type_eq(target, value))
    {
        // MISRA Rule 11.8: Check const qualification during pointer assignment
        if (tc->pctx->config->misra_mode && resolved_target->kind == TYPE_POINTER &&
            resolved_value->kind == TYPE_POINTER)
        {
            misra_check_pointer_conversion(tc->pctx, target, value, t);
        }
        return 1;
    }

    if (tc->pctx->config->misra_mode)
    {
        // Remaining pointer nesting and array param size checks are modularized
        // in their respective node visitors.
    }

    // Resolve type aliases (str -> string, etc.) for non-integer types
    // (Integer aliases handled by resolve_alias above)
    if (target->kind == TYPE_ALIAS && target->name)
    {
        const char *alias = find_type_alias(tc->pctx, target->name);
        if (alias)
        {
            // Check if resolved names match
            if (value->name && strcmp(alias, value->name) == 0)
            {
                return 1;
            }
        }
    }
    if (value->kind == TYPE_ALIAS && value->name)
    {
        const char *alias = find_type_alias(tc->pctx, value->name);
        if (alias)
        {
            if (target->name && strcmp(alias, target->name) == 0)
            {
                return 1;
            }
        }
    }

    // String types: str, string, *char are compatible
    if ((target->kind == TYPE_STRING || (target->name && strcmp(target->name, "str") == 0)) &&
        (value->kind == TYPE_STRING || (value->name && strcmp(value->name, "string") == 0)))
    {
        return 1;
    }

    // void* is generic pointer
    if (resolved_target->kind == TYPE_POINTER && resolved_target->inner &&
        resolved_target->inner->kind == TYPE_VOID)
    {
        return 1;
    }
    if (resolved_value->kind == TYPE_POINTER && resolved_value->inner &&
        resolve_alias(resolved_value->inner)->kind == TYPE_VOID)
    {
        return 1;
    }

    // Array decay: Array[T] -> T*
    // This allows passing a fixed-size array where a pointer is expected.
    if (resolved_target->kind == TYPE_POINTER && resolved_value->kind == TYPE_ARRAY)
    {
        if (resolved_target->inner && resolved_value->inner)
        {
            // Recursive check for inner types (e.g. char* <- char[10])
            if (type_eq(resolved_target->inner, resolved_value->inner))
            {
                return 1;
            }
            // Allow char* <- char[N] explicitly if type_eq is too strict
            if (is_char_type(resolved_target->inner) && is_char_type(resolved_value->inner))
            {
                return 1;
            }
        }
    }

    if (is_integer_type(resolved_target) && is_integer_type(resolved_value))
    {
        return 1;
    }

    // Float compatibility
    if (is_float_type(resolved_target) && is_float_type(resolved_value))
    {
        return 1;
    }

    // Trait object compatibility: Struct -> Trait
    if (resolved_target->name && is_trait(resolved_target->name) &&
        resolved_value->kind == TYPE_STRUCT && resolved_value->name)
    {
        if (check_impl(tc->pctx, resolved_target->name, resolved_value->name))
        {
            return 1;
        }
    }

    // Type mismatch - report error
    char *t_str = type_to_string(target);
    char *v_str = type_to_string(value);

    char msg[MAX_ERROR_MSG_LEN];
    snprintf(msg, sizeof(msg), "Type mismatch: expected '%s', but found '%s'", t_str, v_str);

    const char *hints[] = {
        "Check if you need an explicit cast",
        "Ensure the types match exactly (no implicit conversions for strict types)", NULL};

    tc_error_with_hints(tc, t, msg, hints);
    zfree(t_str);
    zfree(v_str);
    return 0;
}

void check_expr_var(TypeChecker *tc, ASTNode *node)
{
    // Check if it's an enum variant FIRST, prioritizing value over function constructor if no
    // payload
    EnumVariantReg *ev = find_enum_variant(tc->pctx, node->var_ref.name);
    if (ev)
    {
        FuncSig *sig = find_func(tc->pctx, node->var_ref.name);
        // If it's a no-payload variant, treat it as an enum value (TYPE_ENUM)
        // If it has payloads, it's a constructor function (TYPE_FUNCTION)
        if (!sig || sig->total_args == 0)
        {
            Type *et = type_new(TYPE_ENUM);
            et->name = xstrdup(ev->enum_name);
            node->type_info = et;
            return;
        }
    }

    ZenSymbol *sym = tc_lookup(tc, node->var_ref.name);
    node->var_ref.symbol = sym; // Store for MISRA audits

    if (sym && sym->type_info)
    {
        // Clone the type to keep metadata (like lifetime_depth) isolated per usage site
        node->type_info = type_clone(sym->type_info);

        // Rule 8.9 tracking: Identify globals used in only one function
        if (!sym->is_local && sym->kind == SYM_VARIABLE && tc->current_func)
        {
            ZenSymbol *orig = sym->original ? sym->original : sym;
            if (orig->first_using_func == NULL)
            {
                orig->first_using_func = tc->current_func;
            }
            else if (orig->first_using_func != tc->current_func)
            {
                orig->multi_func_use = 1;
            }
        }
    }
    else
    {
        // Fallback: Check if it's a mangled function name (e.g. from :: operator or enum
        // constructor)
        FuncSig *sig = find_func(tc->pctx, node->var_ref.name);
        if (sig)
        {
            Type *fn_type = type_new(TYPE_FUNCTION);
            fn_type->is_raw = 1;
            fn_type->inner = sig->ret_type ? sig->ret_type : type_new(TYPE_VOID);
            fn_type->count = sig->total_args;
            if (sig->total_args > 0)
            {
                fn_type->args = xmalloc(sizeof(Type *) * (size_t)(sig->total_args));
                for (int i = 0; i < sig->total_args; i++)
                {
                    fn_type->args[i] = sig->arg_types[i];
                }
            }
            node->type_info = fn_type;
        }
        else if (tc->pctx->config->misra_mode)
        {
            char msg[MAX_SHORT_MSG_LEN];
            snprintf(msg, sizeof(msg), "Undefined variable '%s' (MISRA Rule 17.3)",
                     node->var_ref.name);
            tc_error(tc, node->token, msg);
        }
    }

    if (!tc->is_assign_lhs)
    {
        check_use_validity(tc, node);
    }
}

void check_expr_literal(TypeChecker *tc, ASTNode *node)
{
    (void)tc;
    if (node->type_info && node->type_info->kind != TYPE_UNKNOWN)
    {
        return;
    }

    switch (node->literal.kind)
    {
    case LITERAL_INT:
        node->type_info = type_new(TYPE_I32); // Default to i32
        break;
    case LITERAL_FLOAT:
        node->type_info = type_new(TYPE_F64); // Default to f64
        break;
    case LITERAL_STRING:
    case LITERAL_RAW_STRING:
        node->type_info = type_new(TYPE_STRING);
        break;
    case LITERAL_CHAR:
        node->type_info = type_new(TYPE_CHAR);
        break;
    default:
        break;
    }
}

void check_struct_init(TypeChecker *tc, ASTNode *node, int depth)
{
    if (!node)
    {
        return;
    }
    RECURSION_GUARD_TOKEN(tc->pctx, node->token, );

    // MISRA: Mark struct type as used
    mark_type_as_used(tc, node->type_info);
    if (tc->pctx->config->misra_mode)
    {
        ZenSymbol *struct_sym =
            symbol_lookup_kind(tc->pctx->global_scope, node->struct_init.struct_name, SYM_STRUCT);
        if (struct_sym)
        {
            struct_sym->is_dereferenced = 1;
        }
    }

    // Find struct definition (the literal may name it through an alias)
    ASTNode *def = find_struct_def(tc->pctx, node->struct_init.struct_name);
    if (!def)
    {
        def = find_struct_def(tc->pctx, resolve_alias_name(tc, node->struct_init.struct_name));
    }
    if (!def)
    {
        char msg[MAX_SHORT_MSG_LEN];
        snprintf(msg, sizeof(msg), "Unknown struct '%s'", node->struct_init.struct_name);
        tc_error(tc, node->token, msg);
        RECURSION_EXIT(tc->pctx);
        return;
    }

    // Iterate provided fields
    ASTNode *field_init = node->struct_init.fields;
    while (field_init)
    {
        // Rule 9.4: Double initialization check
        if (tc->pctx->config->misra_mode)
        {
            ASTNode *prev = node->struct_init.fields;
            while (prev != field_init)
            {
                if (strcmp(prev->var_decl.name, field_init->var_decl.name) == 0)
                {
                    misra_check_double_initialization(tc->pctx, field_init->var_decl.name,
                                                      field_init->token);
                    break;
                }
                prev = prev->next;
            }
        }

        // Find corresponding field in definition
        ASTNode *def_field = def->strct.fields;
        Type *expected_type = NULL;
        int found = 0;

        while (def_field)
        {
            if (def_field->kind == NODE_FIELD &&
                strcmp(def_field->field.name, field_init->var_decl.name) == 0)
            {
                found = 1;
                expected_type = def_field->type_info;
                break;
            }
            def_field = def_field->next;
        }

        if (found)
        {
            field_init->type_info = expected_type;
        }

        if (found && expected_type && field_init->var_decl.init_expr->kind == NODE_LAMBDA)
        {
            field_init->var_decl.init_expr->type_info = expected_type;
        }

        // Check the initialization expression
        check_node(tc, field_init->var_decl.init_expr, depth + 1);

        if (!found)
        {
            char msg[MAX_SHORT_MSG_LEN];
            snprintf(msg, sizeof(msg), "Struct '%s' has no field named '%s'",
                     node->struct_init.struct_name, field_init->var_decl.name);
            tc_error(tc, field_init->token, msg);
        }
        else if (expected_type && field_init->var_decl.init_expr->type_info)
        {
            // Localize expected field type depth for initialization check.
            // A local struct's fields expect lifetimes compatible with the struct's own scope.
            Type *localized_expected = type_clone(expected_type);
            if (localized_expected && tc->current_func)
            {
                localized_expected->lifetime_depth = tc->current_depth;
            }
            check_type_compatibility(tc, localized_expected,
                                     field_init->var_decl.init_expr->type_info, field_init->token,
                                     field_init->var_decl.init_expr, 0);
        }

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

        field_init = field_init->next;
    }

    // Fields left out of a struct literal are zero-initialised (the C designated
    // initializer the build emits), so they are only reported in MISRA mode,
    // which requires explicit initialization.
    ASTNode *def_field = tc->pctx->config->misra_mode ? def->strct.fields : NULL;
    while (def_field)
    {
        if (def_field->kind == NODE_FIELD && def_field->field.name)
        {
            int provided = 0;
            ASTNode *fi = node->struct_init.fields;
            while (fi)
            {
                if (fi->var_decl.name && strcmp(fi->var_decl.name, def_field->field.name) == 0)
                {
                    provided = 1;
                    break;
                }
                fi = fi->next;
            }
            if (!provided)
            {
                char msg[MAX_SHORT_MSG_LEN];
                snprintf(msg, sizeof(msg), "Missing field '%s' in initializer for struct '%s'",
                         def_field->field.name, node->struct_init.struct_name);
                const char *hints[] = {"All struct fields must be initialized", NULL};
                tc_error_with_hints(tc, node->token, msg, hints);
            }
        }
        def_field = def_field->next;
    }

    node->type_info = def->type_info;
    RECURSION_EXIT(tc->pctx);
}

void check_expr_lambda(TypeChecker *tc, ASTNode *node, int depth)
{
    Type *expected = get_inner_type(node->type_info);
    if (expected && expected->kind == TYPE_FUNCTION && expected->is_raw)
    {
        if (node->lambda.capture_count == 0)
        {
            node->lambda.is_bare = 1;
        }
        else
        {
            const char *hints[] = {
                "Only non-capturing lambdas can be converted to raw function pointers", NULL};
            tc_error_with_hints(tc, node->token,
                                "Cannot convert capturing lambda to raw function pointer", hints);
        }
    }

    if (node->lambda.captured_vars)
    {
        for (int i = 0; i < node->lambda.capture_count; i++)
        {
            char *var_name = node->lambda.captured_vars[i];
            int mode = node->lambda.capture_modes ? node->lambda.capture_modes[i]
                                                  : node->lambda.default_capture_mode;

            ZenSymbol *sym = tc_lookup(tc, var_name);
            if (!sym)
            {
                continue;
            }

            check_path_validity(tc, var_name, node->token);

            if (mode == 0)
            {
                Type *t = sym->type_info;
                if (!is_type_copy(tc->pctx, t))
                {
                    mark_symbol_moved(tc->pctx, sym, node);
                }
            }
        }
    }

    tc_enter_scope(tc);

    for (int i = 0; i < node->lambda.count; i++)
    {
        char *pname = node->lambda.param_names[i];
        Type *ptype = NULL;
        Type *node_ti = get_inner_type(node->type_info);
        if (node_ti && node_ti->kind == TYPE_FUNCTION && node_ti->args)
        {
            if (i < node_ti->count)
            {
                ptype = node_ti->args[i];
            }
        }
        tc_add_symbol(tc, pname, ptype, node->token, 0);
    }

    // Add captured variables to the scope to ensure visibility and immutability inside the lambda.
    if (node->lambda.captured_vars)
    {
        int saved_silent = tc->pctx->silent_warnings;
        tc->pctx->silent_warnings = 1;
        for (int i = 0; i < node->lambda.capture_count; i++)
        {
            char *var_name = node->lambda.captured_vars[i];
            int mode = node->lambda.capture_modes ? node->lambda.capture_modes[i]
                                                  : node->lambda.default_capture_mode;

            // Lookup original symbol to get its type
            ZenSymbol *orig_sym = tc_lookup(tc, var_name);
            if (orig_sym)
            {
                // Shadow the original variable in the lambda scope
                // Value captures are immutable.
                tc_add_symbol(tc, var_name, orig_sym->type_info, node->token, (mode == 0));
            }
        }
        tc->pctx->silent_warnings = saved_silent;
    }

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

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

    if (node->lambda.body)
    {
        if (node->lambda.body->kind == NODE_BLOCK)
        {
            check_block(tc, node->lambda.body, depth + 1);
        }
        else
        {
            check_node(tc, node->lambda.body, depth + 1);
        }
    }

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

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

// INFERENCE & ENTRY POINTS