// sema.bux — Semantic analysis (type checker, ported from sema.nim)
// Validates types, resolves identifiers, checks function calls.
module Sema {

extern func bux_string_concat(a: String, b: String) -> String;
extern func bux_int_to_str(n: int64) -> String;

// ---------------------------------------------------------------------------
// Sema context
// ---------------------------------------------------------------------------
struct Sema {
    module: *Module;
    scope: *Scope;
    typeTable: *void;
    methodTable: *void;
    diagCount: int;
    diags: *SemaDiag;
    hasError: bool;
    currentRetType: int;    // return type of the function being checked
    checkedFunc: bool;      // true inside @[Checked] function
    movedCount: int;        // number of moved variables
    movedName0: String;     // inline moved var names (up to 8)
    movedName1: String;
    movedName2: String;
    movedName3: String;
    movedName4: String;
    movedName5: String;
    movedName6: String;
    movedName7: String;
    closureDepth: int;      // nesting depth inside closures
    currentClosureExpr: *Expr; // current closure being analyzed (for capture tracking)
    closureScope: *Scope;   // scope at which the current closure was entered
    // Trait bounds tables
    interfaceTable: *InterfaceEntry;
    interfaceCount: int;
    methodEntries: *MethodEntry;
    methodCount: int;
}

struct SemaDiag {
    line: uint32;
    column: uint32;
    message: String;
}

// ---------------------------------------------------------------------------
// Interface / Method tables for trait bounds checking
// ---------------------------------------------------------------------------

struct InterfaceEntry {
    name: String,
    decl: *Decl,
}

struct MethodEntry {
    typeName: String,
    methodName: String,
    decl: *Decl,
}

// ---------------------------------------------------------------------------
// Diagnostics
// ---------------------------------------------------------------------------

func Sema_EmitError(sema: *Sema, line: uint32, col: uint32, msg: String) {
    if sema.diagCount < 256 {
        sema.diags[sema.diagCount] = SemaDiag { line: line, column: col, message: msg };
        sema.diagCount = sema.diagCount + 1;
    }
    sema.hasError = true;
}

// ---------------------------------------------------------------------------
// Symbol zero-init helper (bootstrap C backend does not zero-init structs)
// ---------------------------------------------------------------------------

func Sema_ZeroInitSymbol(sym: *Symbol) {
    sym.kind = 0;
    sym.name = "";
    sym.typeKind = 0;
    sym.typeName = "";
    sym.refType = null as *TypeExpr;
    sym.isMutable = false;
    sym.isPublic = false;
    sym.decl = null as *Decl;
}

// ---------------------------------------------------------------------------
// Build a tekFunc TypeExpr from a function declaration
// ---------------------------------------------------------------------------

func Sema_BuildFuncTypeExprFromDecl(decl: *Decl) -> *TypeExpr {
    let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
    te.kind = tekFunc;
    te.line = decl.line;
    te.column = decl.column;
    te.funcRet = decl.retType;
    te.funcParamCount = decl.paramCount;
    var head: *TypeExprList = null as *TypeExprList;
    var tail: *TypeExprList = null as *TypeExprList;
    var i: int = 0;
    while i < decl.paramCount && i < 9 {
        var p: Param;
        if i == 0 { p = decl.param0; }
        else if i == 1 { p = decl.param1; }
        else if i == 2 { p = decl.param2; }
        else if i == 3 { p = decl.param3; }
        else if i == 4 { p = decl.param4; }
        else if i == 5 { p = decl.param5; }
        else if i == 6 { p = decl.param6; }
        else if i == 7 { p = decl.param7; }
        else { p = decl.param8; }
        if p.refParamType != null as *TypeExpr {
            let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
            node.te = p.refParamType;
            node.next = null as *TypeExprList;
            if head == null as *TypeExprList {
                head = node;
            } else {
                tail.next = node;
            }
            tail = node;
        }
        i = i + 1;
    }
    te.funcParams = head;
    return te;
}

// ---------------------------------------------------------------------------
// Type resolution from TypeExpr → Type constants
// ---------------------------------------------------------------------------

func Sema_ResolveType(sema: *Sema, te: *TypeExpr) -> int {
    if te == null as *TypeExpr { return tyUnknown; }
    let name: String = te.typeName;

    if te.kind == tekPointer {
        return tyPointer;
    }

    if te.kind == tekFunc {
        return tyFunc;
    }

    if String_Eq(name, "void") { return tyVoid; }
    if String_Eq(name, "bool") { return tyBool; }
    if String_Eq(name, "bool8") { return tyBool8; }
    if String_Eq(name, "bool16") { return tyBool16; }
    if String_Eq(name, "bool32") { return tyBool32; }
    if String_Eq(name, "char8") { return tyChar8; }
    if String_Eq(name, "char16") { return tyChar16; }
    if String_Eq(name, "char32") { return tyChar32; }
    if String_Eq(name, "String") { return tyStr; }
    if String_Eq(name, "str") { return tyStr; }
    if String_Eq(name, "int8") { return tyInt8; }
    if String_Eq(name, "int16") { return tyInt16; }
    if String_Eq(name, "int32") { return tyInt32; }
    if String_Eq(name, "int64") { return tyInt64; }
    if String_Eq(name, "int") { return tyInt; }
    if String_Eq(name, "uint8") { return tyUInt8; }
    if String_Eq(name, "uint16") { return tyUInt16; }
    if String_Eq(name, "uint32") { return tyUInt32; }
    if String_Eq(name, "uint64") { return tyUInt64; }
    if String_Eq(name, "uint") { return tyUInt; }
    if String_Eq(name, "float32") { return tyFloat32; }
    if String_Eq(name, "float64") { return tyFloat64; }
    if String_Eq(name, "float") { return tyFloat64; }

    // Type resolution uses scope-based lookup via Sema_CollectGlobals
    // TODO: add StringMap-based type table for faster named-type validation
    return tyNamed;
}

// ---------------------------------------------------------------------------
// Type predicates
// ---------------------------------------------------------------------------

func Sema_IsNumeric(kind: int) -> bool {
    if kind == tyUnknown || kind == tyNamed || kind == tyTypeParam { return true; }
    if kind == tyInt8 || kind == tyInt16 || kind == tyInt32 || kind == tyInt64 || kind == tyInt { return true; }
    if kind == tyUInt8 || kind == tyUInt16 || kind == tyUInt32 || kind == tyUInt64 || kind == tyUInt { return true; }
    if kind == tyFloat32 || kind == tyFloat64 { return true; }
    return false;
}

func Sema_IsBool(kind: int) -> bool {
    return kind == tyBool || kind == tyBool8 || kind == tyBool16 || kind == tyBool32;
}

// ---------------------------------------------------------------------------
// Block checking helper
// ---------------------------------------------------------------------------

func Sema_CheckBlock(sema: *Sema, block: *Block) {
    if block == null as *Block { return; }
    // Create child scope for block (matching Nim bootstrap behavior)
    var blockScope: Scope = Scope_NewChild(sema.scope);
    let prevScope: *Scope = sema.scope;
    sema.scope = &blockScope;
    var stmt: *Stmt = block.firstStmt;
    while stmt != null as *Stmt {
        Sema_CheckStmt(sema, stmt);
        stmt = stmt.nextStmt;
    }
    sema.scope = prevScope;
}

// ---------------------------------------------------------------------------
// Call argument resolution: inject defaults and reorder named args
// ---------------------------------------------------------------------------

func Sema_ResolveCallArgs(sema: *Sema, expr: *Expr) {
    if expr == null as *Expr || expr.kind != ekCall { return; }
    if expr.child1 == null as *Expr || expr.child1.kind != ekIdent { return; }
    let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
    if sym.kind != skFunc || sym.decl == null as *Decl { return; }
    let decl: *Decl = sym.decl;
    if decl.paramCount == 0 { return; }

    // Check if any named args present
    var hasNamed: bool = false;
    var arg: *ExprList = expr.callArgs;
    while arg != null as *ExprList {
        if !String_Eq(arg.argName, "") { hasNamed = true; }
        arg = arg.next;
    }
    if !hasNamed && expr.callArgCount >= decl.paramCount { return; }

    // Build new linked list in param order
    var newFirst: *ExprList = null as *ExprList;
    var newLast: *ExprList = null as *ExprList;
    var newCount: int = 0;
    var usedPositional: int = 0;
    var positionalAfterNamed: bool = false;

    var i: int = 0;
    while i < decl.paramCount {
        var p: *Param = null as *Param;
        if i == 0 { p = &decl.param0; }
        else if i == 1 { p = &decl.param1; }
        else if i == 2 { p = &decl.param2; }
        else if i == 3 { p = &decl.param3; }
        else if i == 4 { p = &decl.param4; }
        else if i == 5 { p = &decl.param5; }
        else if i == 6 { p = &decl.param6; }
        else if i == 7 { p = &decl.param7; }
        else if i == 8 { p = &decl.param8; }

        var matched: *Expr = null as *Expr;

        // Look for positional arg at this index
        if usedPositional < expr.callArgCount {
            var posIdx: int = 0;
            var posArg: *ExprList = expr.callArgs;
            while posArg != null as *ExprList {
                if String_Eq(posArg.argName, "") {
                    if posIdx == usedPositional {
                        matched = posArg.expr;
                        usedPositional = usedPositional + 1;
                        break;
                    }
                    posIdx = posIdx + 1;
                }
                posArg = posArg.next;
            }
        }

        // Look for named arg matching this param
        if matched == null as *Expr {
            var namedArg: *ExprList = expr.callArgs;
            while namedArg != null as *ExprList {
                if String_Eq(namedArg.argName, p.name) {
                    matched = namedArg.expr;
                    break;
                }
                namedArg = namedArg.next;
            }
        }

        // Use default if available
        if matched == null as *Expr && p.defaultExpr != null as *Expr {
            matched = p.defaultExpr;
        }

        if matched != null as *Expr {
            let node: *ExprList = bux_alloc(sizeof(ExprList)) as *ExprList;
            node.expr = matched;
            node.next = null as *ExprList;
            node.argName = "";
            if newFirst == null as *ExprList {
                newFirst = node;
                newLast = node;
            } else {
                newLast.next = node;
                newLast = node;
            }
            newCount = newCount + 1;
        }

        i = i + 1;
    }

    expr.callArgs = newFirst;
    expr.callArgCount = newCount;
}

// ---------------------------------------------------------------------------
// Borrow checker helpers (inline array, matching Decl param pattern)
// ---------------------------------------------------------------------------

func Sema_AddMoved(sema: *Sema, name: String) {
    if sema == null as *Sema { return; }
    if sema.movedCount >= 8 { return; }
    if sema.movedCount == 0 { sema.movedName0 = name; }
    else if sema.movedCount == 1 { sema.movedName1 = name; }
    else if sema.movedCount == 2 { sema.movedName2 = name; }
    else if sema.movedCount == 3 { sema.movedName3 = name; }
    else if sema.movedCount == 4 { sema.movedName4 = name; }
    else if sema.movedCount == 5 { sema.movedName5 = name; }
    else if sema.movedCount == 6 { sema.movedName6 = name; }
    else if sema.movedCount == 7 { sema.movedName7 = name; }
    sema.movedCount = sema.movedCount + 1;
}

func Sema_IsMoved(sema: *Sema, name: String) -> bool {
    if sema == null as *Sema { return false; }
    if sema.movedCount > 0 && String_Eq(sema.movedName0, name) { return true; }
    if sema.movedCount > 1 && String_Eq(sema.movedName1, name) { return true; }
    if sema.movedCount > 2 && String_Eq(sema.movedName2, name) { return true; }
    if sema.movedCount > 3 && String_Eq(sema.movedName3, name) { return true; }
    if sema.movedCount > 4 && String_Eq(sema.movedName4, name) { return true; }
    if sema.movedCount > 5 && String_Eq(sema.movedName5, name) { return true; }
    if sema.movedCount > 6 && String_Eq(sema.movedName6, name) { return true; }
    if sema.movedCount > 7 && String_Eq(sema.movedName7, name) { return true; }
    return false;
}

func Sema_RemoveMoved(sema: *Sema, name: String) {
    if sema == null as *Sema { return; }
    var found: int = -1;
    if sema.movedCount > 0 && String_Eq(sema.movedName0, name) { found = 0; }
    else if sema.movedCount > 1 && String_Eq(sema.movedName1, name) { found = 1; }
    else if sema.movedCount > 2 && String_Eq(sema.movedName2, name) { found = 2; }
    else if sema.movedCount > 3 && String_Eq(sema.movedName3, name) { found = 3; }
    else if sema.movedCount > 4 && String_Eq(sema.movedName4, name) { found = 4; }
    else if sema.movedCount > 5 && String_Eq(sema.movedName5, name) { found = 5; }
    else if sema.movedCount > 6 && String_Eq(sema.movedName6, name) { found = 6; }
    else if sema.movedCount > 7 && String_Eq(sema.movedName7, name) { found = 7; }
    if found >= 0 {
        var i: int = found;
        while i < sema.movedCount - 1 {
            if i == 0 { sema.movedName0 = sema.movedName1; }
            else if i == 1 { sema.movedName1 = sema.movedName2; }
            else if i == 2 { sema.movedName2 = sema.movedName3; }
            else if i == 3 { sema.movedName3 = sema.movedName4; }
            else if i == 4 { sema.movedName4 = sema.movedName5; }
            else if i == 5 { sema.movedName5 = sema.movedName6; }
            else if i == 6 { sema.movedName6 = sema.movedName7; }
            i = i + 1;
        }
        sema.movedCount = sema.movedCount - 1;
    }
}

// ---------------------------------------------------------------------------
// Capture tracking for closures
// ---------------------------------------------------------------------------

func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
    if closureExpr == null as *Expr { return; }
    // Check if already captured
    var i: int = 0;
    while i < closureExpr.captureCount {
        var capName: String = "";
        if i == 0 { capName = closureExpr.captureName0; }
        else if i == 1 { capName = closureExpr.captureName1; }
        else if i == 2 { capName = closureExpr.captureName2; }
        else if i == 3 { capName = closureExpr.captureName3; }
        else if i == 4 { capName = closureExpr.captureName4; }
        else if i == 5 { capName = closureExpr.captureName5; }
        else if i == 6 { capName = closureExpr.captureName6; }
        else if i == 7 { capName = closureExpr.captureName7; }
        if String_Eq(capName, name) { return; }
        i = i + 1;
    }
    // Add new capture (up to 8)
    if closureExpr.captureCount < 8 {
        let idx: int = closureExpr.captureCount;
        if idx == 0 { closureExpr.captureName0 = name; closureExpr.captureType0 = typeKind; }
        else if idx == 1 { closureExpr.captureName1 = name; closureExpr.captureType1 = typeKind; }
        else if idx == 2 { closureExpr.captureName2 = name; closureExpr.captureType2 = typeKind; }
        else if idx == 3 { closureExpr.captureName3 = name; closureExpr.captureType3 = typeKind; }
        else if idx == 4 { closureExpr.captureName4 = name; closureExpr.captureType4 = typeKind; }
        else if idx == 5 { closureExpr.captureName5 = name; closureExpr.captureType5 = typeKind; }
        else if idx == 6 { closureExpr.captureName6 = name; closureExpr.captureType6 = typeKind; }
        else if idx == 7 { closureExpr.captureName7 = name; closureExpr.captureType7 = typeKind; }
        closureExpr.captureCount = closureExpr.captureCount + 1;
    }
}

