From f63cbd1bf0c240e4ec29f6e31d529eb5843d8eb5 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Wed, 10 Jun 2026 08:48:10 +0300 Subject: [PATCH] selfhost: trait bounds parsing, sema checks, and generic monomorphization fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AST: add typeParam0Bound/typeParam1Bound to Decl, strValue2 to dkImpl - Parser: parse 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(...) 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) --- lib/Array.bux | 5 ++ src/ast.bux | 5 ++ src/c_backend.bux | 6 ++ src/hir_lower.bux | 224 ++++++++++++++++++++++++++++++++++++++-------- src/parser.bux | 145 ++++++++++++++++++++++-------- src/sema.bux | 124 ++++++++++++++++++++++++- 6 files changed, 435 insertions(+), 74 deletions(-) diff --git a/lib/Array.bux b/lib/Array.bux index 1c3e022..2dbfb4e 100644 --- a/lib/Array.bux +++ b/lib/Array.bux @@ -30,6 +30,11 @@ func Array_Get(self: *Array, index: uint) -> T { return self.data[index]; } +func Array_Set(self: *Array, index: uint, value: T) { + bux_bounds_check(index, self.len); + self.data[index] = value; +} + func Array_Len(self: *Array) -> uint { return self.len; } diff --git a/src/ast.bux b/src/ast.bux index d9a5114..cd4db1a 100644 --- a/src/ast.bux +++ b/src/ast.bux @@ -281,6 +281,9 @@ struct Decl { typeParam0: String, typeParam1: String, typeParamCount: int, + // Trait bounds for type params (e.g. ) + 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, diff --git a/src/c_backend.bux b/src/c_backend.bux index dea8de2..2f0a470 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -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; } diff --git a/src/hir_lower.bux b/src/hir_lower.bux index 93ac14b..92eb4c4 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -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 or *Array, 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); diff --git a/src/parser.bux b/src/parser.bux index 17eb670..dc5da3f 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -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: +// --------------------------------------------------------------------------- + +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 - 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 + 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 + 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 - 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 + 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 '{'"); diff --git a/src/sema.bux b/src/sema.bux index a4dfe94..84960b7 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -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(...) + 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);