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
+15 -1
View File
@@ -75,8 +75,19 @@ struct Pattern {
patLitKind: int, // for pkLiteral (token kind)
patLitText: String, // for pkLiteral (token text)
patRangeInclusive: bool, // for pkRange
patEnumPath: String, // for pkEnum (path joined)
patEnumPath: String, // for pkEnum: "Enum::Variant"
patStructName: String, // for pkStruct
patChild1: *Pattern, // range lo / nested
patChild2: *Pattern, // range hi / nested
}
// Match arm: pattern => body
struct MatchArm {
line: uint32,
column: uint32,
pattern: *Pattern,
body: *Expr,
next: *MatchArm,
}
// ---------------------------------------------------------------------------
@@ -165,6 +176,9 @@ struct Expr {
// Call arguments (linked list for multi-arg support)
callArgs: *ExprList,
callArgCount: int,
// Match arms (for ekMatch)
matchArms: *MatchArm,
matchArmCount: int,
}
// ---------------------------------------------------------------------------
+299 -3
View File
@@ -406,6 +406,251 @@ func Lcx_GetArrayElemType(te: *TypeExpr) -> String {
return "";
}
// ---------------------------------------------------------------------------
// Match lowering helpers
// ---------------------------------------------------------------------------
func Lcx_EnumHasData(ctx: *LowerCtx, enumName: String) -> bool {
if String_Eq(enumName, "") { return false; }
let sym: Symbol = Scope_Lookup(ctx.scope, enumName);
if sym.decl == null as *Decl || sym.decl.kind != dkEnum { return false; }
if sym.decl.variantCount > 0 && sym.decl.variant0.fieldCount > 0 { return true; }
if sym.decl.variantCount > 1 && sym.decl.variant1.fieldCount > 0 { return true; }
if sym.decl.variantCount > 2 && sym.decl.variant2.fieldCount > 0 { return true; }
if sym.decl.variantCount > 3 && sym.decl.variant3.fieldCount > 0 { return true; }
if sym.decl.variantCount > 4 && sym.decl.variant4.fieldCount > 0 { return true; }
if sym.decl.variantCount > 5 && sym.decl.variant5.fieldCount > 0 { return true; }
if sym.decl.variantCount > 6 && sym.decl.variant6.fieldCount > 0 { return true; }
if sym.decl.variantCount > 7 && sym.decl.variant7.fieldCount > 0 { return true; }
if sym.decl.variantCount > 8 && sym.decl.variant8.fieldCount > 0 { return true; }
return false;
}
func Lcx_MakeLitHir(litKind: int, litText: String, line: uint32, col: uint32) -> *HirNode {
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hLit;
n.line = line;
n.column = col;
n.intValue = litKind;
n.strValue = litText;
return n;
}
func Lcx_MakeBinHir(op: int, left: *HirNode, right: *HirNode, line: uint32, col: uint32) -> *HirNode {
let n: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
n.kind = hBinary;
n.line = line;
n.column = col;
n.intValue = op;
n.child1 = left;
n.child2 = right;
return n;
}
func Lcx_MakeTrueHir(line: uint32, col: uint32) -> *HirNode {
return Lcx_MakeLitHir(tkBoolLiteral, "true", line, col);
}
// Build condition HirNode for a match pattern. Returns null = always-true.
func Lcx_PatternCond(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
subjectEnumName: String, subjectHasData: bool,
line: uint32, col: uint32) -> *HirNode {
if pat == null as *Pattern { return null as *HirNode; }
let kind: int = pat.kind;
if kind == pkWildcard || kind == pkIdent {
return null as *HirNode;
}
if kind == pkLiteral {
let lit: *HirNode = Lcx_MakeLitHir(pat.patLitKind, pat.patLitText, line, col);
return Lcx_MakeBinHir(tkEq, subject, lit, line, col);
}
if kind == pkRange {
let loPat: *Pattern = pat.patChild1;
let hiPat: *Pattern = pat.patChild2;
if loPat == null as *Pattern || hiPat == null as *Pattern { return null as *HirNode; }
if loPat.kind != pkLiteral || hiPat.kind != pkLiteral { return null as *HirNode; }
let lo: *HirNode = Lcx_MakeLitHir(loPat.patLitKind, loPat.patLitText, line, col);
let hi: *HirNode = Lcx_MakeLitHir(hiPat.patLitKind, hiPat.patLitText, line, col);
let loOk: *HirNode = Lcx_MakeBinHir(tkGe, subject, lo, line, col);
var hiOp: int = tkLt;
if pat.patRangeInclusive { hiOp = tkLe; }
let hiOk: *HirNode = Lcx_MakeBinHir(hiOp, subject, hi, line, col);
return Lcx_MakeBinHir(tkAmpAmp, loOk, hiOk, line, col);
}
if kind == pkEnum {
let path: String = pat.patEnumPath;
// path is "Enum::Variant" or just "Variant"
var enumName: String = "";
var variantName: String = path;
if String_Contains(path, "::") {
enumName = String_SplitPart(path, "::", 0);
variantName = String_SplitPart(path, "::", 1);
}
let tagName: String = String_Concat(String_Concat(enumName, "_"), variantName);
// Prefer subject enum name when path is full
var useEnum: String = enumName;
if String_Eq(useEnum, "") { useEnum = subjectEnumName; }
let fullTag: String = String_Concat(String_Concat(useEnum, "_"), variantName);
if subjectHasData && (String_Eq(enumName, subjectEnumName) || String_Eq(enumName, "")) {
// Algebraic: subject.tag == Enum_Variant
let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagPtr.kind = hFieldPtr;
tagPtr.line = line;
tagPtr.column = col;
tagPtr.strValue = "tag";
tagPtr.child1 = subject;
let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagLoad.kind = hLoad;
tagLoad.line = line;
tagLoad.column = col;
tagLoad.child1 = tagPtr;
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagConst.kind = hVar;
tagConst.line = line;
tagConst.column = col;
tagConst.strValue = fullTag;
return Lcx_MakeBinHir(tkEq, tagLoad, tagConst, line, col);
} else {
// Simple enum: subject == Enum_Variant
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagConst.kind = hVar;
tagConst.line = line;
tagConst.column = col;
if !String_Eq(enumName, "") {
tagConst.strValue = fullTag;
} else {
tagConst.strValue = tagName;
}
return Lcx_MakeBinHir(tkEq, subject, tagConst, line, col);
}
}
return null as *HirNode;
}
// Lower match expr → hBlock: alloca result; if-else stores; strValue = result name
func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
let line: uint32 = expr.line;
let col: uint32 = expr.column;
let subject: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
ctx.varCounter = ctx.varCounter + 1;
let resultName: String = String_Concat("__match_", String_FromInt(ctx.varCounter as int64));
// Result type from sema refType, default int
var typeName: String = "int";
if expr.refType != null as *TypeExpr && !String_Eq(expr.refType.typeName, "") {
typeName = expr.refType.typeName;
}
// Subject enum info
var subjectEnumName: String = "";
var subjectHasData: bool = false;
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
if expr.child1.refType.kind == tekNamed {
subjectEnumName = expr.child1.refType.typeName;
subjectHasData = Lcx_EnumHasData(ctx, subjectEnumName);
}
}
// Alloca result
let allocaNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
allocaNode.kind = hAlloca;
allocaNode.line = line;
allocaNode.column = col;
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;
}
// 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 bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
// result = body
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
storeNode.kind = hStore;
storeNode.line = line;
storeNode.column = col;
let resVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
resVar.kind = hVar;
resVar.strValue = resultName;
storeNode.child1 = resVar;
storeNode.child2 = bodyHir;
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
armBlock.kind = hBlock;
armBlock.line = line;
armBlock.column = col;
armBlock.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;
}
ai = ai - 1;
}
// Chain: alloca → ifChain
allocaNode.child3 = ifChain;
let block: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
block.kind = hBlock;
block.line = line;
block.column = col;
block.child1 = allocaNode;
// Yield marker: strValue = result var name for last-expr return / let init
block.strValue = resultName;
block.typeName = typeName;
return block;
}
// ---------------------------------------------------------------------------
// Expression lowering
// ---------------------------------------------------------------------------
@@ -422,6 +667,11 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
let kind: int = expr.kind;
// Match expression
if kind == ekMatch {
return Lcx_LowerMatch(ctx, expr);
}
// Literal
if kind == ekLiteral {
n.kind = hLit;
@@ -2286,8 +2536,54 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
var firstNode: *HirNode = null as *HirNode;
var prevNode: *HirNode = null as *HirNode;
var stmt: *Stmt = block.firstStmt;
var lastStmt: *Stmt = null as *Stmt;
while stmt != null as *Stmt {
let lowered: *HirNode = Lcx_LowerStmt(ctx, stmt);
lastStmt = stmt;
let isLast: bool = stmt.nextStmt == null as *Stmt;
// Last expression statement in a non-void function → implicit return
// (supports `func F() -> T { match ... }` / bare value as body)
var lowered: *HirNode = null as *HirNode;
if isLast && retTypeKind != tyVoid && retTypeKind != tyUnknown && retTypeKind >= 0
&& stmt.kind == skExpr && stmt.child1 != null as *Expr {
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
if exprNode != null as *HirNode {
// Match (and similar multi-stmt yields): hBlock with strValue = result var
if exprNode.kind == hBlock && !String_Eq(exprNode.strValue, "") {
let retVar: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retVar.kind = hVar;
retVar.strValue = exprNode.strValue;
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = stmt.line;
retNode.column = stmt.column;
retNode.child1 = retVar;
// Append return at end of match block's child3 chain
var lastInBlock: *HirNode = exprNode.child1;
if lastInBlock == null as *HirNode {
exprNode.child1 = retNode;
} else {
while lastInBlock.child3 != null as *HirNode {
lastInBlock = lastInBlock.child3;
}
lastInBlock.child3 = retNode;
}
// Clear yield marker so emit treats it as plain block
exprNode.strValue = "";
lowered = exprNode;
} else if exprNode.kind == hReturn {
lowered = exprNode;
} else {
let retNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
retNode.kind = hReturn;
retNode.line = stmt.line;
retNode.column = stmt.column;
retNode.child1 = exprNode;
lowered = retNode;
}
}
} else {
lowered = Lcx_LowerStmt(ctx, stmt);
}
if lowered != null as *HirNode {
if firstNode == null as *HirNode {
firstNode = lowered;
@@ -2430,11 +2726,11 @@ func Lcx_LowerFunc(ctx: *LowerCtx, decl: *Decl) -> *HirFunc {
pi = pi + 1;
}
// Lower body with function scope active
// Lower body with function scope active (pass ret kind for last-expr return)
let prevScope: *Scope = ctx.scope;
ctx.scope = &funcScope;
if decl.refBody != null as *Block {
f.body = Lcx_LowerBlock(ctx, decl.refBody, -1);
f.body = Lcx_LowerBlock(ctx, decl.refBody, f.retTypeKind);
} else {
f.body = null as *HirNode;
}
+180 -19
View File
@@ -13,6 +13,8 @@ func parserParsePrimary(p: *Parser) -> *Expr;
func parserParsePostfixExpr(p: *Parser) -> *Expr;
func parserParseUnary(p: *Parser) -> *Expr;
func parserParseBinaryPrec(p: *Parser, minPrec: int) -> *Expr;
func parserParsePattern(p: *Parser) -> *Pattern;
func parserParseMatchExpr(p: *Parser) -> *Expr;
// ---------------------------------------------------------------------------
// Parser state
@@ -346,6 +348,8 @@ func parserMakeExpr(kind: int, line: uint32, col: uint32) -> *Expr {
e.structFieldCount = 0;
e.callArgs = null as *ExprList;
e.callArgCount = 0;
e.matchArms = null as *MatchArm;
e.matchArmCount = 0;
return e;
}
@@ -490,10 +494,182 @@ func parserParsePrimary(p: *Parser) -> *Expr {
return e;
}
// match expr { arms }
if kind == tkMatch {
return parserParseMatchExpr(p);
}
parserEmitDiag(p, line, col, "expected expression");
return parserMakeExpr(ekLiteral, line, col);
}
// ---------------------------------------------------------------------------
// Patterns (for match arms)
// ---------------------------------------------------------------------------
func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
let pat: *Pattern = bux_alloc(sizeof(Pattern)) as *Pattern;
pat.kind = kind;
pat.line = line;
pat.column = col;
pat.patIdent = "";
pat.patLitKind = 0;
pat.patLitText = "";
pat.patRangeInclusive = false;
pat.patEnumPath = "";
pat.patStructName = "";
pat.patChild1 = null as *Pattern;
pat.patChild2 = null as *Pattern;
return pat;
}
func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
let tok: LexToken = parserCurToken(p);
let line: uint32 = tok.line;
let col: uint32 = tok.column;
let kind: int = tok.kind;
// _
if kind == tkUnderscore {
discard parserAdvance(p);
return parserMakePattern(pkWildcard, line, col);
}
// Literals
if kind == tkIntLiteral || kind == tkFloatLiteral || kind == tkStringLiteral
|| kind == tkCharLiteral || kind == tkBoolLiteral {
discard parserAdvance(p);
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
pat.patLitKind = kind;
pat.patLitText = tok.text;
return pat;
}
// Ident / enum path / true|false as names
if kind == tkIdent {
discard parserAdvance(p);
let name: String = tok.text;
if String_Eq(name, "true") || String_Eq(name, "false") {
let pat: *Pattern = parserMakePattern(pkLiteral, line, col);
pat.patLitKind = tkBoolLiteral;
pat.patLitText = name;
return pat;
}
// Enum path: Enum::Variant or Enum::Variant(...)
if parserCheck(p, tkColonColon) {
var path: String = name;
while parserCheck(p, tkColonColon) {
discard parserAdvance(p);
let seg: LexToken = parserExpectIdentOrKeyword(p, "expected identifier in pattern path");
path = String_Concat(path, "::");
path = String_Concat(path, seg.text);
}
// Optional (args) for algebraic variants — parse and ignore bindings for now
if parserCheck(p, tkLParen) {
discard parserAdvance(p);
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
discard parserParsePattern(p);
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRParen, "expected ')' to close enum pattern");
}
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
pat.patEnumPath = path;
return pat;
}
// Bare name with (args): Variant(...) treated as single-segment enum
if parserCheck(p, tkLParen) {
discard parserAdvance(p);
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
discard parserParsePattern(p);
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRParen, "expected ')' to close pattern");
let pat: *Pattern = parserMakePattern(pkEnum, line, col);
pat.patEnumPath = name;
return pat;
}
// Ident binding / catch-all name
let pat: *Pattern = parserMakePattern(pkIdent, line, col);
pat.patIdent = name;
return pat;
}
parserEmitDiag(p, line, col, "expected pattern");
return parserMakePattern(pkWildcard, line, col);
}
func parserParsePattern(p: *Parser) -> *Pattern {
let locTok: LexToken = parserCurToken(p);
let line: uint32 = locTok.line;
let col: uint32 = locTok.column;
let left: *Pattern = parserParsePrimaryPattern(p);
// Range pattern: lo..hi or lo..=hi
if parserCheck(p, tkDotDot) || parserCheck(p, tkDotDotEqual) {
let inclusive: bool = parserCheck(p, tkDotDotEqual);
discard parserAdvance(p);
let right: *Pattern = parserParsePrimaryPattern(p);
let pat: *Pattern = parserMakePattern(pkRange, line, col);
pat.patRangeInclusive = inclusive;
pat.patChild1 = left;
pat.patChild2 = right;
return pat;
}
return left;
}
// match subject { pat => body, ... }
func parserParseMatchExpr(p: *Parser) -> *Expr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
discard parserAdvance(p); // match
p.structInitAllowed = false;
let subject: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
var firstArm: *MatchArm = null as *MatchArm;
var lastArm: *MatchArm = null as *MatchArm;
var armCount: int = 0;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRBrace) || parserPeek(p, 0) == tkEndOfFile { break; }
let armLine: uint32 = parserCurToken(p).line;
let armCol: uint32 = parserCurToken(p).column;
let mp: int = p.pos;
let pat: *Pattern = parserParsePattern(p);
discard parserExpect(p, tkFatArrow, "expected '=>' in match arm");
let body: *Expr = parserParseExpr(p);
let arm: *MatchArm = bux_alloc(sizeof(MatchArm)) as *MatchArm;
arm.line = armLine;
arm.column = armCol;
arm.pattern = pat;
arm.body = body;
arm.next = null as *MatchArm;
if firstArm == null as *MatchArm {
firstArm = arm;
lastArm = arm;
} else {
lastArm.next = arm;
lastArm = arm;
}
armCount = armCount + 1;
if parserCheck(p, tkComma) { discard parserAdvance(p); }
if p.pos == mp { discard parserAdvance(p); }
}
discard parserExpect(p, tkRBrace, "expected '}' to close match");
let e: *Expr = parserMakeExpr(ekMatch, line, col);
e.child1 = subject;
e.matchArms = firstArm;
e.matchArmCount = armCount;
return e;
}
// ---------------------------------------------------------------------------
// Closure: |params| -> Ret { body }
// ---------------------------------------------------------------------------
@@ -1117,29 +1293,14 @@ func parserParseStmt(p: *Parser) -> *Stmt {
return s;
}
// match
// match — expression statement (arms fully parsed)
if kind == tkMatch {
discard parserAdvance(p);
p.structInitAllowed = false;
let subject: *Expr = parserParseExpr(p);
p.structInitAllowed = true;
discard parserExpect(p, tkLBrace, "expected '{' to start match body");
// Skip match body (simplified — just parse arms as empty)
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
let mp: int = p.pos;
discard parserParseExpr(p); // pattern
if parserMatch(p, tkFatArrow) {
discard parserParseExpr(p); // body
}
if p.pos == mp { discard parserAdvance(p); }
}
discard parserExpect(p, tkRBrace, "expected '}' to close match");
let matchExpr: *Expr = parserParseMatchExpr(p);
let s: *Stmt = bux_alloc(sizeof(Stmt)) as *Stmt;
s.kind = skMatch;
s.kind = skExpr;
s.line = line;
s.column = col;
s.child1 = subject;
s.child1 = matchExpr;
return s;
}
+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;