// ---------------------------------------------------------------------------
// Expression type checking
// ---------------------------------------------------------------------------

func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
    if expr == null as *Expr { return tyUnknown; }
    let kind: int = expr.kind;

    // Literal
    if kind == ekLiteral {
        let tk: int = expr.tokKind;
        if tk == tkIntLiteral { return tyInt; }
        if tk == tkFloatLiteral { return tyFloat64; }
        if tk == tkStringLiteral { return tyStr; }
        if tk == tkBoolLiteral { return tyBool; }
        if tk == tkCharLiteral { return tyChar32; }
        if tk == tkNull { return tyPointer; }
        return tyUnknown;
    }

    // Identifier — look up in scope
    if kind == ekIdent {
        // Borrow check: use-after-move in @[Checked] functions
        if sema.checkedFunc && Sema_IsMoved(sema, expr.strValue) {
            Sema_EmitError(sema, expr.line, expr.column,
                String_Concat("use of moved value '", String_Concat(expr.strValue, "'")));
        }
        let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue);
        if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) {
            let errMsg: String = String_Concat("undeclared identifier '", expr.strValue);
            let errMsg2: String = String_Concat(errMsg, "'");
            Sema_EmitError(sema, expr.line, expr.column, errMsg2);
            return tyUnknown;
        }
        if sym.refType != null as *TypeExpr {
            expr.refType = sym.refType;
        } else if sym.typeName != null as String && !String_Eq(sym.typeName, "") {
            let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
            te.kind = tekNamed;
            te.typeName = sym.typeName;
            expr.refType = te;
        }
        // Capture tracking: if inside closure and identifier is not local but from parent scope
        if sema.closureDepth > 0 && sema.currentClosureExpr != null as *Expr && sema.closureScope != null as *Scope {
            let localSym: Symbol = Scope_LookupUpTo(sema.scope, expr.strValue, sema.closureScope);
            if localSym.kind == 0 && !String_Eq(localSym.name, expr.strValue) {
                // Not local to closure scope — check if it's a variable from outer scope
                if sym.kind == skVar {
                    Sema_AddCapture(sema.currentClosureExpr, expr.strValue, sym.typeKind);
                }
            }
        }
        return sym.typeKind;
    }

    // self
    if kind == ekSelf {
        let sym: Symbol = Scope_Lookup(sema.scope, "self");
        if sym.kind == 0 && !String_Eq(sym.name, "self") {
            Sema_EmitError(sema, expr.line, expr.column, "self outside method");
            return tyUnknown;
        }
        return sym.typeKind;
    }

    // Binary
    if kind == ekBinary {
        let left: int = Sema_CheckExpr(sema, expr.child1);
        let right: int = Sema_CheckExpr(sema, expr.child2);
        let op: int = expr.intValue;

        // Borrow check: reject assignment through deref in @[Checked] functions
        if sema.checkedFunc && op == tkAssign {
            if expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
                Sema_EmitError(sema, expr.line, expr.column,
                    "cannot assign through raw pointer in checked function — use '&mut T' instead");
            }
        }

        // Borrow check: reinitialization after move
        if sema.checkedFunc && op == tkAssign {
            if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
                Sema_RemoveMoved(sema, expr.child1.strValue);
            }
        }

        // Assignment operators return target type
        if op == tkAssign || (op >= tkPlusAssign && op <= tkShrAssign) {
            return left;
        }
        // Comparison operators return bool
        if op >= tkEq && op <= tkGe { return tyBool; }
        // Logical operators return bool
        if op == tkAmpAmp || op == tkPipePipe || op == tkBang { return tyBool; }
        // Arithmetic returns wider type
        if !Sema_IsNumeric(left) || !Sema_IsNumeric(right) {
            Sema_EmitError(sema, expr.line, expr.column, "arithmetic requires numeric operands");
        }
        if left == tyFloat64 || right == tyFloat64 { return tyFloat64; }
        return tyInt;
    }

    // Unary
    if kind == ekUnary {
        let operand: int = Sema_CheckExpr(sema, expr.child1);
        let op: int = expr.intValue;
        if op == tkBang { return tyBool; }
        if op == tkStar { return tyUnknown; } // dereference — resolve pointee
        if op == tkAmp {
            if expr.child1.refType != null as *TypeExpr {
                expr.refType = expr.child1.refType;
            }
            return tyPointer;
        }
        return operand;
    }

    // Assign
    if kind == ekAssign {
        let target: int = Sema_CheckExpr(sema, expr.child1);
        let value: int = Sema_CheckExpr(sema, expr.child2);
        // Borrow check: reject assignment through deref in @[Checked] functions
        if sema.checkedFunc && expr.child1 != null as *Expr && expr.child1.kind == ekUnary && expr.child1.intValue == tkStar {
            Sema_EmitError(sema, expr.line, expr.column,
                "cannot assign through raw pointer in checked function — use '&mut T' instead");
        }
        return target;
    }

    // Call
    if kind == ekCall {
        let calleeType: int = Sema_CheckExpr(sema, expr.child1);
        Sema_ResolveCallArgs(sema, expr);
        var arg: *ExprList = expr.callArgs;
        while arg != null as *ExprList {
            discard Sema_CheckExpr(sema, arg.expr);
            arg = arg.next;
        }
        // Borrow check: reject double mutable borrow in @[Checked] functions
        if sema.checkedFunc {
            var a: *ExprList = expr.callArgs;
            var ai: int = 0;
            while a != null as *ExprList {
                if a.expr != null as *Expr && a.expr.kind == ekUnary && a.expr.intValue == tkAmp {
                    if a.expr.child1 != null as *Expr && a.expr.child1.kind == ekIdent {
                        let name1: String = a.expr.child1.strValue;
                        var b: *ExprList = a.next;
                        var bi: int = ai + 1;
                        while b != null as *ExprList {
                            if b.expr != null as *Expr && b.expr.kind == ekUnary && b.expr.intValue == tkAmp {
                                if b.expr.child1 != null as *Expr && b.expr.child1.kind == ekIdent {
                                    if String_Eq(name1, b.expr.child1.strValue) {
                                        Sema_EmitError(sema, expr.line, expr.column,
                                            String_Concat("mutable borrow conflict: multiple &mut references to '", String_Concat(name1, "'")));
                                    }
                                }
                            }
                            b = b.next;
                            bi = bi + 1;
                        }
                    }
                }
                a = a.next;
                ai = ai + 1;
            }
        }
        // Indirect call through function-typed value
        if calleeType == tyFunc {
            if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekFunc {
                if expr.child1.refType.funcRet != null as *TypeExpr {
                    return Sema_ResolveType(sema, expr.child1.refType.funcRet);
                }
                return tyVoid;
            }
        }
        // Direct call to named function
        if expr.child1.kind == ekIdent {
            let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
            if sym.kind == skFunc {
            if sym.decl != null as *Decl {
                // Trait bounds checking for explicit generic calls: Max<Circle>(...)
                if expr.child1.genericTypeArgCount > 0 {
                    Sema_CheckTraitBounds(sema, sym.decl, expr.child1.genericTypeArg0, expr.child1.genericTypeArg1, expr.child1.genericTypeArgCount, expr.line, expr.column);
                }
                if sym.decl.retType != null as *TypeExpr {
                    return Sema_ResolveType(sema, sym.decl.retType);
                }
            }
            }
        }
        return tyUnknown;
    }

    // Ternary
    if kind == ekTernary {
        return Sema_CheckExpr(sema, expr.child2); // then type
    }

    // Cast — return target type
    if kind == ekCast {
        if expr.refType != null as *TypeExpr {
            return Sema_ResolveType(sema, expr.refType);
        }
        return tyUnknown;
    }

    // Try (?)
    if kind == ekTry {
        let inner: int = Sema_CheckExpr(sema, expr.child1);
        return inner; // simplified
    }

    // Struct init: TypeName { field: value, ... }
    if kind == ekStructInit {
        return tyNamed;
    }

    // Field access
    if kind == ekField {
        discard Sema_CheckExpr(sema, expr.child1);
        return tyUnknown;
    }

    // Index
    if kind == ekIndex {
        let obj: int = Sema_CheckExpr(sema, expr.child1);
        let idx: int = Sema_CheckExpr(sema, expr.child2);
        if !Sema_IsNumeric(idx) && idx != tyUnknown {
            Sema_EmitError(sema, expr.line, expr.column, "index must be integer");
        }
        return tyUnknown;
    }

    // Block expression
    if kind == ekBlock {
        if expr.refBlock != null as *Block {
            Sema_CheckBlock(sema, expr.refBlock);
        }
        return tyVoid;
    }

    // Closure: |params| -> Ret { body }
    if kind == ekClosure {
        let savedRetType: int = sema.currentRetType;
        let savedScope: *Scope = sema.scope;
        let savedClosureDepth: int = sema.closureDepth;
        let savedClosureExpr: *Expr = sema.currentClosureExpr;
        let savedClosureScope: *Scope = sema.closureScope;
        let childScope: Scope = Scope_NewChild(sema.scope);
        sema.scope = &childScope;

        // Set up closure tracking
        sema.closureDepth = sema.closureDepth + 1;
        sema.currentClosureExpr = expr;
        sema.closureScope = &childScope;
        expr.captureCount = 0;

        // Set return type from annotation, or unknown for inference
        var closureRetType: int = tyUnknown;
        if expr.refType != null as *TypeExpr {
            closureRetType = Sema_ResolveType(sema, expr.refType);
        }
        sema.currentRetType = closureRetType;

        // Register params in child scope
        let params: *Decl = expr.closureParams;
        if params != null as *Decl {
            var i: int = 0;
            while i < params.paramCount {
                var p: Param;
                if i == 0 { p = params.param0; }
                else if i == 1 { p = params.param1; }
                else if i == 2 { p = params.param2; }
                else if i == 3 { p = params.param3; }
                else if i == 4 { p = params.param4; }
                else if i == 5 { p = params.param5; }
                else if i == 6 { p = params.param6; }
                else if i == 7 { p = params.param7; }
                else if i == 8 { p = params.param8; }

                if !String_Eq(p.name, "") {
                    var sym: Symbol;
                    sym.kind = skVar;
                    sym.name = p.name;
                    sym.typeKind = tyUnknown;
                    if p.refParamType != null as *TypeExpr {
                        sym.typeKind = Sema_ResolveType(sema, p.refParamType);
                        sym.refType = p.refParamType;
                    }
                    sym.typeName = "";
                    sym.isMutable = false;
                    sym.isPublic = false;
                    sym.decl = null as *Decl;
                    discard Scope_Define(sema.scope, sym);
                }
                i = i + 1;
            }
        }

        // Check body
        if expr.refBlock != null as *Block {
            Sema_CheckBlock(sema, expr.refBlock);
        }

        // Restore
        sema.scope = savedScope;
        sema.currentRetType = savedRetType;
        sema.closureDepth = savedClosureDepth;
        sema.currentClosureExpr = savedClosureExpr;
        sema.closureScope = savedClosureScope;

        // Build function type expression for later use
        let funcType: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
        funcType.kind = tekFunc;
        funcType.funcRet = expr.refType;
        funcType.funcParamCount = params.paramCount;
        // Build param type list
        if params.paramCount > 0 {
            var head: *TypeExprList = null as *TypeExprList;
            var tail: *TypeExprList = null as *TypeExprList;
            var i: int = 0;
            while i < params.paramCount {
                var p: Param;
                if i == 0 { p = params.param0; }
                else if i == 1 { p = params.param1; }
                else if i == 2 { p = params.param2; }
                else if i == 3 { p = params.param3; }
                else if i == 4 { p = params.param4; }
                else if i == 5 { p = params.param5; }
                else if i == 6 { p = params.param6; }
                else if i == 7 { p = params.param7; }
                else if i == 8 { p = params.param8; }

                let node: *TypeExprList = bux_alloc(sizeof(TypeExprList)) as *TypeExprList;
                node.te = p.refParamType;
                node.next = null as *TypeExprList;
                if head == null as *TypeExprList {
                    head = node;
                    tail = node;
                } else {
                    tail.next = node;
                    tail = node;
                }
                i = i + 1;
            }
            funcType.funcParams = head;
        }
        expr.refType = funcType;

        return tyFunc;
    }

    return tyUnknown;
}

