feat: match guards, HOF inference, ownership C.2/C.3, LSP sema hover

Sessions 18–23 quality work:

- B.3c match arm guards + sequential found-flag lower (bootstrap + selfhost)
- Generic HOF type inference (Array/Iter map/filter/fold without type args)
- Pattern binding shadowing via unique C locals (__pN_src)
- Ownership C.2 exclusive &mut data-flow + C.4 goldens; *p= store-through fix
- Ownership C.3 auto-drop on early return/branches: scoped defers, move-on-return,
  Drop monomorphization, materialize return before Drop
- LSP 0.3.0: hover from real sema types
- Examples and QUALITY_PLAN session log; selfhost-loop identical
This commit is contained in:
2026-07-18 21:52:14 +03:00
parent 66f11d1869
commit 3eb1ad3a82
25 changed files with 2246 additions and 457 deletions
+3 -1
View File
@@ -66,6 +66,7 @@ const pkRange: int = 3;
const pkEnum: int = 4;
const pkStruct: int = 5;
const pkTuple: int = 6;
const pkGuarded: int = 7; // `p if cond` — patChild1 = inner, patGuardExpr = condition
struct Pattern {
kind: int,
@@ -78,10 +79,11 @@ struct Pattern {
patEnumPath: String, // for pkEnum: "Enum::Variant"
patStructName: String, // for pkStruct (type name)
patFieldName: String, // for struct field entry: field name in Point { x: a }
patChild1: *Pattern, // range lo / nested
patChild1: *Pattern, // range lo / nested / guarded inner
patChild2: *Pattern, // range hi / nested
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
patNext: *Pattern, // next sibling in patArgs list
patGuardExpr: *Expr, // for pkGuarded: the `if` condition
}
// Match arm: pattern => body
+91 -30
View File
@@ -75,6 +75,8 @@ struct CEmitter {
movedName5: String,
movedName6: String,
movedName7: String,
tmpCounter: int,
currentRetType: String,
}
func CBE_PushDefer(cbe: *CEmitter, node: *HirNode) {
@@ -155,36 +157,56 @@ func CBE_GetAutoDropVarName(node: *HirNode) -> String {
return varNode.strValue;
}
// Emit one defer slot (shared by full-stack and scope-pop emitters).
func CBE_EmitOneDefer(cbe: *CEmitter, i: int) {
var dn: *HirNode = null as *HirNode;
if i == 0 { dn = cbe.defer0; }
if i == 1 { dn = cbe.defer1; }
if i == 2 { dn = cbe.defer2; }
if i == 3 { dn = cbe.defer3; }
if i == 4 { dn = cbe.defer4; }
if i == 5 { dn = cbe.defer5; }
if i == 6 { dn = cbe.defer6; }
if i == 7 { dn = cbe.defer7; }
// Skip auto-drop for moved variables
let deferVarName: String = CBE_GetAutoDropVarName(dn);
if !String_Eq(deferVarName, "") && CBE_IsMoved(cbe, deferVarName) {
return;
}
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, dn);
StringBuilder_Append(&cbe.sb, ";");
}
// Emit all active defers (LIFO) without clearing the stack.
// Must NOT clear: multiple return paths each need the full defer list.
// (Clearing caused Early(flag) { if (0) return; return 1 } to drop only on first exit.)
// Stack is reset at the start of each function emission.
func CBE_EmitDefers(cbe: *CEmitter) -> int {
if cbe.deferCount == 0 { return 0; }
var i: int = cbe.deferCount - 1;
while i >= 0 {
var dn: *HirNode = null as *HirNode;
if i == 0 { dn = cbe.defer0; }
if i == 1 { dn = cbe.defer1; }
if i == 2 { dn = cbe.defer2; }
if i == 3 { dn = cbe.defer3; }
if i == 4 { dn = cbe.defer4; }
if i == 5 { dn = cbe.defer5; }
if i == 6 { dn = cbe.defer6; }
if i == 7 { dn = cbe.defer7; }
// Skip auto-drop for moved variables
let deferVarName: String = CBE_GetAutoDropVarName(dn);
if !String_Eq(deferVarName, "") && CBE_IsMoved(cbe, deferVarName) {
i = i - 1;
continue;
}
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
CBE_EmitExpr(cbe, dn);
StringBuilder_Append(&cbe.sb, ";");
CBE_EmitOneDefer(cbe, i);
i = i - 1;
}
cbe.deferCount = 0;
return 1;
}
// Emit branch/loop-local defers (indices fromIdx..count-1) then pop them.
// Outer defers stay live so sibling branches and later returns still drop correctly.
func CBE_EmitAndPopDefersFrom(cbe: *CEmitter, fromIdx: int) -> int {
if cbe.deferCount <= fromIdx { return 0; }
var i: int = cbe.deferCount - 1;
while i >= fromIdx {
CBE_EmitOneDefer(cbe, i);
i = i - 1;
}
cbe.deferCount = fromIdx;
return 1;
}
@@ -381,20 +403,48 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// Return
// Return — evaluate value first, then drop live locals, then return.
// (Emitting Drop before the value used to use-after-drop on `return a.id`.)
if kind == hReturn {
// Track moved variables via return
// Track moved variables via return (skip auto-drop of moved-out locals)
if node.child1 != null as *HirNode && node.child1.kind == hVar {
CBE_AddMoved(cbe, node.child1.strValue);
}
let hadDefers: int = CBE_EmitDefers(cbe);
if hadDefers != 0 {
if node.child1 != null as *HirNode && cbe.deferCount > 0 {
// Materialize into a temp so Drop cannot clobber the returned value.
// Prefer the enclosing function return type (field-access HIR often
// carries the base struct typeName, which is wrong for `return a.id`).
cbe.tmpCounter = cbe.tmpCounter + 1;
let tmpName: String = String_Concat("__retdrop_", String_FromInt(cbe.tmpCounter));
var retCt: String = "int";
if cbe.currentRetType != null as String && !String_Eq(cbe.currentRetType, "") && !String_Eq(cbe.currentRetType, "void") {
retCt = cbe.currentRetType;
} else if node.child1.typeKind != 0 {
retCt = CBackend_TypeToC(node.child1.typeKind);
}
StringBuilder_Append(&cbe.sb, CBE_CParamDecl(retCt, tmpName));
StringBuilder_Append(&cbe.sb, " = ");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ";");
discard CBE_EmitDefers(cbe);
StringBuilder_Append(&cbe.sb, "\n");
var sp: int = 0;
while sp < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp = sp + 1;
}
StringBuilder_Append(&cbe.sb, "return ");
StringBuilder_Append(&cbe.sb, tmpName);
return;
}
let hadDefers: int = CBE_EmitDefers(cbe);
if hadDefers != 0 {
StringBuilder_Append(&cbe.sb, "\n");
var sp2: int = 0;
while sp2 < cbe.indent {
StringBuilder_Append(&cbe.sb, " ");
sp2 = sp2 + 1;
}
}
StringBuilder_Append(&cbe.sb, "return");
if node.child1 != null as *HirNode {
@@ -444,15 +494,17 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// If
// If — each branch has its own defer scope (locals do not leak)
if kind == hIf {
StringBuilder_Append(&cbe.sb, "if (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
let savedThen: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
discard CBE_EmitAndPopDefersFrom(cbe, savedThen);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
@@ -464,8 +516,10 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
let elseBlock: *HirNode = node.extraData as *HirNode;
if elseBlock != null as *HirNode {
StringBuilder_Append(&cbe.sb, " else {\n");
let savedElse: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, elseBlock);
discard CBE_EmitAndPopDefersFrom(cbe, savedElse);
cbe.indent = cbe.indent - 1;
sp = 0;
while sp < cbe.indent {
@@ -479,14 +533,16 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
return;
}
// While
// While — loop-body locals dropped each iteration
if kind == hWhile {
StringBuilder_Append(&cbe.sb, "while (");
CBE_EmitExpr(cbe, node.child1);
StringBuilder_Append(&cbe.sb, ") {\n");
if node.child2 != null as *HirNode {
let savedW: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child2);
discard CBE_EmitAndPopDefersFrom(cbe, savedW);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
@@ -502,8 +558,10 @@ func CBE_EmitExpr(cbe: *CEmitter, node: *HirNode) {
if kind == hLoop {
StringBuilder_Append(&cbe.sb, "while (1) {\n");
if node.child1 != null as *HirNode {
let savedL: int = cbe.deferCount;
cbe.indent = cbe.indent + 1;
CBE_EmitExpr(cbe, node.child1);
discard CBE_EmitAndPopDefersFrom(cbe, savedL);
cbe.indent = cbe.indent - 1;
}
var sp: int = 0;
@@ -1266,6 +1324,7 @@ func CBackend_Generate(mod: *HirModule) -> String {
cbe.mod = mod;
cbe.deferCount = 0;
cbe.movedCount = 0;
cbe.tmpCounter = 0;
// Header
StringBuilder_Append(&cbe.sb, "// Generated by Bux C Backend v2\n");
@@ -1566,6 +1625,8 @@ func CBackend_Generate(mod: *HirModule) -> String {
cbe.checkedFunc = mod.funcs[i].checkedFunc;
cbe.deferCount = 0;
cbe.movedCount = 0;
cbe.tmpCounter = 0;
cbe.currentRetType = mod.funcs[i].retTypeName;
var hasReturn: bool = false;
cbe.indent = 1;
CBE_EmitExpr(cbe, body);
+380 -217
View File
@@ -34,6 +34,101 @@ struct LowerCtx {
// Borrow checker state
checkedFunc: bool,
releaseFunc: bool,
// Pattern binding renames: source name → unique C local (shadowing-safe)
patMapCount: int,
patMapFrom0: String,
patMapTo0: String,
patMapFrom1: String,
patMapTo1: String,
patMapFrom2: String,
patMapTo2: String,
patMapFrom3: String,
patMapTo3: String,
patMapFrom4: String,
patMapTo4: String,
patMapFrom5: String,
patMapTo5: String,
patMapFrom6: String,
patMapTo6: String,
patMapFrom7: String,
patMapTo7: String,
}
func Lcx_PatLookup(ctx: *LowerCtx, src: String) -> String {
// Most recent rename wins (scan from end)
var i: int = ctx.patMapCount - 1;
while i >= 0 {
var from: String = "";
var to: String = "";
if i == 0 { from = ctx.patMapFrom0; to = ctx.patMapTo0; }
else if i == 1 { from = ctx.patMapFrom1; to = ctx.patMapTo1; }
else if i == 2 { from = ctx.patMapFrom2; to = ctx.patMapTo2; }
else if i == 3 { from = ctx.patMapFrom3; to = ctx.patMapTo3; }
else if i == 4 { from = ctx.patMapFrom4; to = ctx.patMapTo4; }
else if i == 5 { from = ctx.patMapFrom5; to = ctx.patMapTo5; }
else if i == 6 { from = ctx.patMapFrom6; to = ctx.patMapTo6; }
else if i == 7 { from = ctx.patMapFrom7; to = ctx.patMapTo7; }
if String_Eq(from, src) { return to; }
i = i - 1;
}
return "";
}
func Lcx_PatPush(ctx: *LowerCtx, src: String, dst: String) {
if ctx.patMapCount >= 8 { return; }
let i: int = ctx.patMapCount;
if i == 0 { ctx.patMapFrom0 = src; ctx.patMapTo0 = dst; }
else if i == 1 { ctx.patMapFrom1 = src; ctx.patMapTo1 = dst; }
else if i == 2 { ctx.patMapFrom2 = src; ctx.patMapTo2 = dst; }
else if i == 3 { ctx.patMapFrom3 = src; ctx.patMapTo3 = dst; }
else if i == 4 { ctx.patMapFrom4 = src; ctx.patMapTo4 = dst; }
else if i == 5 { ctx.patMapFrom5 = src; ctx.patMapTo5 = dst; }
else if i == 6 { ctx.patMapFrom6 = src; ctx.patMapTo6 = dst; }
else if i == 7 { ctx.patMapFrom7 = src; ctx.patMapTo7 = dst; }
ctx.patMapCount = ctx.patMapCount + 1;
}
func Lcx_FreshPatName(ctx: *LowerCtx, src: String) -> String {
ctx.varCounter = ctx.varCounter + 1;
var safe: String = src;
if String_Eq(src, "") || String_Eq(src, "_") { safe = "x"; }
return String_Concat(String_Concat("__p", String_FromInt(ctx.varCounter as int64)),
String_Concat("_", safe));
}
// Alloca unique C local + store + scope define + rename map for pattern binding.
func Lcx_BindPatIdent(ctx: *LowerCtx, src: String, ty: String, value: *HirNode,
line: uint32, col: uint32) -> *HirNode {
if String_Eq(src, "") || String_Eq(src, "_") { return null as *HirNode; }
let cName: String = Lcx_FreshPatName(ctx, src);
Lcx_PatPush(ctx, src, cName);
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = cName;
alloca.typeName = ty;
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
store.kind = hStore;
store.line = line;
store.column = col;
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
v.kind = hVar;
v.strValue = cName;
store.child1 = v;
store.child2 = value;
alloca.child3 = store;
var bsym: Symbol;
bsym.kind = skVar;
bsym.name = src;
bsym.typeKind = tyInt;
bsym.typeName = ty;
bsym.refType = null as *TypeExpr;
bsym.isMutable = false;
bsym.isPublic = false;
bsym.decl = null as *Decl;
discard Scope_Define(ctx.scope, bsym);
return alloca;
}
// ---------------------------------------------------------------------------
@@ -265,6 +360,27 @@ func Lcx_FindGenericStruct(ctx: *LowerCtx, name: String) -> *Decl {
return null as *Decl;
}
// Extract element type from mangled collection name: Array_int → int, Iter_String → String
func Lcx_ExtractElemFromName(typeName: String) -> String {
if String_Eq(typeName, "") { return ""; }
let len: uint = bux_strlen(typeName);
// "Array_" prefix (6 chars)
if len > 6 {
let p: String = bux_str_slice(typeName, 0, 6);
if String_Eq(p, "Array_") {
return bux_str_slice(typeName, 6, len - 6);
}
}
// "Iter_" prefix (5 chars)
if len > 5 {
let p: String = bux_str_slice(typeName, 0, 5);
if String_Eq(p, "Iter_") {
return bux_str_slice(typeName, 5, len - 5);
}
}
return "";
}
func Lcx_MangleName(base: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
let r: String = String_Concat(base, "_");
r = String_Concat(r, typeArg0);
@@ -487,6 +603,11 @@ func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
if pat == null as *Pattern { return null as *HirNode; }
let kind: int = pat.kind;
// Guarded: condition is only the inner pattern; guard applied after binds
if kind == pkGuarded {
return Lcx_PatternCond(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
}
if kind == pkWildcard || kind == pkIdent {
return null as *HirNode;
}
@@ -568,40 +689,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
subjectEnumName: String, subjectHasData: bool,
line: uint32, col: uint32) -> *HirNode {
if pat == null as *Pattern { return null as *HirNode; }
// Guarded: bind from inner pattern
if pat.kind == pkGuarded {
return Lcx_PatternBindings(ctx, subject, pat.patChild1, subjectEnumName, subjectHasData, line, col);
}
if pat.kind == pkIdent {
// `_` is wildcard, not a binding
if String_Eq(pat.patIdent, "_") { return null as *HirNode; }
let ty: String = "int";
if subject != null as *HirNode && !String_Eq(subject.typeName, "") {
ty = subject.typeName;
}
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = pat.patIdent;
alloca.typeName = ty;
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
store.kind = hStore;
store.line = line;
store.column = col;
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
v.kind = hVar;
v.strValue = pat.patIdent;
store.child1 = v;
store.child2 = subject;
alloca.child3 = store;
var bsym: Symbol;
bsym.kind = skVar;
bsym.name = pat.patIdent;
bsym.typeKind = tyInt;
bsym.typeName = ty;
bsym.refType = null as *TypeExpr;
bsym.isMutable = false;
bsym.isPublic = false;
bsym.decl = null as *Decl;
discard Scope_Define(ctx.scope, bsym);
return alloca;
return Lcx_BindPatIdent(ctx, pat.patIdent, ty, subject, line, col);
}
// Tuple: (a, b) → a = subject._0; b = subject._1
@@ -629,38 +726,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
fLoad.column = col;
fLoad.child1 = fPtr;
fLoad.typeName = "int";
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = elem.patIdent;
alloca.typeName = "int";
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
store.kind = hStore;
store.line = line;
store.column = col;
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
v.kind = hVar;
v.strValue = elem.patIdent;
store.child1 = v;
store.child2 = fLoad;
alloca.child3 = store;
var bsym: Symbol;
bsym.kind = skVar;
bsym.name = elem.patIdent;
bsym.typeKind = tyInt;
bsym.typeName = "int";
bsym.refType = null as *TypeExpr;
bsym.isMutable = false;
bsym.isPublic = false;
bsym.decl = null as *Decl;
discard Scope_Define(ctx.scope, bsym);
if head == null as *HirNode {
head = alloca;
tail = store;
} else {
tail.child3 = alloca;
tail = store;
let bound: *HirNode = Lcx_BindPatIdent(ctx, elem.patIdent, "int", fLoad, line, col);
if bound != null as *HirNode {
if head == null as *HirNode {
head = bound;
tail = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
}
elem = elem.patNext;
@@ -690,38 +765,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
fLoad.column = col;
fLoad.child1 = fPtr;
fLoad.typeName = "int";
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = field.patIdent;
alloca.typeName = "int";
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
store.kind = hStore;
store.line = line;
store.column = col;
let v: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
v.kind = hVar;
v.strValue = field.patIdent;
store.child1 = v;
store.child2 = fLoad;
alloca.child3 = store;
var bsym: Symbol;
bsym.kind = skVar;
bsym.name = field.patIdent;
bsym.typeKind = tyInt;
bsym.typeName = "int";
bsym.refType = null as *TypeExpr;
bsym.isMutable = false;
bsym.isPublic = false;
bsym.decl = null as *Decl;
discard Scope_Define(ctx.scope, bsym);
if head == null as *HirNode {
head = alloca;
tail = store;
} else {
tail.child3 = alloca;
tail = store;
let bound: *HirNode = Lcx_BindPatIdent(ctx, field.patIdent, "int", fLoad, line, col);
if bound != null as *HirNode {
if head == null as *HirNode {
head = bound;
tail = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
}
field = field.patNext;
@@ -827,41 +880,16 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
fLoad.typeName = ftype;
if arg.kind == pkIdent && !String_Eq(arg.patIdent, "_") {
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
alloca.line = line;
alloca.column = col;
alloca.strValue = arg.patIdent;
alloca.typeName = ftype;
let store: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
store.kind = hStore;
store.line = line;
store.column = col;
let bv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
bv.kind = hVar;
bv.strValue = arg.patIdent;
store.child1 = bv;
store.child2 = fLoad;
alloca.child3 = store;
var bsym: Symbol;
bsym.kind = skVar;
bsym.name = arg.patIdent;
bsym.typeKind = tyInt;
bsym.typeName = ftype;
bsym.refType = null as *TypeExpr;
bsym.isMutable = false;
bsym.isPublic = false;
bsym.decl = null as *Decl;
discard Scope_Define(ctx.scope, bsym);
if head == null as *HirNode {
head = alloca;
tail = store;
} else {
tail.child3 = alloca;
tail = store;
let bound: *HirNode = Lcx_BindPatIdent(ctx, arg.patIdent, ftype, fLoad, line, col);
if bound != null as *HirNode {
if head == null as *HirNode {
head = bound;
tail = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = bound;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
} else if arg.kind == pkTuple || arg.kind == pkStruct || arg.kind == pkEnum {
// Nested pattern on payload field
@@ -913,7 +941,9 @@ func Lcx_AppendToChain(head: *HirNode, node: *HirNode) {
cur.child3 = node;
}
// Lower match expr → hBlock: alloca result; if-else stores; strValue = result name
// Lower match expr → sequential ifs with a found flag (no shared HIR DAG).
// Each arm: if (!found) { if (cond) { binds; if (guard) { result=body; found=true; } } }
// Guards see pattern bindings. Supports pkGuarded (`p if cond`).
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
let line: uint32 = expr.line;
let col: uint32 = expr.column;
@@ -921,6 +951,8 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
ctx.varCounter = ctx.varCounter + 1;
let resultName: String = String_Concat("__match_", String_FromInt(ctx.varCounter as int64));
ctx.varCounter = ctx.varCounter + 1;
let foundName: String = String_Concat("__found_", String_FromInt(ctx.varCounter as int64));
// Result type from sema refType, default int
var typeName: String = "int";
@@ -938,7 +970,7 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
}
}
// Alloca result
// Alloca result + found flag
let allocaNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
allocaNode.kind = hAlloca;
allocaNode.line = line;
@@ -946,35 +978,48 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
allocaNode.strValue = resultName;
allocaNode.typeName = typeName;
// Collect arms into a temporary array via reverse build of if-chain
// First count arms and build from last to first
var armCount: int = 0;
var arm: *MatchArm = expr.matchArms;
while arm != null as *MatchArm {
armCount = armCount + 1;
arm = arm.next;
}
let foundAlloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundAlloca.kind = hAlloca;
foundAlloca.line = line;
foundAlloca.column = col;
foundAlloca.strValue = foundName;
foundAlloca.typeName = "bool";
allocaNode.child3 = foundAlloca;
// Build if-else from last arm to first
var ifChain: *HirNode = null as *HirNode;
var ai: int = armCount - 1;
while ai >= 0 {
// Find arm at index ai
var cur: *MatchArm = expr.matchArms;
var j: int = 0;
while j < ai && cur != null as *MatchArm {
cur = cur.next;
j = j + 1;
}
if cur == null as *MatchArm {
ai = ai - 1;
continue;
}
let foundInit: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundInit.kind = hStore;
foundInit.line = line;
foundInit.column = col;
let foundVar0: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundVar0.kind = hVar;
foundVar0.strValue = foundName;
foundInit.child1 = foundVar0;
foundInit.child2 = Lcx_MakeLitHir(tkBoolLiteral, "false", line, col);
foundAlloca.child3 = foundInit;
// Pattern bindings before body (so body idents resolve)
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
// Chain of arm ifs (forward order), linked via child3
var tail: *HirNode = foundInit;
var cur: *MatchArm = expr.matchArms;
while cur != null as *MatchArm {
// Snapshot rename map so this arm's bindings don't leak to later arms
let savedMapCount: int = ctx.patMapCount;
var bindPat: *Pattern = cur.pattern;
var guardExpr: *Expr = null as *Expr;
if cur.pattern != null as *Pattern && cur.pattern.kind == pkGuarded {
bindPat = cur.pattern.patChild1;
guardExpr = cur.pattern.patGuardExpr;
}
// Bindings before guard/body so renames are active and allocas precede uses
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
var guardHirEarly: *HirNode = null as *HirNode;
if guardExpr != null as *Expr {
guardHirEarly = Lcx_LowerExpr(ctx, guardExpr);
}
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
// result = body (expand block/match yield: run stmts then store result var)
// Pop this arm's renames (nested matches already restored themselves)
ctx.patMapCount = savedMapCount;
// store result = body
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
storeNode.kind = hStore;
storeNode.line = line;
@@ -992,59 +1037,95 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
storeNode.child2 = bodyHir;
}
// armBlock = bindings → body stmts → store
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
armBlock.kind = hBlock;
armBlock.line = line;
armBlock.column = col;
var chainHead: *HirNode = bindHead;
if chainHead == null as *HirNode {
chainHead = bodyPrefix;
} else if bodyPrefix != null as *HirNode {
var bt0: *HirNode = chainHead;
while bt0.child3 != null as *HirNode { bt0 = bt0.child3; }
bt0.child3 = bodyPrefix;
}
if chainHead != null as *HirNode {
armBlock.child1 = chainHead;
var bt: *HirNode = chainHead;
while bt.child3 != null as *HirNode { bt = bt.child3; }
bt.child3 = storeNode;
// found = true
let foundSet: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundSet.kind = hStore;
foundSet.line = line;
foundSet.column = col;
let foundVar1: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundVar1.kind = hVar;
foundVar1.strValue = foundName;
foundSet.child1 = foundVar1;
foundSet.child2 = Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
storeNode.child3 = foundSet;
// success block: bodyPrefix → store → found=true
let successBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
successBlock.kind = hBlock;
successBlock.line = line;
successBlock.column = col;
if bodyPrefix != null as *HirNode {
successBlock.child1 = bodyPrefix;
var sbt: *HirNode = bodyPrefix;
while sbt.child3 != null as *HirNode { sbt = sbt.child3; }
sbt.child3 = storeNode;
} else {
armBlock.child1 = storeNode;
successBlock.child1 = storeNode;
}
let cond: *HirNode = Lcx_PatternCond(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
if cond == null as *HirNode {
// Always-true arm
if ifChain == null as *HirNode {
ifChain = armBlock;
} else {
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
ifNode.kind = hIf;
ifNode.line = line;
ifNode.column = col;
ifNode.child1 = Lcx_MakeTrueHir(line, col);
ifNode.child2 = armBlock;
ifNode.extraData = ifChain as *void;
ifChain = ifNode;
}
} else {
let ifNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
ifNode.kind = hIf;
ifNode.line = line;
ifNode.column = col;
ifNode.child1 = cond;
ifNode.child2 = armBlock;
ifNode.extraData = ifChain as *void;
ifChain = ifNode;
// Optional guard wraps success (already lowered with renames active)
var afterBinds: *HirNode = successBlock;
if guardHirEarly != null as *HirNode {
let guardIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
guardIf.kind = hIf;
guardIf.line = line;
guardIf.column = col;
guardIf.child1 = guardHirEarly;
guardIf.child2 = successBlock;
afterBinds = guardIf;
}
ai = ai - 1;
// armInner: binds → afterBinds
let armInner: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
armInner.kind = hBlock;
armInner.line = line;
armInner.column = col;
if bindHead != null as *HirNode {
armInner.child1 = bindHead;
var ibt: *HirNode = bindHead;
while ibt.child3 != null as *HirNode { ibt = ibt.child3; }
ibt.child3 = afterBinds;
} else {
armInner.child1 = afterBinds;
}
// Optional pattern condition
let cond: *HirNode = Lcx_PatternCond(ctx, subject, bindPat, subjectEnumName, subjectHasData, line, col);
var armBody: *HirNode = armInner;
if cond != null as *HirNode {
let condIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
condIf.kind = hIf;
condIf.line = line;
condIf.column = col;
condIf.child1 = cond;
condIf.child2 = armInner;
armBody = condIf;
}
// if (!found) armBody
let foundLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
foundLoad.kind = hVar;
foundLoad.strValue = foundName;
foundLoad.typeName = "bool";
let notFound: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
notFound.kind = hUnary;
notFound.line = line;
notFound.column = col;
notFound.intValue = tkBang;
notFound.child1 = foundLoad;
let tryIf: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tryIf.kind = hIf;
tryIf.line = line;
tryIf.column = col;
tryIf.child1 = notFound;
tryIf.child2 = armBody;
tail.child3 = tryIf;
tail = tryIf;
cur = cur.next;
}
// Chain: alloca → ifChain
allocaNode.child3 = ifChain;
let block: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
block.kind = hBlock;
block.line = line;
@@ -1153,6 +1234,18 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Identifier → variable reference
if kind == ekIdent {
// Pattern binding rename: source name → unique C local
let ren: String = Lcx_PatLookup(ctx, expr.strValue);
if !String_Eq(ren, "") {
n.kind = hVar;
n.strValue = ren;
let rsym: Symbol = Scope_Lookup(ctx.scope, expr.strValue);
n.typeKind = rsym.typeKind;
if rsym.typeName != null as String && !String_Eq(rsym.typeName, "") {
n.typeName = rsym.typeName;
}
return n;
}
// Capture rewriting: if inside closure body and this ident is captured,
// emit field access on env instance instead of bare variable
if ctx.closureDepth > 0 && ctx.currentClosureExpr != null as *Expr && !String_Eq(ctx.envInstanceName, "") {
@@ -1564,18 +1657,65 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
n.kind = hCall;
n.strValue = expr.child1.strValue;
// Generic call monomorphization
if expr.child1 != null as *Expr && expr.child1.genericTypeArgCount > 0 {
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
if genDecl != null as *Decl {
var typeArg0: String = expr.child1.genericTypeArg0;
var typeArg1: String = expr.child1.genericTypeArg1;
if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; }
if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; }
if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; }
if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; }
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, typeArg1, expr.child1.genericTypeArgCount);
n.strValue = mangled;
// Generic call monomorphization (explicit / inferred type args)
if expr.child1 != null as *Expr {
var argc: int = expr.child1.genericTypeArgCount;
var typeArg0: String = expr.child1.genericTypeArg0;
var typeArg1: String = expr.child1.genericTypeArg1;
// Fallback: infer T from first *Array/*Iter arg when sema left count=0
if argc == 0 {
let genTry: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
if genTry != null as *Decl && genTry.typeParamCount > 0 {
if expr.callArgs != null as *ExprList && expr.callArgs.expr != null as *Expr {
var a0: *Expr = expr.callArgs.expr;
var te: *TypeExpr = a0.refType;
if te == null as *TypeExpr && a0.kind == ekUnary && a0.intValue == tkAmp {
if a0.child1 != null as *Expr { te = a0.child1.refType; }
}
if te != null as *TypeExpr {
// Unwrap pointer
if (te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef)
&& te.pointerPointee != null as *TypeExpr {
te = te.pointerPointee;
}
var elem: String = "";
if te.typeArgCount > 0 {
elem = te.typeArgName0;
} else {
// Mangled Array_int / Iter_String
elem = Lcx_ExtractElemFromName(te.typeName);
}
if !String_Eq(elem, "") {
typeArg0 = elem;
argc = 1;
// Second type arg from func-typed second argument if needed
if genTry.typeParamCount >= 2 && expr.callArgs.next != null as *ExprList {
let a1e: *Expr = expr.callArgs.next.expr;
if a1e != null as *Expr && a1e.kind == ekIdent {
let fsym: Symbol = Scope_Lookup(ctx.scope, a1e.strValue);
if fsym.kind == skFunc && fsym.decl != null as *Decl
&& fsym.decl.retType != null as *TypeExpr
&& fsym.decl.retType.kind == tekNamed {
typeArg1 = fsym.decl.retType.typeName;
argc = 2;
}
}
}
}
}
}
}
}
if argc > 0 {
let genDecl: *Decl = Lcx_FindGenericFunc(ctx, expr.child1.strValue);
if genDecl != null as *Decl {
if String_Eq(typeArg0, ctx.substParam0) { typeArg0 = ctx.substArg0; }
if String_Eq(typeArg0, ctx.substParam1) { typeArg0 = ctx.substArg1; }
if String_Eq(typeArg1, ctx.substParam0) { typeArg1 = ctx.substArg0; }
if String_Eq(typeArg1, ctx.substParam1) { typeArg1 = ctx.substArg1; }
let mangled: String = Lcx_GenerateFuncInstance(ctx, genDecl, typeArg0, typeArg1, argc);
n.strValue = mangled;
}
}
}
@@ -2476,10 +2616,33 @@ func Lcx_LowerStmt(ctx: *LowerCtx, stmt: *Stmt) -> *HirNode {
// Return
if kind == skReturn {
n.kind = hReturn;
if stmt.child1 != null as *Expr {
n.child1 = Lcx_LowerExpr(ctx, stmt.child1);
let retVal: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// `return match { ... }` / other multi-stmt yields: expand stmts then return result var
if retVal != null as *HirNode && Lcx_IsMatchYield(retVal) {
let retVar: *HirNode = Lcx_YieldVarOf(retVal);
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = line;
retNode.column = col;
retNode.child1 = retVar;
retVal.strValue = "";
var lastIn: *HirNode = retVal.child1;
if lastIn == null as *HirNode {
retVal.child1 = retNode;
} else {
while lastIn.child3 != null as *HirNode {
lastIn = lastIn.child3;
}
lastIn.child3 = retNode;
}
return retVal;
}
n.kind = hReturn;
n.child1 = retVal;
return n;
}
n.kind = hReturn;
return n;
}
+34 -2
View File
@@ -58,14 +58,27 @@ func parserPeek(p: *Parser, ahead: int) -> int {
return tkEndOfFile;
}
// Lookahead to determine if '<' starts a type argument list.
// Lookahead to determine if '<' starts a type argument list (`Foo<int>`).
// Must not treat value comparisons `x < 0` as generics when a later `x > 0`
// exists (e.g. multiple match arm guards).
func parserIsTypeArgListAhead(p: *Parser) -> bool {
if !parserCheck(p, tkLt) { return false; }
var depth: int = 0;
var ahead: int = 0;
while true {
let kind: int = parserPeek(p, ahead);
if kind == tkEndOfFile || kind == tkLBrace || kind == tkSemicolon {
// Hard stops: cannot appear inside <...> type args
if kind == tkEndOfFile || kind == tkLBrace || kind == tkRBrace || kind == tkSemicolon
|| kind == tkFatArrow || kind == tkIf || kind == tkElse || kind == tkWhile
|| kind == tkFor || kind == tkMatch || kind == tkReturn || kind == tkLet || kind == tkVar
|| kind == tkEq || kind == tkNe || kind == tkLe || kind == tkGe
|| kind == tkAmpAmp || kind == tkPipePipe || kind == tkAssign {
return false;
}
// Literals / arithmetic ⇒ value expression, not type args
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|| kind == tkCharLiteral || kind == tkBoolLiteral
|| kind == tkPlus || kind == tkMinus || kind == tkSlash || kind == tkPercent {
return false;
}
if kind == tkLt {
@@ -729,6 +742,7 @@ func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
pat.patChild2 = null as *Pattern;
pat.patArgs = null as *Pattern;
pat.patNext = null as *Pattern;
pat.patGuardExpr = null as *Expr;
return pat;
}
@@ -904,6 +918,24 @@ func parserParsePattern(p: *Parser) -> *Pattern {
pat.patRangeInclusive = inclusive;
pat.patChild1 = left;
pat.patChild2 = right;
// Range can still take a guard: `1..10 if x % 2 == 0`
if parserCheck(p, tkIf) {
discard parserAdvance(p);
let guard: *Expr = parserParseExpr(p);
let gpat: *Pattern = parserMakePattern(pkGuarded, line, col);
gpat.patChild1 = pat;
gpat.patGuardExpr = guard;
return gpat;
}
return pat;
}
// Guarded pattern: `p if cond` (bindings from p visible in cond)
if parserCheck(p, tkIf) {
discard parserAdvance(p);
let guard: *Expr = parserParseExpr(p);
let pat: *Pattern = parserMakePattern(pkGuarded, line, col);
pat.patChild1 = left;
pat.patGuardExpr = guard;
return pat;
}
return left;
+253 -24
View File
@@ -388,6 +388,11 @@ func Sema_AddCapture(closureExpr: *Expr, name: String, typeKind: int) {
// Enum payloads: Option::Some(value) → value:int (from variant field type).
func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
if pat == null as *Pattern { return; }
// Guarded: bind from inner pattern only (`p if cond`)
if pat.kind == pkGuarded {
Sema_BindPattern(sema, pat.patChild1, subject);
return;
}
if pat.kind == pkIdent {
var sym: Symbol;
Sema_ZeroInitSymbol(&sym);
@@ -807,8 +812,7 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
ai = ai + 1;
}
}
// Trait bounds checking for explicit generic calls: Max<Circle>(...)
// Must happen before indirect/direct call returns
// Trait bounds + inference for generic calls: Max / Iter_Map / Array_Push
if expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(sema.scope, expr.child1.strValue);
if sym.kind == skFunc && sym.decl != null as *Decl {
@@ -831,13 +835,21 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return tyVoid;
}
}
// Direct call to named function
// Direct call to named function — substitute return type with inferred args
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);
var retTe: *TypeExpr = sym.decl.retType;
// Substitute type params in return type when we inferred args
if expr.child1.genericTypeArgCount > 0 {
retTe = Sema_SubstTypeExpr(sym.decl.retType,
sym.decl.typeParam0, expr.child1.genericTypeArg0,
sym.decl.typeParam1, expr.child1.genericTypeArg1,
expr.child1.genericTypeArgCount);
}
expr.refType = retTe;
return Sema_ResolveType(sema, retTe);
}
}
}
@@ -851,6 +863,10 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
// Cast — return target type
if kind == ekCast {
// Must type-check the operand (enables generic inference on nested calls)
if expr.child1 != null as *Expr {
discard Sema_CheckExpr(sema, expr.child1);
}
if expr.refType != null as *TypeExpr {
return Sema_ResolveType(sema, expr.refType);
}
@@ -990,6 +1006,16 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
let savedScope: *Scope = sema.scope;
sema.scope = &armScope;
Sema_BindPattern(sema, arm.pattern, expr.child1);
// Type-check `p if guard` (must be bool; sees pattern bindings)
if arm.pattern != null as *Pattern && arm.pattern.kind == pkGuarded {
if arm.pattern.patGuardExpr != null as *Expr {
let gt: int = Sema_CheckExpr(sema, arm.pattern.patGuardExpr);
if gt != tyBool && gt != tyUnknown {
Sema_EmitError(sema, arm.pattern.line, arm.pattern.column,
"match guard condition must be bool");
}
}
}
let bt: int = Sema_CheckExpr(sema, arm.body);
sema.scope = savedScope;
if first {
@@ -1677,19 +1703,213 @@ func Sema_ExtractElemType(te: *TypeExpr) -> String {
return "";
}
// Infer generic type argument from call arguments.
// Supports Array_* and Iter_* stdlib functions.
// Substitute type params in a TypeExpr (shallow clone). Used for call return types
// after inference: Array<U> + U=String → Array with typeArgName0=String / Array_String.
func Sema_SubstTypeExpr(te: *TypeExpr, p0: String, a0: String, p1: String, a1: String, argc: int) -> *TypeExpr {
if te == null as *TypeExpr { return null as *TypeExpr; }
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
r.kind = te.kind;
r.line = te.line;
r.column = te.column;
r.typeName = te.typeName;
r.pathStr = te.pathStr;
r.pathCount = te.pathCount;
r.typeArgName0 = te.typeArgName0;
r.typeArgName1 = te.typeArgName1;
r.typeArgCount = te.typeArgCount;
r.sliceElement = te.sliceElement;
r.pointerPointee = te.pointerPointee;
r.funcParams = te.funcParams;
r.funcRet = te.funcRet;
r.funcParamCount = te.funcParamCount;
r.tupleElems = te.tupleElems;
r.tupleCount = te.tupleCount;
if te.kind == tekNamed {
// Bare type param → concrete named type
if te.typeArgCount == 0 {
if argc >= 1 && String_Eq(te.typeName, p0) {
r.typeName = a0;
return r;
}
if argc >= 2 && String_Eq(te.typeName, p1) {
r.typeName = a1;
return r;
}
}
// Named with type args: Array<U> → Array_String (mangled) for downstream mono
if te.typeArgCount > 0 {
var na0: String = te.typeArgName0;
var na1: String = te.typeArgName1;
if argc >= 1 && String_Eq(na0, p0) { na0 = a0; }
if argc >= 2 && String_Eq(na0, p1) { na0 = a1; }
if argc >= 1 && String_Eq(na1, p0) { na1 = a0; }
if argc >= 2 && String_Eq(na1, p1) { na1 = a1; }
r.typeArgName0 = na0;
r.typeArgName1 = na1;
// Mangle for monomorphized struct name (Array_int, Iter_String)
if te.typeArgCount == 1 && !String_Eq(na0, "") {
r.typeName = String_Concat(String_Concat(te.typeName, "_"), na0);
r.typeArgCount = 0;
r.typeArgName0 = "";
} else if te.typeArgCount >= 2 {
r.typeName = String_Concat(String_Concat(te.typeName, "_"),
String_Concat(na0, String_Concat("_", na1)));
r.typeArgCount = 0;
r.typeArgName0 = "";
r.typeArgName1 = "";
}
return r;
}
}
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef {
r.pointerPointee = Sema_SubstTypeExpr(te.pointerPointee, p0, a0, p1, a1, argc);
return r;
}
if te.kind == tekFunc {
r.funcRet = Sema_SubstTypeExpr(te.funcRet, p0, a0, p1, a1, argc);
return r;
}
return r;
}
// Bind a type param name on the call callee if not already set.
func Sema_BindInferredArg(expr: *Expr, funcDecl: *Decl, tpName: String, typeName: String) {
if String_Eq(tpName, "") || String_Eq(typeName, "") { return; }
if expr.child1 == null as *Expr { return; }
if funcDecl.typeParamCount >= 1 && String_Eq(tpName, funcDecl.typeParam0) {
if String_Eq(expr.child1.genericTypeArg0, "") {
expr.child1.genericTypeArg0 = typeName;
}
if expr.child1.genericTypeArgCount < 1 { expr.child1.genericTypeArgCount = 1; }
}
if funcDecl.typeParamCount >= 2 && String_Eq(tpName, funcDecl.typeParam1) {
if String_Eq(expr.child1.genericTypeArg1, "") {
expr.child1.genericTypeArg1 = typeName;
}
if expr.child1.genericTypeArgCount < 2 { expr.child1.genericTypeArgCount = 2; }
}
}
// Resolve concrete type name for a value expression (for bare type-param params).
func Sema_ArgTypeName(argExpr: *Expr) -> String {
if argExpr == null as *Expr { return ""; }
var argType: *TypeExpr = argExpr.refType;
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
if argExpr.child1 != null as *Expr { argType = argExpr.child1.refType; }
}
if argType == null as *TypeExpr { return ""; }
if argType.kind == tekNamed { return argType.typeName; }
if argType.kind == tekPointer && argType.pointerPointee != null as *TypeExpr {
if argType.pointerPointee.kind == tekNamed {
return argType.pointerPointee.typeName;
}
}
return "";
}
// Infer type args from a param TypeExpr pattern against a concrete arg TypeExpr.
func Sema_UnifyInfer(expr: *Expr, funcDecl: *Decl, pattern: *TypeExpr, concrete: *TypeExpr) {
if pattern == null as *TypeExpr || concrete == null as *TypeExpr { return; }
// Bare type param: T / Acc / U
if pattern.kind == tekNamed && pattern.typeArgCount == 0 {
if String_Eq(pattern.typeName, funcDecl.typeParam0) || String_Eq(pattern.typeName, funcDecl.typeParam1) {
var cn: String = "";
if concrete.kind == tekNamed { cn = concrete.typeName; }
Sema_BindInferredArg(expr, funcDecl, pattern.typeName, cn);
return;
}
}
// *T / &T — unwrap pointer/ref on both sides
if pattern.kind == tekPointer || pattern.kind == tekRef || pattern.kind == tekMutRef {
var conc: *TypeExpr = concrete;
if concrete.kind == tekPointer || concrete.kind == tekRef || concrete.kind == tekMutRef {
conc = concrete.pointerPointee;
}
Sema_UnifyInfer(expr, funcDecl, pattern.pointerPointee, conc);
return;
}
// Named with type args: Iter<T>, Array<U>, Map<K,V>
if pattern.kind == tekNamed && pattern.typeArgCount > 0 {
// concrete may be Iter with typeArgName0, or mangled Iter_int
if concrete.kind == tekNamed {
if concrete.typeArgCount > 0 {
if pattern.typeArgCount >= 1 && !String_Eq(pattern.typeArgName0, "") {
let te0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te0.kind = tekNamed;
te0.typeName = pattern.typeArgName0;
te0.typeArgCount = 0;
let ce0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
ce0.kind = tekNamed;
ce0.typeName = concrete.typeArgName0;
ce0.typeArgCount = 0;
Sema_UnifyInfer(expr, funcDecl, te0, ce0);
}
if pattern.typeArgCount >= 2 && !String_Eq(pattern.typeArgName1, "") {
let te1: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te1.kind = tekNamed;
te1.typeName = pattern.typeArgName1;
te1.typeArgCount = 0;
let ce1: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
ce1.kind = tekNamed;
ce1.typeName = concrete.typeArgName1;
ce1.typeArgCount = 0;
Sema_UnifyInfer(expr, funcDecl, te1, ce1);
}
} else {
// Mangled Array_int / Iter_String
let elem: String = Sema_ExtractElemType(concrete);
if !String_Eq(elem, "") && pattern.typeArgCount >= 1 {
let te0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te0.kind = tekNamed;
te0.typeName = pattern.typeArgName0;
te0.typeArgCount = 0;
let ce0: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
ce0.kind = tekNamed;
ce0.typeName = elem;
ce0.typeArgCount = 0;
Sema_UnifyInfer(expr, funcDecl, te0, ce0);
}
}
}
return;
}
// func(T)->U vs concrete function type
if pattern.kind == tekFunc {
var conc: *TypeExpr = concrete;
// Named function used as value: build tekFunc from its decl if needed — refType may already be tekFunc
if conc.kind == tekFunc {
// Params
var pp: *TypeExprList = pattern.funcParams;
var cp: *TypeExprList = conc.funcParams;
while pp != null as *TypeExprList && cp != null as *TypeExprList {
Sema_UnifyInfer(expr, funcDecl, pp.te, cp.te);
pp = pp.next;
cp = cp.next;
}
if pattern.funcRet != null as *TypeExpr && conc.funcRet != null as *TypeExpr {
Sema_UnifyInfer(expr, funcDecl, pattern.funcRet, conc.funcRet);
}
}
return;
}
}
// Infer generic type arguments from call arguments (structural).
// Handles *Array<T>, *Iter<T>, func(T)->U, bare Acc, etc.
func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
if expr.callArgs == null as *ExprList { return; }
if expr.child1 == null as *Expr { return; }
var argList: *ExprList = expr.callArgs;
var pi: int = 0;
while argList != null as *ExprList && pi < funcDecl.paramCount {
let argExpr: *Expr = argList.expr;
if argExpr == null as *Expr { argList = argList.next; pi = pi + 1; continue; }
var argType: *TypeExpr = argExpr.refType;
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
argType = argExpr.child1.refType;
}
var paramType: *TypeExpr = null as *TypeExpr;
if pi == 0 { paramType = funcDecl.param0.refParamType; }
else if pi == 1 { paramType = funcDecl.param1.refParamType; }
@@ -1701,20 +1921,29 @@ func Sema_InferGenericArgs(sema: *Sema, funcDecl: *Decl, expr: *Expr) {
else if pi == 7 { paramType = funcDecl.param7.refParamType; }
else if pi == 8 { paramType = funcDecl.param8.refParamType; }
if paramType != null as *TypeExpr && paramType.kind == tekNamed && argType != null as *TypeExpr {
let inferred: String = Sema_ExtractElemType(argType);
var typeName: String = inferred;
if String_Eq(typeName, "") && argType.kind == tekNamed {
typeName = argType.typeName;
var argType: *TypeExpr = argExpr.refType;
// &x → use type of x, wrap as pointer if pattern expects pointer
if argType == null as *TypeExpr && argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
if argExpr.child1 != null as *Expr {
argType = argExpr.child1.refType;
}
if !String_Eq(typeName, "") {
if funcDecl.typeParamCount >= 1 && String_Eq(paramType.typeName, funcDecl.typeParam0) && String_Eq(expr.child1.genericTypeArg0, "") {
expr.child1.genericTypeArg0 = typeName;
if expr.child1.genericTypeArgCount < 1 { expr.child1.genericTypeArgCount = 1; }
}
if funcDecl.typeParamCount >= 2 && String_Eq(paramType.typeName, funcDecl.typeParam1) && String_Eq(expr.child1.genericTypeArg1, "") {
expr.child1.genericTypeArg1 = typeName;
if expr.child1.genericTypeArgCount < 2 { expr.child1.genericTypeArgCount = 2; }
}
// Named function as value: synthesize tekFunc from its declaration
if (argType == null as *TypeExpr || argType.kind != tekFunc) && argExpr.kind == ekIdent {
let fsym: Symbol = Scope_Lookup(sema.scope, argExpr.strValue);
if fsym.kind == skFunc && fsym.decl != null as *Decl {
argType = Sema_BuildFuncTypeExprFromDecl(fsym.decl);
}
}
if paramType != null as *TypeExpr && argType != null as *TypeExpr {
Sema_UnifyInfer(expr, funcDecl, paramType, argType);
// If pattern is *T and arg is bare T (from &x we unwrapped), re-wrap
if (paramType.kind == tekPointer || paramType.kind == tekRef || paramType.kind == tekMutRef)
&& argExpr.kind == ekUnary && argExpr.intValue == tkAmp {
// already handled via unwrap of pattern against pointee type of variable
if argExpr.child1 != null as *Expr && argExpr.child1.refType != null as *TypeExpr {
Sema_UnifyInfer(expr, funcDecl, paramType.pointerPointee, argExpr.child1.refType);
}
}
}