selfhost: trait bounds parsing, sema checks, and generic monomorphization fix
- AST: add typeParam0Bound/typeParam1Bound to Decl, strValue2 to dkImpl - Parser: parse <T: Trait> bounds, Self type in interfaces, extend-for blocks - Sema: add interfaceTable/methodTable, Sema_CheckTraitBounds, Sema_TypeImplements - HIR lower: two-pass decl iteration — collect generic funcs before lowering bodies Fixes Max<Circle>(...) not generating Max_Circle when caller precedes callee - C backend: skip generic funcs in emission (only emit monomorphized instances) - Array.bux: fix for-loop over monomorphized Array types - All debug prints removed; selfhost loop passes (C output IDENTICAL)
This commit is contained in:
@@ -281,6 +281,9 @@ struct Decl {
|
||||
typeParam0: String,
|
||||
typeParam1: String,
|
||||
typeParamCount: int,
|
||||
// Trait bounds for type params (e.g. <T: Comparable>)
|
||||
typeParam0Bound: String,
|
||||
typeParam1Bound: String,
|
||||
// Params (for functions)
|
||||
paramCount: int,
|
||||
param0: Param,
|
||||
@@ -392,6 +395,8 @@ func Ast_MakeDecl(kind: int, line: uint32, col: uint32) -> Decl {
|
||||
return Decl { kind: kind, line: line, column: col, isPublic: false,
|
||||
strValue: "", strValue2: "",
|
||||
typeParam0: "", typeParam1: "", typeParamCount: 0,
|
||||
typeParam0Bound: "", typeParam1Bound: ""
|
||||
,
|
||||
paramCount: 0,
|
||||
retType: null as *TypeExpr,
|
||||
refBody: null as *Block,
|
||||
|
||||
@@ -714,6 +714,12 @@ func CBE_FuncHasGeneric(f: *HirFunc) -> bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
func CBE_IsArrayTypeName(name: String) -> bool {
|
||||
if String_Eq(name, "") { return false; }
|
||||
if String_StartsWith(name, "Array") { return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
func CBE_IsPrimitiveTypeName(name: String) -> bool {
|
||||
if String_Eq(name, "int") || String_Eq(name, "") { return true; }
|
||||
if String_Eq(name, "String") { return true; }
|
||||
|
||||
+189
-35
@@ -31,6 +31,8 @@ struct LowerCtx {
|
||||
substArg0: String,
|
||||
substParam1: String,
|
||||
substArg1: String,
|
||||
// Borrow checker state
|
||||
checkedFunc: bool,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -346,6 +348,46 @@ func Lcx_GenerateFuncInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String,
|
||||
return mangled;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Array type helpers for bounds-checking desugaring
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Lcx_IsArrayTypeExpr(te: *TypeExpr) -> bool {
|
||||
if te == null as *TypeExpr { return false; }
|
||||
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
||||
te = te.pointerPointee;
|
||||
}
|
||||
if te.kind == tekNamed {
|
||||
if String_Eq(te.typeName, "Array") { return true; }
|
||||
let name: String = te.typeName;
|
||||
if 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 true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func Lcx_GetArrayElemType(te: *TypeExpr) -> String {
|
||||
if te == null as *TypeExpr { return ""; }
|
||||
if te.kind == tekPointer && te.pointerPointee != null as *TypeExpr {
|
||||
te = te.pointerPointee;
|
||||
}
|
||||
if te.kind == tekNamed {
|
||||
if String_Eq(te.typeName, "Array") && te.typeArgCount > 0 {
|
||||
return te.typeArgName0;
|
||||
}
|
||||
let name: String = te.typeName;
|
||||
if 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 {
|
||||
let prefixLen: uint = 6;
|
||||
let totalLen: uint = bux_strlen(name);
|
||||
if totalLen > prefixLen {
|
||||
return bux_str_slice(name, prefixLen, totalLen - prefixLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Expression lowering
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -675,6 +717,64 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
|
||||
// Index: arr[idx]
|
||||
if kind == ekIndex {
|
||||
// In @[Checked] functions, Array access goes through Array_Get with bounds check
|
||||
if ctx.checkedFunc && expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) {
|
||||
let elemType: String = Lcx_GetArrayElemType(expr.child1.refType);
|
||||
if !String_Eq(elemType, "") {
|
||||
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
||||
let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
||||
|
||||
var isPtr: bool = false;
|
||||
if expr.child1.refType != null as *TypeExpr && expr.child1.refType.kind == tekPointer {
|
||||
isPtr = true;
|
||||
}
|
||||
|
||||
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
callNode.kind = hCall;
|
||||
callNode.strValue = Lcx_MangleName("Array_Get", elemType, "", 1);
|
||||
callNode.line = line;
|
||||
callNode.column = col;
|
||||
|
||||
let genGet: *Decl = Lcx_FindGenericFunc(ctx, "Array_Get");
|
||||
if genGet != null as *Decl {
|
||||
Lcx_GenerateFuncInstance(ctx, genGet, elemType, "", 1);
|
||||
}
|
||||
|
||||
if isPtr {
|
||||
callNode.child1 = baseNode;
|
||||
} else {
|
||||
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
addrNode.kind = hUnary;
|
||||
addrNode.intValue = tkAmp;
|
||||
addrNode.child1 = baseNode;
|
||||
callNode.child1 = addrNode;
|
||||
}
|
||||
callNode.child2 = idxNode;
|
||||
callNode.extraCount = 0;
|
||||
callNode.extraData = null as *void;
|
||||
|
||||
return callNode;
|
||||
}
|
||||
}
|
||||
|
||||
// For Array<T> or *Array<T>, desugar arr[idx] → arr.data[idx]
|
||||
if expr.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.refType) {
|
||||
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
|
||||
let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
||||
|
||||
let fieldPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
fieldPtr.kind = hFieldPtr;
|
||||
fieldPtr.line = line;
|
||||
fieldPtr.column = col;
|
||||
fieldPtr.strValue = "data";
|
||||
fieldPtr.child1 = baseNode;
|
||||
|
||||
n.kind = hIndexPtr;
|
||||
n.child1 = fieldPtr;
|
||||
n.child2 = idxNode;
|
||||
return n;
|
||||
}
|
||||
|
||||
n.kind = hIndexPtr;
|
||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1);
|
||||
n.child2 = Lcx_LowerExpr(ctx, expr.child2);
|
||||
@@ -683,6 +783,52 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
|
||||
|
||||
// Assign: target = value
|
||||
if kind == ekAssign {
|
||||
// Array bounds-checking for write in @[Checked]: arr[idx] = val → Array_Set_T(&arr, idx, val)
|
||||
// Only for plain assignment (=), not compound operators (+=, -=, etc.)
|
||||
if expr.intValue == tkAssign && ctx.checkedFunc && expr.child1 != null as *Expr && expr.child1.kind == ekIndex && expr.child1.child1 != null as *Expr && Lcx_IsArrayTypeExpr(expr.child1.child1.refType) {
|
||||
let elemType: String = Lcx_GetArrayElemType(expr.child1.child1.refType);
|
||||
if !String_Eq(elemType, "") {
|
||||
let baseNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child1);
|
||||
let idxNode: *HirNode = Lcx_LowerExpr(ctx, expr.child1.child2);
|
||||
let valNode: *HirNode = Lcx_LowerExpr(ctx, expr.child2);
|
||||
|
||||
var isPtr: bool = false;
|
||||
if expr.child1.child1.refType != null as *TypeExpr && expr.child1.child1.refType.kind == tekPointer {
|
||||
isPtr = true;
|
||||
}
|
||||
|
||||
let callNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
callNode.kind = hCall;
|
||||
callNode.strValue = Lcx_MangleName("Array_Set", elemType, "", 1);
|
||||
callNode.line = line;
|
||||
callNode.column = col;
|
||||
|
||||
let genSet: *Decl = Lcx_FindGenericFunc(ctx, "Array_Set");
|
||||
if genSet != null as *Decl {
|
||||
Lcx_GenerateFuncInstance(ctx, genSet, elemType, "", 1);
|
||||
}
|
||||
|
||||
if isPtr {
|
||||
callNode.child1 = baseNode;
|
||||
} else {
|
||||
let addrNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
|
||||
addrNode.kind = hUnary;
|
||||
addrNode.intValue = tkAmp;
|
||||
addrNode.child1 = baseNode;
|
||||
callNode.child1 = addrNode;
|
||||
}
|
||||
callNode.child2 = idxNode;
|
||||
|
||||
let extra: *HirArgList = bux_alloc(sizeof(HirArgList)) as *HirArgList;
|
||||
extra.node = valNode;
|
||||
extra.next = null as *HirArgList;
|
||||
callNode.extraData = extra as *void;
|
||||
callNode.extraCount = 1;
|
||||
|
||||
return callNode;
|
||||
}
|
||||
}
|
||||
|
||||
n.kind = hAssign;
|
||||
n.child1 = Lcx_LowerExpr(ctx, expr.child1); // target
|
||||
n.child2 = Lcx_LowerExpr(ctx, expr.child2); // value
|
||||
@@ -1434,6 +1580,9 @@ func Lcx_LowerParam(out: *HirParam, p: *Param, ctx: *LowerCtx) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
||||
let oldChecked: bool = ctx.checkedFunc;
|
||||
ctx.checkedFunc = decl.isChecked != 0;
|
||||
|
||||
let f: *HirFunc = bux_alloc(sizeof(HirFunc)) as *HirFunc;
|
||||
f.name = decl.strValue;
|
||||
f.isPublic = decl.isPublic;
|
||||
@@ -1516,6 +1665,7 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
|
||||
f.body = null as *HirNode;
|
||||
}
|
||||
ctx.scope = prevScope;
|
||||
ctx.checkedFunc = oldChecked;
|
||||
|
||||
return f;
|
||||
}
|
||||
@@ -1685,50 +1835,54 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
|
||||
// Second pass: actually collect them
|
||||
// For simplicity, do single pass with pre-allocated field arrays
|
||||
|
||||
// Iterate declarations
|
||||
// Pass 1: collect generic declarations for monomorphization
|
||||
var decl: *Decl = mod.firstItem;
|
||||
while decl != null as *Decl {
|
||||
// Collect generic declarations for monomorphization
|
||||
if decl.kind == dkFunc && decl.typeParamCount > 0 {
|
||||
ctx.genFuncs[ctx.genFuncCount] = *decl;
|
||||
ctx.genFuncCount = ctx.genFuncCount + 1;
|
||||
}
|
||||
if decl.kind == dkStruct {
|
||||
if decl.typeParamCount > 0 {
|
||||
ctx.genStructs[ctx.genStructCount] = *decl;
|
||||
ctx.genStructCount = ctx.genStructCount + 1;
|
||||
} else if !String_Eq(decl.strValue, "") {
|
||||
// Collect struct definition for C codegen
|
||||
let si: int = hm.structCount;
|
||||
hm.structs[si].name = decl.strValue;
|
||||
hm.structs[si].fieldCount = decl.fieldCount;
|
||||
hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
|
||||
var fi: int = 0;
|
||||
while fi < decl.fieldCount {
|
||||
var fname: String = "";
|
||||
var ftype: *TypeExpr = null as *TypeExpr;
|
||||
fname = decl.fields[fi].name;
|
||||
ftype = decl.fields[fi].refFieldType;
|
||||
// Skip empty field names
|
||||
if String_Eq(fname, "") {
|
||||
fi = fi + 1;
|
||||
continue;
|
||||
}
|
||||
hm.structs[si].fields[fi].name = fname;
|
||||
if ftype != null as *TypeExpr {
|
||||
if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr {
|
||||
// Pointer type: emit "TypeName*"
|
||||
if !String_Eq(ftype.pointerPointee.typeName, "") {
|
||||
hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*");
|
||||
}
|
||||
} else if !String_Eq(ftype.typeName, "") {
|
||||
hm.structs[si].fields[fi].typeName = ftype.typeName;
|
||||
}
|
||||
}
|
||||
if decl.kind == dkStruct && decl.typeParamCount > 0 {
|
||||
ctx.genStructs[ctx.genStructCount] = *decl;
|
||||
ctx.genStructCount = ctx.genStructCount + 1;
|
||||
}
|
||||
decl = decl.childDecl2;
|
||||
}
|
||||
|
||||
// Pass 2: lower all declarations
|
||||
decl = mod.firstItem;
|
||||
while decl != null as *Decl {
|
||||
if decl.kind == dkStruct && decl.typeParamCount == 0 && !String_Eq(decl.strValue, "") {
|
||||
// Collect struct definition for C codegen
|
||||
let si: int = hm.structCount;
|
||||
hm.structs[si].name = decl.strValue;
|
||||
hm.structs[si].fieldCount = decl.fieldCount;
|
||||
hm.structs[si].fields = bux_alloc(decl.fieldCount as uint * sizeof(HirStructField)) as *HirStructField;
|
||||
var fi: int = 0;
|
||||
while fi < decl.fieldCount {
|
||||
var fname: String = "";
|
||||
var ftype: *TypeExpr = null as *TypeExpr;
|
||||
fname = decl.fields[fi].name;
|
||||
ftype = decl.fields[fi].refFieldType;
|
||||
// Skip empty field names
|
||||
if String_Eq(fname, "") {
|
||||
fi = fi + 1;
|
||||
continue;
|
||||
}
|
||||
hm.structCount = hm.structCount + 1;
|
||||
hm.structs[si].fields[fi].name = fname;
|
||||
if ftype != null as *TypeExpr {
|
||||
if ftype.kind == tekPointer && ftype.pointerPointee != null as *TypeExpr {
|
||||
// Pointer type: emit "TypeName*"
|
||||
if !String_Eq(ftype.pointerPointee.typeName, "") {
|
||||
hm.structs[si].fields[fi].typeName = String_Concat(ftype.pointerPointee.typeName, "*");
|
||||
}
|
||||
} else if !String_Eq(ftype.typeName, "") {
|
||||
hm.structs[si].fields[fi].typeName = ftype.typeName;
|
||||
}
|
||||
}
|
||||
fi = fi + 1;
|
||||
}
|
||||
hm.structCount = hm.structCount + 1;
|
||||
}
|
||||
if decl.kind == dkFunc && decl.refBody != null as *Block {
|
||||
let f: *HirFunc = Lcx_LowerFunc(ctx, decl);
|
||||
|
||||
+109
-36
@@ -232,6 +232,15 @@ func parserParseType(p: *Parser) -> *TypeExpr {
|
||||
|
||||
// name
|
||||
let nameTok: LexToken = parserExpect(p, tkIdent, "expected type name");
|
||||
// self / Self -> tekSelf
|
||||
if String_Eq(nameTok.text, "self") || String_Eq(nameTok.text, "Self") {
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekSelf;
|
||||
te.line = nameTok.line;
|
||||
te.column = nameTok.column;
|
||||
te.typeName = "Self";
|
||||
return te;
|
||||
}
|
||||
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
|
||||
te.kind = tekNamed;
|
||||
te.line = nameTok.line;
|
||||
@@ -473,7 +482,6 @@ func parserParseClosure(p: *Parser) -> *Expr {
|
||||
|
||||
func parserParsePostfixExpr(p: *Parser) -> *Expr {
|
||||
var left: *Expr = parserParsePrimary(p);
|
||||
|
||||
while true {
|
||||
let kind: int = parserPeek(p, 0);
|
||||
|
||||
@@ -700,7 +708,7 @@ func parserParsePostfixExpr(p: *Parser) -> *Expr {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func parserPrecedence(op: int) -> int {
|
||||
if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign { return 1; }
|
||||
// Assignment operators are parsed by parserParseAssign, not here
|
||||
if op == tkPipePipe { return 2; }
|
||||
if op == tkAmpAmp { return 3; }
|
||||
if op == tkPipe { return 4; }
|
||||
@@ -801,12 +809,31 @@ func parserParseTernary(p: *Parser) -> *Expr {
|
||||
return left;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Assignment: target = value (right-associative)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func parserParseAssign(p: *Parser) -> *Expr {
|
||||
let left: *Expr = parserParseTernary(p);
|
||||
let op: int = parserPeek(p, 0);
|
||||
if op == tkAssign || op == tkPlusAssign || op == tkMinusAssign || op == tkStarAssign || op == tkSlashAssign || op == tkPercentAssign || op == tkAmpAssign || op == tkPipeAssign || op == tkCaretAssign || op == tkShlAssign || op == tkShrAssign {
|
||||
let opTok: LexToken = parserAdvance(p);
|
||||
let right: *Expr = parserParseAssign(p);
|
||||
let e: *Expr = parserMakeExpr(ekAssign, opTok.line, opTok.column);
|
||||
e.intValue = opTok.kind;
|
||||
e.child1 = left;
|
||||
e.child2 = right;
|
||||
return e;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Top-level expression
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func parserParseExpr(p: *Parser) -> *Expr {
|
||||
return parserParseTernary(p);
|
||||
return parserParseAssign(p);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1227,6 +1254,32 @@ func parserParseParamList(p: *Parser) -> *Decl {
|
||||
return d;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Type parameters: <T: Bound, U: Bound2>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func parserParseTypeParams(p: *Parser, d: *Decl) {
|
||||
if !parserCheck(p, tkLt) { return; }
|
||||
discard parserAdvance(p);
|
||||
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
|
||||
d.typeParam0 = tp0.text;
|
||||
d.typeParamCount = 1;
|
||||
if parserMatch(p, tkColon) {
|
||||
let bound0: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
|
||||
d.typeParam0Bound = bound0.text;
|
||||
}
|
||||
if parserMatch(p, tkComma) {
|
||||
let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
|
||||
d.typeParam1 = tp1.text;
|
||||
d.typeParamCount = 2;
|
||||
if parserMatch(p, tkColon) {
|
||||
let bound1: LexToken = parserExpect(p, tkIdent, "expected trait bound name");
|
||||
d.typeParam1Bound = bound1.text;
|
||||
}
|
||||
}
|
||||
discard parserExpect(p, tkGt, "expected '>'");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Declarations
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1245,19 +1298,8 @@ func parserParseFuncDecl(p: *Parser, isPublic: bool, isExtern: bool, isAsync: bo
|
||||
d.isAsync = isAsync;
|
||||
d.strValue = nameTok.text;
|
||||
|
||||
// Type params <T, U>
|
||||
if parserCheck(p, tkLt) {
|
||||
discard parserAdvance(p);
|
||||
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
|
||||
d.typeParam0 = tp0.text;
|
||||
d.typeParamCount = 1;
|
||||
if parserMatch(p, tkComma) {
|
||||
let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
|
||||
d.typeParam1 = tp1.text;
|
||||
d.typeParamCount = 2;
|
||||
}
|
||||
discard parserExpect(p, tkGt, "expected '>'");
|
||||
}
|
||||
// Type params <T: Bound, U: Bound2>
|
||||
parserParseTypeParams(p, d);
|
||||
|
||||
// Params
|
||||
let params: *Decl = parserParseParamList(p);
|
||||
@@ -1305,19 +1347,8 @@ func parserParseStructDecl(p: *Parser, isPublic: bool) -> *Decl {
|
||||
d.strValue = nameTok.text;
|
||||
var fieldCount: int = 0;
|
||||
|
||||
// Type params
|
||||
if parserCheck(p, tkLt) {
|
||||
discard parserAdvance(p);
|
||||
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
|
||||
d.typeParam0 = tp0.text;
|
||||
d.typeParamCount = 1;
|
||||
if parserMatch(p, tkComma) {
|
||||
let tp1: LexToken = parserExpect(p, tkIdent, "expected type param");
|
||||
d.typeParam1 = tp1.text;
|
||||
d.typeParamCount = 2;
|
||||
}
|
||||
discard parserExpect(p, tkGt, "expected '>'");
|
||||
}
|
||||
// Type params <T: Bound, U: Bound2>
|
||||
parserParseTypeParams(p, d);
|
||||
|
||||
discard parserExpect(p, tkLBrace, "expected '{'");
|
||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||
@@ -1487,6 +1518,47 @@ func parserParseExternDecl(p: *Parser, isPublic: bool) -> *Decl {
|
||||
return d;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interface declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func parserParseInterfaceDecl(p: *Parser, isPublic: bool) -> *Decl {
|
||||
let line: uint32 = parserCurToken(p).line;
|
||||
let col: uint32 = parserCurToken(p).column;
|
||||
discard parserExpect(p, tkInterface, "expected 'interface'");
|
||||
let nameTok: LexToken = parserExpect(p, tkIdent, "expected interface name");
|
||||
discard parserExpect(p, tkLBrace, "expected '{' to start interface body");
|
||||
|
||||
let d: *Decl = bux_alloc(sizeof(Decl)) as *Decl;
|
||||
d.kind = dkInterface;
|
||||
d.line = line;
|
||||
d.column = col;
|
||||
d.isPublic = isPublic;
|
||||
d.strValue = nameTok.text;
|
||||
|
||||
var methods: *Decl = null as *Decl;
|
||||
var lastMethod: *Decl = null as *Decl;
|
||||
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
|
||||
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
|
||||
if parserCheck(p, tkFunc) {
|
||||
let m: *Decl = parserParseFuncDecl(p, false, false, false);
|
||||
if methods == null as *Decl {
|
||||
methods = m;
|
||||
lastMethod = m;
|
||||
} else {
|
||||
lastMethod.childDecl2 = m;
|
||||
lastMethod = m;
|
||||
}
|
||||
d.methodCount = d.methodCount + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
d.childDecl1 = methods;
|
||||
discard parserExpect(p, tkRBrace, "expected '}' to close interface");
|
||||
return d;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Top-level declaration
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1538,6 +1610,7 @@ func parserParseDecl(p: *Parser) -> *Decl {
|
||||
if kind == tkEnum { return parserParseEnumDecl(p, isPublic); }
|
||||
if kind == tkImport { return parserParseImportDecl(p, isPublic); }
|
||||
if kind == tkExtern { return parserParseExternDecl(p, isPublic); }
|
||||
if kind == tkInterface { return parserParseInterfaceDecl(p, isPublic); }
|
||||
|
||||
if kind == tkExtend {
|
||||
discard parserAdvance(p);
|
||||
@@ -1552,13 +1625,13 @@ func parserParseDecl(p: *Parser) -> *Decl {
|
||||
d.isPublic = isPublic;
|
||||
d.strValue = typeName.text;
|
||||
|
||||
// Optional <T>
|
||||
if parserCheck(p, tkLt) {
|
||||
discard parserAdvance(p);
|
||||
let tp0: LexToken = parserExpect(p, tkIdent, "expected type param");
|
||||
d.typeParam0 = tp0.text;
|
||||
d.typeParamCount = 1;
|
||||
discard parserExpect(p, tkGt, "expected '>'");
|
||||
// Optional <T: Bound>
|
||||
parserParseTypeParams(p, d);
|
||||
|
||||
// Optional 'for InterfaceName'
|
||||
if parserMatch(p, tkFor) {
|
||||
let ifaceName: LexToken = parserExpect(p, tkIdent, "expected interface name");
|
||||
d.strValue2 = ifaceName.text;
|
||||
}
|
||||
|
||||
discard parserExpect(p, tkLBrace, "expected '{'");
|
||||
|
||||
+121
-3
@@ -2,6 +2,9 @@
|
||||
// Validates types, resolves identifiers, checks function calls.
|
||||
module Sema {
|
||||
|
||||
extern func bux_string_concat(a: String, b: String) -> String;
|
||||
extern func bux_int_to_str(n: int64) -> String;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Sema context
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -27,6 +30,11 @@ struct Sema {
|
||||
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 {
|
||||
@@ -35,6 +43,21 @@ struct SemaDiag {
|
||||
message: String;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Interface / Method tables for trait bounds checking
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct InterfaceEntry {
|
||||
name: String,
|
||||
decl: *Decl,
|
||||
}
|
||||
|
||||
struct MethodEntry {
|
||||
typeName: String,
|
||||
methodName: String,
|
||||
decl: *Decl,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Diagnostics
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -556,9 +579,13 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
|
||||
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);
|
||||
}
|
||||
// Trait bounds checking for explicit generic calls: Max<Circle>(...)
|
||||
if expr.child1.genericTypeArgCount > 0 {
|
||||
Sema_CheckTraitBounds(sema, sym.decl, expr.child1.genericTypeArg0, expr.child1.genericTypeArg1, expr.child1.genericTypeArgCount, expr.line, expr.column);
|
||||
}
|
||||
if sym.decl.retType != null as *TypeExpr {
|
||||
return Sema_ResolveType(sema, sym.decl.retType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -915,7 +942,9 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
|
||||
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
|
||||
@@ -1018,6 +1047,30 @@ func Sema_CollectGlobals(sema: *Sema) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1107,6 +1160,67 @@ func Sema_CollectGlobals(sema: *Sema) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trait bounds checking
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func Sema_FindInterface(sema: *Sema, name: String) -> *Decl {
|
||||
var i: int = 0;
|
||||
while i < sema.interfaceCount {
|
||||
if String_Eq(sema.interfaceTable[i].name, name) {
|
||||
return sema.interfaceTable[i].decl;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return null as *Decl;
|
||||
}
|
||||
|
||||
func Sema_TypeHasMethod(sema: *Sema, typeName: String, methodName: String) -> bool {
|
||||
var i: int = 0;
|
||||
while i < sema.methodCount {
|
||||
if String_Eq(sema.methodEntries[i].typeName, typeName) && String_Eq(sema.methodEntries[i].methodName, methodName) {
|
||||
return true;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
func Sema_TypeImplements(sema: *Sema, typeName: String, interfaceName: String) -> bool {
|
||||
let iface: *Decl = Sema_FindInterface(sema, interfaceName);
|
||||
if iface == null as *Decl {
|
||||
return true; // Unknown interface — be permissive
|
||||
}
|
||||
var req: *Decl = iface.childDecl1;
|
||||
while req != null as *Decl {
|
||||
if req.kind == dkFunc {
|
||||
if !Sema_TypeHasMethod(sema, typeName, req.strValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
req = req.childDecl2;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
func Sema_CheckTraitBounds(sema: *Sema, funcDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int, line: uint32, col: uint32) {
|
||||
// Trait bounds checking
|
||||
if funcDecl.typeParamCount >= 1 && typeArgCount >= 1 && !String_Eq(funcDecl.typeParam0Bound, "") {
|
||||
if !Sema_TypeImplements(sema, typeArg0, funcDecl.typeParam0Bound) {
|
||||
let errMsg: String = String_Concat("type '", String_Concat(typeArg0, "' does not implement trait '"));
|
||||
let errMsg2: String = String_Concat(errMsg, String_Concat(funcDecl.typeParam0Bound, "'"));
|
||||
Sema_EmitError(sema, line, col, errMsg2);
|
||||
}
|
||||
}
|
||||
if funcDecl.typeParamCount >= 2 && typeArgCount >= 2 && !String_Eq(funcDecl.typeParam1Bound, "") {
|
||||
if !Sema_TypeImplements(sema, typeArg1, funcDecl.typeParam1Bound) {
|
||||
let errMsg: String = String_Concat("type '", String_Concat(typeArg1, "' does not implement trait '"));
|
||||
let errMsg2: String = String_Concat(errMsg, String_Concat(funcDecl.typeParam1Bound, "'"));
|
||||
Sema_EmitError(sema, line, col, errMsg2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Analyze — main entry point
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1124,6 +1238,10 @@ func Sema_Analyze(mod: *Module) -> *Sema {
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user