d70ccba5ca
- sema: add Sema_InferGenericArg + Sema_ExtractElemType helpers - Infers T from first argument's collection type: - Array_Push(&arr, 10) → Array_Push_int (from &arr's Array<int> type) - Iter_HasNext(&it) → Iter_HasNext_int (from &it's Iter<int> type) - Supports both explicit generic types (Array<int>) and mangled types (Array_int) - Works with reference expressions (&x) by unwrapping to x's type - Selfhost loop: C output IDENTICAL
1434 lines
56 KiB
Plaintext
1434 lines
56 KiB
Plaintext
// 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;
|
|
extern func bux_strlen(s: String) -> uint;
|
|
extern func bux_str_slice(s: String, start: uint, len: uint) -> 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;
|
|
}
|
|
}
|
|
// Trait bounds checking for explicit generic calls: Max<Circle>(...)
|
|
// Must happen before indirect/direct call returns
|
|
if expr.child1.kind == ekIdent {
|
|
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
|
|
if sym.kind == skFunc && sym.decl != null as *Decl {
|
|
// Implicit generic type argument inference
|
|
if expr.child1.genericTypeArgCount == 0 && sym.decl.typeParamCount > 0 {
|
|
let inferred: String = Sema_InferGenericArg(sema, sym.decl, expr);
|
|
if !String_Eq(inferred, "") {
|
|
expr.child1.genericTypeArgCount = 1;
|
|
expr.child1.genericTypeArg0 = inferred;
|
|
}
|
|
}
|
|
if expr.child1.genericTypeArgCount > 0 {
|
|
Sema_CheckTraitBounds(sema, sym.decl, expr.child1.genericTypeArg0, expr.child1.genericTypeArg1, expr.child1.genericTypeArgCount, expr.line, expr.column);
|
|
}
|
|
}
|
|
}
|
|
// 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 {
|
|
expr.refType = expr.child1.refType.funcRet;
|
|
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 && sym.decl != null as *Decl {
|
|
if sym.decl.retType != null as *TypeExpr {
|
|
expr.refType = sym.decl.retType;
|
|
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;
|
|
}
|
|
|
|
// Extract element type name from a collection TypeExpr.
|
|
// Handles both explicit generic types (Array<int>) and mangled types (Array_int).
|
|
func Sema_ExtractElemType(te: *TypeExpr) -> String {
|
|
if te == null as *TypeExpr { return ""; }
|
|
// Pointer: unwrap and recurse
|
|
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
|
return Sema_ExtractElemType(te.pointerPointee);
|
|
}
|
|
if te.kind == tekNamed {
|
|
// Explicit generic: Array<int>
|
|
if String_Eq(te.typeName, "Array") || String_Eq(te.typeName, "Iter") {
|
|
if te.typeArgCount > 0 {
|
|
return te.typeArgName0;
|
|
}
|
|
}
|
|
// Mangled: Array_int, Iter_string
|
|
let name: String = te.typeName;
|
|
let len: uint = bux_strlen(name);
|
|
// Check for "Array_" prefix
|
|
if len > 6 && name[0] as int == 65 && name[1] as int == 114 && name[2] as int == 114 && name[3] as int == 97 && name[4] as int == 121 && name[5] as int == 95 {
|
|
return bux_str_slice(name, 6, len - 6);
|
|
}
|
|
// Check for "Iter_" prefix
|
|
if len > 5 && name[0] as int == 73 && name[1] as int == 116 && name[2] as int == 101 && name[3] as int == 114 && name[4] as int == 95 {
|
|
return bux_str_slice(name, 5, len - 5);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Infer generic type argument from call arguments.
|
|
// Supports Array_* and Iter_* stdlib functions.
|
|
func Sema_InferGenericArg(sema: *Sema, funcDecl: *Decl, expr: *Expr) -> String {
|
|
if expr.callArgs == null as *ExprList { return ""; }
|
|
let firstArg: *Expr = expr.callArgs.expr;
|
|
if firstArg == null as *Expr { return ""; }
|
|
// Check first argument's type
|
|
let argType: *TypeExpr = firstArg.refType;
|
|
if argType == null as *TypeExpr && firstArg.kind == ekUnary && firstArg.intValue == tkAmp {
|
|
// &x: use x's type
|
|
argType = firstArg.child1.refType;
|
|
}
|
|
let elemType: String = Sema_ExtractElemType(argType);
|
|
if !String_Eq(elemType, "") {
|
|
return elemType;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
}
|