feat: full match expressions in bootstrap and selfhost

Lower match to if-else for literals, ranges, enum tags, and wildcards.
Fix bootstrap literal arms that always matched; port real arm AST/parser
and Lcx_LowerMatch to selfhost with last-expression return. Expand
pattern_matching example and add parse/use-after-move/double-mut golden
diagnostics. Selfhost-loop remains binary-identical.
This commit is contained in:
2026-07-16 16:19:43 +03:00
parent 2cbbccc508
commit f619316470
18 changed files with 786 additions and 110 deletions
+41 -1
View File
@@ -783,6 +783,46 @@ func Sema_CheckExpr(sema: *Sema, expr: *Expr) -> int {
return tyVoid;
}
// Match expression
if kind == ekMatch {
discard Sema_CheckExpr(sema, expr.child1); // subject
var armType: int = tyUnknown;
var first: bool = true;
var arm: *MatchArm = expr.matchArms;
while arm != null as *MatchArm {
let bt: int = Sema_CheckExpr(sema, arm.body);
if first {
armType = bt;
first = false;
}
arm = arm.next;
}
// Propagate type of first arm for codegen (result temp type)
if armType == tyStr {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = "String";
expr.refType = te;
} else if armType == tyBool {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = "bool";
expr.refType = te;
} else if armType == tyInt || armType == tyUnknown {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = "int";
expr.refType = te;
if armType == tyUnknown { armType = tyInt; }
} else {
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = "int";
expr.refType = te;
}
return armType;
}
// Closure: |params| -> Ret { body }
if kind == ekClosure {
let savedRetType: int = sema.currentRetType;
@@ -1052,7 +1092,7 @@ func Sema_CheckStmt(sema: *Sema, stmt: *Stmt) {
return;
}
// Match
// Match (legacy skMatch — prefer ekMatch via skExpr)
if kind == skMatch {
discard Sema_CheckExpr(sema, stmt.child1);
return;