// ---------------------------------------------------------------------------
// Statement checking
// ---------------------------------------------------------------------------

func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
    if stmt == null as *Stmt { return; }
    let kind: int = stmt.kind;

    // Let/var
    if kind == skLet {
        let initType: int = Sema_CheckExpr(sema, stmt.child1);
        // Borrow check: move tracking
        if sema.checkedFunc && stmt.child1 != null as *Expr && stmt.child1.kind == ekIdent {
            Sema_AddMoved(sema, stmt.child1.strValue);
        }
        // Register variable in scope
        var sym: Symbol;
        sym.kind = skVar;
        sym.name = stmt.strValue;
        sym.typeKind = initType;
        sym.typeName = "";
        sym.refType = null as *TypeExpr;
        if stmt.refStmtType != null as *TypeExpr {
            sym.refType = stmt.refStmtType;
            if stmt.refStmtType.kind == tekPointer && stmt.refStmtType.pointerPointee != null as *TypeExpr {
                sym.typeName = String_Concat(stmt.refStmtType.pointerPointee.typeName, "*");
            } else {
                sym.typeName = stmt.refStmtType.typeName;
            }
        } else if stmt.child1 != null as *Expr && stmt.child1.refType != null as *TypeExpr {
            // Infer type from initializer expression
            sym.refType = stmt.child1.refType;
            stmt.refStmtType = stmt.child1.refType;
            if stmt.child1.refType.kind == tekPointer && stmt.child1.refType.pointerPointee != null as *TypeExpr {
                sym.typeName = String_Concat(stmt.child1.refType.pointerPointee.typeName, "*");
            } else {
                sym.typeName = stmt.child1.refType.typeName;
            }
        }
        sym.isMutable = stmt.boolValue;
        sym.isPublic = false;
        sym.decl = null as *Decl;
        discard Scope_Define(sema.scope, sym);
        return;
    }

    // Return
    if kind == skReturn {
        if stmt.child1 != null as *Expr {
            let retType: int = Sema_CheckExpr(sema, stmt.child1);
            if sema.currentRetType != tyUnknown && retType != tyUnknown {
                if retType != sema.currentRetType {
                    // Be permissive: allow numeric widening, pointer compatibility
                    if !Sema_IsNumeric(retType) || !Sema_IsNumeric(sema.currentRetType) {
                        // Allow pointer-type compatibility (String <-> *T, *T <-> *U)
                        let retIsPtr: bool = retType == tyPointer || retType == tyStr;
                        let expectedIsPtr: bool = sema.currentRetType == tyPointer || sema.currentRetType == tyStr;
                        if !retIsPtr || !expectedIsPtr {
                            Sema_EmitError(sema, stmt.line, stmt.column, "return type mismatch");
                        }
                    }
                }
            }
        } else {
            if sema.currentRetType != tyVoid && sema.currentRetType != tyUnknown {
                Sema_EmitError(sema, stmt.line, stmt.column, "missing return value");
            }
        }
        return;
    }

    // If
    if kind == skIf {
        let condType: int = Sema_CheckExpr(sema, stmt.child1);
        if !Sema_IsBool(condType) && condType != tyUnknown {
            Sema_EmitError(sema, stmt.line, stmt.column, "if condition must be bool");
        }
        Sema_CheckBlock(sema, stmt.refStmtBlock);
        Sema_CheckBlock(sema, stmt.refStmtElse);
        return;
    }

    // While
    if kind == skWhile {
        let condType: int = Sema_CheckExpr(sema, stmt.child1);
        if !Sema_IsBool(condType) && condType != tyUnknown {
            Sema_EmitError(sema, stmt.line, stmt.column, "while condition must be bool");
        }
        Sema_CheckBlock(sema, stmt.refStmtBlock);
        return;
    }

    // Do-while
    if kind == skDoWhile {
        Sema_CheckBlock(sema, stmt.refStmtBlock);
        let condType: int = Sema_CheckExpr(sema, stmt.child1);
        if !Sema_IsBool(condType) && condType != tyUnknown {
            Sema_EmitError(sema, stmt.line, stmt.column, "do-while condition must be bool");
        }
        return;
    }

    // Loop
    if kind == skLoop {
        Sema_CheckBlock(sema, stmt.refStmtBlock);
        return;
    }

    // For
    if kind == skFor {
        let iterType: int = Sema_CheckExpr(sema, stmt.child1);
        var forScope: Scope = Scope_NewChild(sema.scope);
        var loopSym: Symbol;
        loopSym.kind = skVar;
        loopSym.name = stmt.strValue;
        loopSym.typeKind = tyUnknown;
        loopSym.typeName = "";
        loopSym.refType = null as *TypeExpr;
        loopSym.isMutable = true;
        loopSym.isPublic = false;
        loopSym.decl = null as *Decl;

        // Determine loop variable type from iterator expression
        if stmt.child1 != null as *Expr {
            // Range-based: type from lower bound (selfhost parses .. as ekBinary)
            if stmt.child1.kind == ekRange || (stmt.child1.kind == ekBinary && (stmt.child1.intValue == tkDotDot || stmt.child1.intValue == tkDotDotEqual)) {
                let boundType: int = Sema_CheckExpr(sema, stmt.child1.child1);
                loopSym.typeKind = boundType;
            }
            // Array-based: extract element type from Array<T> annotation
            if stmt.child1.kind == ekIdent {
                let sym: Symbol = Scope_Lookup(sema.scope, stmt.child1.strValue);
                if sym.refType != null as *TypeExpr {
                    if String_Eq(sym.refType.typeName, "Array") && sym.refType.typeArgCount > 0 {
                        let elemTe: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
                        elemTe.kind = tekNamed;
                        elemTe.typeName = sym.refType.typeArgName0;
                        elemTe.line = stmt.line;
                        elemTe.column = stmt.column;
                        loopSym.typeKind = Sema_ResolveType(sema, elemTe);
                        loopSym.typeName = sym.refType.typeArgName0;
                        loopSym.refType = elemTe;
                    }
                }
            }
        }

        discard Scope_Define(&forScope, loopSym);
        let prevScope: *Scope = sema.scope;
        sema.scope = &forScope;
        Sema_CheckBlock(sema, stmt.refStmtBlock);
        sema.scope = prevScope;
        return;
    }

    // Match
    if kind == skMatch {
        discard Sema_CheckExpr(sema, stmt.child1);
        return;
    }

    // Break / Continue
    if kind == skBreak || kind == skContinue {
        return;
    }

    // Expression statement
    if kind == skExpr && stmt.child1 != null as *Expr {
        discard Sema_CheckExpr(sema, stmt.child1);
        return;
    }

    // Decl (nested)
    if kind == skDecl {
        if stmt.refStmtDecl != null as *Decl && stmt.refStmtDecl.kind == dkFunc {
            Sema_EmitError(sema, stmt.line, stmt.column, "nested functions not yet supported");
        }
        return;
    }
}

