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

// ---------------------------------------------------------------------------
// 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
}

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

// ---------------------------------------------------------------------------
// 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.isMutable = false;
    sym.isPublic = false;
    sym.decl = null as *Decl;
}

// ---------------------------------------------------------------------------
// 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 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; }

    // Check type table for user-defined types (StringMap not yet supported)
    // TODO: re-enable when StringMap generic is available
    // if sema.typeTable != null as *void { ... }
    return tyNamed; // assume named type
}

// ---------------------------------------------------------------------------
// 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;
}

func Sema_TypeName(kind: int) -> String {
    if kind == tyUnknown { return "?"; }
    if kind == tyVoid { return "void"; }
    if kind == tyBool { return "bool"; }
    if kind == tyInt{ return "int"; }
    if kind == tyInt64 { return "int64"; }
    if kind == tyUInt { return "uint"; }
    if kind == tyFloat64 { return "float64"; }
    if kind == tyStr { return "String"; }
    if kind == tyPointer { return "*ptr"; }
    if kind == tyNamed { return "user-type"; }
    if kind == tyTypeParam { return "type-param"; }
    return "?";
}

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

func Sema_CheckBlock(sema: *Sema, block: *Block) {
    if block == null as *Block { return; }
    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;
}

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

func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
    if expr == null as *Expr { return tyUnknown; }
    let kind: int = expr.kind;
    // Debug: print expr kind
    // PrintInt(kind as int64);
    // PrintLine("");

    // 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 {
        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.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;
        }
        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;

        // 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 { return tyPointer; }
        return operand;
    }

    // Assign
    if kind == ekAssign {
        let target: int = Sema_CheckExpr(sema, expr.child1);
        let value: int = Sema_CheckExpr(sema, expr.child2);
        // Basic type compatibility (permissive for now)
        return target;
    }

    // Call
    if kind == ekCall {
        discard Sema_CheckExpr(sema, expr.child1);
        var arg: *ExprList = expr.callArgs;
        while arg != null as *ExprList {
            discard Sema_CheckExpr(sema, arg.expr);
            arg = arg.next;
        }
        // Try to resolve return type from function declaration
        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 {
            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;
    }

    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);
        // Register variable in scope
        var sym: Symbol;
        sym.kind = skVar;
        sym.name = stmt.strValue;
        sym.typeKind = initType;
        sym.typeName = "";
        if stmt.refStmtType != null as *TypeExpr {
            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;
            }
        }
        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 {
        discard 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.isMutable = true;
        loopSym.isPublic = false;
        loopSym.decl = null as *Decl;
        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;
    while decl != null as *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.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 < 8 {
                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; }
                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.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;
            } else {
                sym.typeKind = tyInt;
            }
            sym.isMutable = false;
            sym.isPublic = decl.isPublic;
            sym.decl = decl;
            discard Scope_Define(sema.scope, sym);
        }

        // Use (import)
        if dk == dkUse {
            if decl.useKind == 2 {
                // Multi-import: names are comma-separated in useNames
                // For now, register the whole useNames string as a single func name
                // (permissive fallback — real fix needs string split)
                let namesStr: String = decl.useNames;
                if !String_Eq(namesStr, "") {
                    // Simple split by comma using available string functions
                    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 {
                            // Check if character at pos is comma
                            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);
                                var sym: Symbol;
                                sym.kind = skFunc;
                                sym.name = name;
                                sym.typeKind = tyUnknown;
                                sym.typeName = "";
                                sym.isMutable = false;
                                sym.isPublic = true;
                                sym.decl = null as *Decl;
                                let existing: Symbol = Scope_LookupLocal(sema.scope, name);
                                if !String_Eq(existing.name, name) {
                                    discard Scope_Define(sema.scope, sym);
                                }
                            }
                            start = pos + 1;
                        }
                        pos = pos + 1;
                    }
                }
            } else {
                // Single import or glob — add last path segment
                let path: String = decl.usePath;
                if !String_Eq(path, "") {
                    // Find last :: segment
                    var lastSeg: String = path;
                    let containsColons: int = bux_str_contains(path, "::");
                    if containsColons != 0 {
                        // Try to get last segment by finding last ::
                        // Use a simple heuristic: slice from various positions
                        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;
                        }
                    }
                    var sym: Symbol;
                    sym.kind = skFunc;
                    sym.name = lastSeg;
                    sym.typeKind = tyUnknown;
                    sym.typeName = "";
                    sym.isMutable = false;
                    sym.isPublic = true;
                    sym.decl = null as *Decl;
                    let existing: Symbol = Scope_LookupLocal(sema.scope, lastSeg);
                    if !String_Eq(existing.name, lastSeg) {
                        discard Scope_Define(sema.scope, sym);
                    }
                }
            }
        }

        // Const
        if dk == dkConst {
            var sym: Symbol;
            Sema_ZeroInitSymbol(&sym);
            sym.kind = skConst;
            sym.name = decl.strValue;
            sym.typeKind = tyUnknown;
            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);
        }

        decl = decl.childDecl2;
    }
}

// ---------------------------------------------------------------------------
// 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;

    // 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);
                    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;
            }

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

            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);
}

}
