f0f94f30e1
- parser: add parserIsTypeArgListAhead() for Func<Type>(args) and Type<T> { ... }
- hir_lower: method call desugaring with typeName tracking, self keyword,
ekField typeName propagation, variable type tracking, strip '*' suffix
from pointer type names in method calls, fix externFuncs buffer overflow
(64 -> 256 elements) that corrupted structCount
- qbe_backend: QBE_FindStruct, QBE_FieldOffset, QBE_TypeSize helpers;
hStructInit field-by-field codegen; hFieldPtr with offset + auto-load;
hAssign/hStore special case for hFieldPtr (generate pointer, not value);
fix null as *HirNode -> null as *void for extraData comparisons
423 lines
14 KiB
Plaintext
423 lines
14 KiB
Plaintext
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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; }
|
|
return kind >= tyInt8 && kind <= tyUInt64;
|
|
}
|
|
|
|
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 "?";
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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 {
|
|
let sym: Symbol = Scope_Lookup(sema.scope, expr.strValue);
|
|
if sym.kind == 0 && !String_Eq(sym.name, expr.strValue) {
|
|
Sema_EmitError(sema, expr.line, expr.column, "undeclared identifier");
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Call
|
|
if kind == ekCall {
|
|
let callee: int = Sema_CheckExpr(sema, expr.child1);
|
|
if callee == tyUnknown { return tyUnknown; }
|
|
// Assume callee returns same type (simplified)
|
|
return callee;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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 {
|
|
sym.typeName = stmt.refStmtType.typeName;
|
|
}
|
|
sym.isMutable = stmt.boolValue;
|
|
sym.isPublic = false;
|
|
discard Scope_Define(sema.scope, sym);
|
|
return;
|
|
}
|
|
|
|
// Return
|
|
if kind == skReturn {
|
|
if stmt.child1 != null as *Expr {
|
|
discard Sema_CheckExpr(sema, stmt.child1);
|
|
}
|
|
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");
|
|
}
|
|
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");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Expression statement
|
|
if kind == skExpr && stmt.child1 != null as *Expr {
|
|
discard Sema_CheckExpr(sema, stmt.child1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Collect globals (register functions, structs, enums in scope)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func Sema_CollectGlobals(sema: *Sema) {
|
|
var decl: *Decl = sema.module.firstItem;
|
|
while decl != null as *Decl {
|
|
let dk: int = decl.kind;
|
|
|
|
// Function
|
|
if dk == dkFunc {
|
|
var sym: Symbol;
|
|
sym.kind = skFunc;
|
|
sym.name = decl.strValue;
|
|
sym.typeKind = tyFunc;
|
|
sym.isPublic = decl.isPublic;
|
|
discard Scope_Define(sema.scope, sym);
|
|
}
|
|
|
|
// Struct
|
|
if dk == dkStruct {
|
|
var sym: Symbol;
|
|
sym.kind = skType;
|
|
sym.name = decl.strValue;
|
|
sym.typeKind = tyNamed;
|
|
sym.isPublic = decl.isPublic;
|
|
discard Scope_Define(sema.scope, sym);
|
|
}
|
|
|
|
// Enum
|
|
if dk == dkEnum {
|
|
var sym: Symbol;
|
|
sym.kind = skType;
|
|
sym.name = decl.strValue;
|
|
sym.typeKind = tyNamed;
|
|
sym.isPublic = decl.isPublic;
|
|
discard Scope_Define(sema.scope, sym);
|
|
}
|
|
|
|
// Extern function
|
|
if dk == dkExternFunc {
|
|
var sym: Symbol;
|
|
sym.kind = skFunc;
|
|
sym.name = decl.strValue;
|
|
sym.typeKind = tyFunc;
|
|
sym.isPublic = true;
|
|
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;
|
|
|
|
// 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;
|
|
tpSym.kind = skType;
|
|
tpSym.name = decl.typeParam0;
|
|
tpSym.typeKind = tyTypeParam;
|
|
discard Scope_Define(&funcScope, tpSym);
|
|
}
|
|
if decl.typeParamCount >= 2 {
|
|
var tpSym2: Symbol;
|
|
tpSym2.kind = skType;
|
|
tpSym2.name = decl.typeParam1;
|
|
tpSym2.typeKind = tyTypeParam;
|
|
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; }
|
|
var pSym: Symbol;
|
|
pSym.kind = skVar;
|
|
if p != null as *Param && p.refParamType != null as *TypeExpr {
|
|
pSym.typeKind = Sema_ResolveType(s, p.refParamType);
|
|
pSym.typeName = p.refParamType.typeName;
|
|
} else {
|
|
pSym.typeKind = tyInt;
|
|
pSym.typeName = "";
|
|
}
|
|
pSym.isMutable = false;
|
|
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; }
|
|
discard Scope_Define(&funcScope, pSym);
|
|
i = i + 1;
|
|
}
|
|
|
|
// Switch to function scope and check body statements
|
|
let prevScope: *Scope = s.scope;
|
|
s.scope = &funcScope;
|
|
|
|
// Check body (simplified — just count statements)
|
|
var stmtCount: int = decl.refBody.stmtCount;
|
|
// In a full implementation, iterate statements and check each
|
|
|
|
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);
|
|
}
|
|
}
|