// ---------------------------------------------------------------------------
// Collect globals (register functions, structs, enums in scope)
// ---------------------------------------------------------------------------

func Sema_CollectGlobals(sema: *Sema) {
    var decl: *Decl = sema.module.firstItem;
    var funcCount: int = 0;
    var lastDecl: *Decl = null as *Decl;
    while decl != null as *Decl {
        lastDecl = decl;
        let dk: int = decl.kind;

        // Function
        if dk == dkFunc {
            var sym: Symbol;
            Sema_ZeroInitSymbol(&sym);
            sym.kind = skFunc;
            sym.name = decl.strValue;
            sym.typeKind = tyFunc;
            sym.refType = Sema_BuildFuncTypeExprFromDecl(decl);
            sym.isPublic = decl.isPublic;
            sym.decl = decl;
            discard Scope_Define(sema.scope, sym);
        }

        // Struct
        if dk == dkStruct {
            var sym: Symbol;
            Sema_ZeroInitSymbol(&sym);
            sym.kind = skType;
            sym.name = decl.strValue;
            sym.typeKind = tyNamed;
            sym.isPublic = decl.isPublic;
            sym.decl = decl;
            discard Scope_Define(sema.scope, sym);
        }

        // Enum
        if dk == dkEnum {
            var sym: Symbol;
            Sema_ZeroInitSymbol(&sym);
            sym.kind = skType;
            sym.name = decl.strValue;
            sym.typeKind = tyNamed;
            sym.isPublic = decl.isPublic;
            sym.decl = decl;
            discard Scope_Define(sema.scope, sym);

            // Register enum variants as constants
            var vi: int = 0;
            while vi < decl.variantCount && vi < 9 {
                var v: EnumVariant;
                if vi == 0 { v = decl.variant0; }
                else if vi == 1 { v = decl.variant1; }
                else if vi == 2 { v = decl.variant2; }
                else if vi == 3 { v = decl.variant3; }
                else if vi == 4 { v = decl.variant4; }
                else if vi == 5 { v = decl.variant5; }
                else if vi == 6 { v = decl.variant6; }
                else if vi == 7 { v = decl.variant7; }
                else if vi == 8 { v = decl.variant8; }
                if v.name == null as String || String_Eq(v.name, "") {
                    vi = vi + 1;
                    continue;
                }
                let variantName: String = String_Concat(decl.strValue, "_");
                let variantName2: String = String_Concat(variantName, v.name);
                var vSym: Symbol;
                vSym.kind = skConst;
                vSym.name = variantName2;
                vSym.typeKind = tyNamed;
                vSym.typeName = String_Concat(decl.strValue, "_Tag");
                vSym.refType = null as *TypeExpr;
                vSym.isMutable = false;
                vSym.isPublic = decl.isPublic;
                vSym.decl = decl;
                discard Scope_Define(sema.scope, vSym);
                vi = vi + 1;
            }
        }

        // Const
        if dk == dkConst {
            var sym: Symbol;
            Sema_ZeroInitSymbol(&sym);
            sym.kind = skConst;
            sym.name = decl.strValue;
            if decl.constType != null as *TypeExpr {
                sym.typeKind = Sema_ResolveType(sema, decl.constType);
                sym.typeName = decl.constType.typeName;
                sym.refType = decl.constType;
            } else {
                sym.typeKind = tyInt;
            }
            sym.isMutable = false;
            sym.isPublic = decl.isPublic;
            sym.decl = decl;
            discard Scope_Define(sema.scope, sym);
        }

        // Extern function
        if dk == dkExternFunc {
            var sym: Symbol;
            Sema_ZeroInitSymbol(&sym);
            sym.kind = skFunc;
            sym.name = decl.strValue;
            sym.typeKind = tyFunc;
            sym.isPublic = true;
            sym.decl = decl;
            discard Scope_Define(sema.scope, sym);
        }

        // Interface
        if dk == dkInterface {
            if sema.interfaceCount < 64 {
                sema.interfaceTable[sema.interfaceCount].name = decl.strValue;
                sema.interfaceTable[sema.interfaceCount].decl = decl;
                sema.interfaceCount = sema.interfaceCount + 1;
            }
        }

        // Impl block (inherent methods or trait implementations)
        if dk == dkImpl {
            let implTypeName: String = decl.strValue;
            var m: *Decl = decl.childDecl1;
            while m != null as *Decl {
                if m.kind == dkFunc && sema.methodCount < 256 {
                    sema.methodEntries[sema.methodCount].typeName = implTypeName;
                    sema.methodEntries[sema.methodCount].methodName = m.strValue;
                    sema.methodEntries[sema.methodCount].decl = m;
                    sema.methodCount = sema.methodCount + 1;
                }
                m = m.childDecl2;
            }
        }

        decl = decl.childDecl2;
    }

    // Pass 2: resolve imports by looking up actual symbols
    decl = sema.module.firstItem;
    while decl != null as *Decl {
        let dk: int = decl.kind;

        if dk == dkUse {
            if decl.useKind == 1 {
                // Glob import: add all public symbols from scope
                var scope: *Scope = sema.scope;
                while scope != null as *Scope {
                    var i: int = 0;
                    while i < scope.count {
                        let sym: Symbol = scope.symbols[i];
                        if sym.isPublic {
                            let existing: Symbol = Scope_LookupLocal(sema.scope, sym.name);
                            if String_Eq(existing.name, "") {
                                discard Scope_Define(sema.scope, sym);
                            }
                        }
                        i = i + 1;
                    }
                    scope = scope.parent;
                }
            } else if decl.useKind == 2 {
                // Multi-import: resolve each name
                let namesStr: String = decl.useNames;
                if !String_Eq(namesStr, "") {
                    var start: uint = 0;
                    var pos: uint = 0;
                    let totalLen: uint = String_Len(namesStr);
                    while pos <= totalLen {
                        let atEnd: bool = pos == totalLen;
                        let isComma: bool = false;
                        if pos < totalLen {
                            let chStr: String = bux_str_slice(namesStr, pos, 1);
                            isComma = String_Eq(chStr, ",");
                        }
                        if atEnd || isComma {
                            let nameLen: uint = pos - start;
                            if nameLen > 0 {
                                let name: String = bux_str_slice(namesStr, start, nameLen);
                                let found: Symbol = Scope_Lookup(sema.scope, name);
                                if !String_Eq(found.name, "") && found.isPublic {
                                    let existing: Symbol = Scope_LookupLocal(sema.scope, name);
                                    if String_Eq(existing.name, "") {
                                        discard Scope_Define(sema.scope, found);
                                    }
                                }
                            }
                            start = pos + 1;
                        }
                        pos = pos + 1;
                    }
                }
            } else {
                // Single import: resolve last path segment
                let path: String = decl.usePath;
                if !String_Eq(path, "") {
                    var lastSeg: String = path;
                    let containsColons: int = bux_str_contains(path, "::");
                    if containsColons != 0 {
                        let pathLen: uint = String_Len(path);
                        var tryPos: uint = 0;
                        while tryPos < pathLen {
                            let slice: String = bux_str_slice(path, tryPos, pathLen - tryPos);
                            if String_StartsWith(slice, "::") {
                                lastSeg = bux_str_slice(slice, 2, String_Len(slice) - 2);
                            }
                            tryPos = tryPos + 1;
                        }
                    }
                    let found: Symbol = Scope_Lookup(sema.scope, lastSeg);
                    if !String_Eq(found.name, "") && found.isPublic {
                        let existing: Symbol = Scope_LookupLocal(sema.scope, lastSeg);
                        if String_Eq(existing.name, "") {
                            discard Scope_Define(sema.scope, found);
                        }
                    }
                }
            }
        }

        decl = decl.childDecl2;
    }
}

// ---------------------------------------------------------------------------
// Trait bounds checking
// ---------------------------------------------------------------------------

func Sema_FindInterface(sema: *Sema, name: String) -> *Decl {
    var i: int = 0;
    while i < sema.interfaceCount {
        if String_Eq(sema.interfaceTable[i].name, name) {
            return sema.interfaceTable[i].decl;
        }
        i = i + 1;
    }
    return null as *Decl;
}

func Sema_TypeHasMethod(sema: *Sema, typeName: String, methodName: String) -> bool {
    var i: int = 0;
    while i < sema.methodCount {
        if String_Eq(sema.methodEntries[i].typeName, typeName) && String_Eq(sema.methodEntries[i].methodName, methodName) {
            return true;
        }
        i = i + 1;
    }
    return false;
}

func Sema_TypeImplements(sema: *Sema, typeName: String, interfaceName: String) -> bool {
    let iface: *Decl = Sema_FindInterface(sema, interfaceName);
    if iface == null as *Decl {
        return true; // Unknown interface — be permissive
    }
    var req: *Decl = iface.childDecl1;
    while req != null as *Decl {
        if req.kind == dkFunc {
            if !Sema_TypeHasMethod(sema, typeName, req.strValue) {
                return false;
            }
        }
        req = req.childDecl2;
    }
    return true;
}

func Sema_CheckTraitBounds(sema: *Sema, funcDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int, line: uint32, col: uint32) {
    // Trait bounds checking
    if funcDecl.typeParamCount >= 1 && typeArgCount >= 1 && !String_Eq(funcDecl.typeParam0Bound, "") {
        if !Sema_TypeImplements(sema, typeArg0, funcDecl.typeParam0Bound) {
            let errMsg: String = String_Concat("type '", String_Concat(typeArg0, "' does not implement trait '"));
            let errMsg2: String = String_Concat(errMsg, String_Concat(funcDecl.typeParam0Bound, "'"));
            Sema_EmitError(sema, line, col, errMsg2);
        }
    }
    if funcDecl.typeParamCount >= 2 && typeArgCount >= 2 && !String_Eq(funcDecl.typeParam1Bound, "") {
        if !Sema_TypeImplements(sema, typeArg1, funcDecl.typeParam1Bound) {
            let errMsg: String = String_Concat("type '", String_Concat(typeArg1, "' does not implement trait '"));
            let errMsg2: String = String_Concat(errMsg, String_Concat(funcDecl.typeParam1Bound, "'"));
            Sema_EmitError(sema, line, col, errMsg2);
        }
    }
}

// ---------------------------------------------------------------------------
// Analyze — main entry point
// ---------------------------------------------------------------------------

func Sema_Analyze(mod: *Module) -> *Sema {
    let s: *Sema = bux_alloc(sizeof(Sema)) as *Sema;
    s.module = mod;
    s.scope = bux_alloc(sizeof(Scope)) as *Scope;
    s.scope.symbols = bux_alloc(1024 as uint * sizeof(Symbol)) as *Symbol;
    s.scope.count = 0;
    s.scope.parent = null as *Scope;
    s.hasError = false;
    s.diagCount = 0;
    s.diags = bux_alloc(256 as uint * sizeof(SemaDiag)) as *SemaDiag;
    s.typeTable = null as *void;
    s.methodTable = null as *void;
    s.currentRetType = tyVoid;
    s.interfaceTable = bux_alloc(64 as uint * sizeof(InterfaceEntry)) as *InterfaceEntry;
    s.interfaceCount = 0;
    s.methodEntries = bux_alloc(256 as uint * sizeof(MethodEntry)) as *MethodEntry;
    s.methodCount = 0;

    // First pass: collect globals
    Sema_CollectGlobals(s);

    // Second pass: check function bodies
    var decl: *Decl = mod.firstItem;
    while decl != null as *Decl {
        if decl.kind == dkFunc && decl.refBody != null as *Block {
            // Create function scope (child of global)
            var funcScope: Scope = Scope_NewChild(s.scope);

            // Add type params to scope
            if decl.typeParamCount >= 1 {
                var tpSym: Symbol;
                Sema_ZeroInitSymbol(&tpSym);
                tpSym.kind = skType;
                tpSym.name = decl.typeParam0;
                tpSym.typeKind = tyTypeParam;
                tpSym.decl = null as *Decl;
                discard Scope_Define(&funcScope, tpSym);
            }
            if decl.typeParamCount >= 2 {
                var tpSym2: Symbol;
                Sema_ZeroInitSymbol(&tpSym2);
                tpSym2.kind = skType;
                tpSym2.name = decl.typeParam1;
                tpSym2.typeKind = tyTypeParam;
                tpSym2.decl = null as *Decl;
                discard Scope_Define(&funcScope, tpSym2);
            }

            // Add parameters to scope
            var i: int = 0;
            while i < decl.paramCount {
                var p: *Param = null as *Param;
                if i == 0 { p = &decl.param0; }
                else if i == 1 { p = &decl.param1; }
                else if i == 2 { p = &decl.param2; }
                else if i == 3 { p = &decl.param3; }
                else if i == 4 { p = &decl.param4; }
                else if i == 5 { p = &decl.param5; }
                else if i == 6 { p = &decl.param6; }
                else if i == 7 { p = &decl.param7; }
                else if i == 8 { p = &decl.param8; }
                var pSym: Symbol;
                Sema_ZeroInitSymbol(&pSym);
                pSym.kind = skVar;
                if p != null as *Param && p.refParamType != null as *TypeExpr {
                    pSym.typeKind = Sema_ResolveType(s, p.refParamType);
                    pSym.refType = p.refParamType;
                    if p.refParamType.kind == tekPointer && p.refParamType.pointerPointee != null as *TypeExpr {
                        pSym.typeName = String_Concat(p.refParamType.pointerPointee.typeName, "*");
                    } else {
                        pSym.typeName = p.refParamType.typeName;
                    }
                } else {
                    pSym.typeKind = tyInt;
                    pSym.typeName = "";
                }
                pSym.isMutable = false;
                pSym.isPublic = false;
                pSym.decl = null as *Decl;
                if i == 0 { pSym.name = decl.param0.name; }
                else if i == 1 { pSym.name = decl.param1.name; }
                else if i == 2 { pSym.name = decl.param2.name; }
                else if i == 3 { pSym.name = decl.param3.name; }
                else if i == 4 { pSym.name = decl.param4.name; }
                else if i == 5 { pSym.name = decl.param5.name; }
                else if i == 6 { pSym.name = decl.param6.name; }
                else if i == 7 { pSym.name = decl.param7.name; }
                else if i == 8 { pSym.name = decl.param8.name; }
                discard Scope_Define(&funcScope, pSym);
                i = i + 1;
            }

            // Switch to function scope and check body statements
            let prevScope: *Scope = s.scope;
            s.scope = &funcScope;

            // Set current function return type
            if decl.retType != null as *TypeExpr {
                s.currentRetType = Sema_ResolveType(s, decl.retType);
            } else {
    s.currentRetType = tyVoid;
    s.checkedFunc = false;
    s.movedCount = 0;
            }

            // Enable borrow checking for @[Checked] functions
            let wasChecked: bool = s.checkedFunc;
            s.checkedFunc = decl.isChecked != 0;

            // Check body statements
            var stmt: *Stmt = decl.refBody.firstStmt;
            while stmt != null as *Stmt {
                Sema_CheckStmt(s, stmt);
                stmt = stmt.nextStmt;
            }

            s.checkedFunc = wasChecked;
            s.scope = prevScope;
        }
        decl = decl.childDecl2;
    }

    return s;
}

func Sema_HasError(sema: *Sema) -> bool {
    return sema.hasError;
}

func Sema_DiagCount(sema: *Sema) -> int {
    return sema.diagCount;
}

func Sema_Free(sema: *Sema) {
    bux_free(sema.scope.symbols as *void);
    bux_free(sema.scope as *void);
    bux_free(sema.diags as *void);
    bux_free(sema as *void);
}

}
