feat: multi-stmt match arms and block-as-expression

Blocks can yield a value: last expression statement is the result.
Match arms accept block bodies, so multi-statement arms work:

  match n {
    1 => { let a = 10; a + 1 },
    _ => 0
  }

Bootstrap: lowerBlock(asExpr) lifts the trailing skExpr. Selfhost: parse
{...} as ekBlock, emit __blk_N yield temps (retTypeKind -2), and treat
only non-empty strValue as match/block yield. Nested enum/struct pattern
bindings recurse in both compilers.

Example: match_block.bux. Selfhost-loop remains binary-identical.
This commit is contained in:
2026-07-18 01:24:48 +03:00
parent e3ca724bfa
commit f9185c96b2
8 changed files with 337 additions and 98 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ SRC := bootstrap/main.nim
OUT := buxc
BUILD_DIR := build
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic struct_tuple_pat
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ctfe async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic struct_tuple_pat match_block
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
+40 -22
View File
@@ -185,19 +185,23 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
typ: makePointer(variantStructTy), loc: loc)
payloadBase = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc)
for i, arg in pattern.patEnumArgs:
if arg == nil or arg.kind != pkIdent:
if arg == nil:
continue
let fieldName = variantName & "_" & $i
let fieldTy = if i < fieldTypes.len: fieldTypes[i] else: makeInt()
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: payloadBase, fieldName: fieldName,
typ: makePointer(fieldTy), loc: loc)
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
if arg.patIdent notin ctx.patternBoundNames:
result.add(hirAlloca(arg.patIdent, fieldTy, loc))
ctx.patternBoundNames.incl(arg.patIdent)
result.add(hirStore(hirVar(arg.patIdent, fieldTy, loc), fieldLoad, loc))
if arg.kind == pkIdent:
if arg.patIdent notin ctx.patternBoundNames:
result.add(hirAlloca(arg.patIdent, fieldTy, loc))
ctx.patternBoundNames.incl(arg.patIdent)
result.add(hirStore(hirVar(arg.patIdent, fieldTy, loc), fieldLoad, loc))
else:
# Nested: Option::Some((a, b)), Pair::Two(Point { x, y })
result.add(ctx.matchPatternBindings(fieldLoad, arg, subjectEnumName, subjectHasData, loc))
for nf in pattern.patEnumNamed:
if nf.pattern == nil or nf.pattern.kind != pkIdent:
if nf.pattern == nil:
continue
var fieldTy = makeInt()
for entry in namedFields:
@@ -211,10 +215,13 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: variantLoad, fieldName: nf.name,
typ: makePointer(fieldTy), loc: loc)
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
if nf.pattern.patIdent notin ctx.patternBoundNames:
result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc))
ctx.patternBoundNames.incl(nf.pattern.patIdent)
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
if nf.pattern.kind == pkIdent:
if nf.pattern.patIdent notin ctx.patternBoundNames:
result.add(hirAlloca(nf.pattern.patIdent, fieldTy, loc))
ctx.patternBoundNames.incl(nf.pattern.patIdent)
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
else:
result.add(ctx.matchPatternBindings(fieldLoad, nf.pattern, subjectEnumName, subjectHasData, loc))
of pkGuarded:
result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc))
of pkTuple:
@@ -536,7 +543,7 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
# Forward declarations
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode
proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode
proc lowerClosureFunc(ctx: var LowerCtx, expr: Expr): HirFunc
proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
@@ -1339,7 +1346,7 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
typ: typ, loc: loc)
of ekBlock:
return ctx.lowerBlock(expr.exprBlock)
return ctx.lowerBlock(expr.exprBlock, asExpr = true)
of ekPostfix:
let operand = ctx.lowerExpr(expr.exprPostfixOperand)
@@ -1926,26 +1933,37 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
return HirNode(kind: hLit, litToken: Token(kind: tkIntLiteral, text: "0", loc: loc),
typ: makeVoid(), loc: loc)
proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
proc lowerBlock(ctx: var LowerCtx, blk: Block, asExpr = false): HirNode =
## asExpr=true: block is used as a value (`let x = { ... }`, match arm body).
## Last skExpr becomes the block result. Statement blocks (func body, if/while)
## keep asExpr=false so trailing void calls stay as statements.
if blk == nil: return nil
var stmts: seq[HirNode] = @[]
for s in blk.stmts:
let hir = ctx.lowerStmt(s)
if hir != nil:
stmts.add(hir)
# If the last statement is an expression, make it the block's result expression
var expr: HirNode = nil
if stmts.len > 0 and stmts[^1].kind == hBlock and stmts[^1].blockExpr != nil:
# Nested block expression (e.g., from match lowering) — lift it
if asExpr and stmts.len > 0 and blk.stmts.len > 0 and blk.stmts[^1].kind == skExpr:
let last = stmts[^1]
if last.kind == hBlock and last.blockExpr != nil:
# Nested yield block (match, block-expr) — lift result, keep side-effect stmts
stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc)
expr = last.blockExpr
elif last.kind in {hIf, hWhile, hLoop, hReturn, hBreak, hContinue, hAlloca, hStore, hAssign}:
discard
else:
# hBinary, hCall, hLit, hVar, hLoad, … — value expression
expr = last
discard stmts.pop()
elif stmts.len > 0 and stmts[^1].kind == hBlock and stmts[^1].blockExpr != nil:
# Nested block expression (e.g., match) inside statement context — lift for
# function last-expr return via blockExpr when present
let last = stmts[^1]
stmts[^1] = hirBlock(last.blockStmts, nil, makeVoid(), last.loc)
expr = last.blockExpr
elif stmts.len > 0 and stmts[^1].kind != hBlock:
# Last stmt is a simple expression-like node — we can't easily extract it,
# but for hVar/hLit/hCall etc. we could treat them as block expr.
# For now, leave as-is to avoid breaking control-flow statements.
discard
return hirBlock(stmts, expr, if expr != nil: expr.typ else: makeVoid(), blk.loc, isScope = true)
let typ = if expr != nil and expr.typ != nil: expr.typ else: makeVoid()
return hirBlock(stmts, expr, typ, blk.loc, isScope = true)
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
# Set up type substitution for generic functions
+15
View File
@@ -396,6 +396,21 @@ match p {
Point { x, y } => x * 10 + y,
_ => -1
}
// Multi-statement arm bodies (block expression; last expr is the value)
match n {
1 => {
let a: int = 10;
a + 1
},
_ => 0
}
// Block as expression
let r: int = {
let x: int = 5;
x + 6
};
```
---
+16 -4
View File
@@ -273,9 +273,21 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
---
## Сесия 16 (match block arms + block expressions)
1. **Block-as-expression:** `let r = { let x = 1; x + 2 }` — last skExpr is the value
2. **Multi-stmt match arms:** `1 => { PrintLine("…"); let a = 10; a + 1 }`
3. Bootstrap: `lowerBlock(..., asExpr)` promotes last expression; nested enum/struct pattern bindings
4. Selfhost: parse `{ … }` as `ekBlock`; yield temps `__blk_N`; fix `IsMatchYield` null-strValue false positive; retTypeKind `-2` for expr blocks
5. Nested: `Shape::Dot(Point { x, y })` works on bootstrap; selfhost covers struct/tuple/enum + block arms
6. Example: `examples/match_block.bux`
7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓
---
## Следващи стъпки
1. Match arm multi-stmt bodies (beyond single expr)
2. Nested patterns deeper (`Some((a, b))`, `Point { x: (a, b) }`)
3. LSP: wire hover types from real sema
4. Generic type inference for `Iter_Map` without explicit `<T,U>`
1. Deeper nested patterns (`Some((a, b))` with multi-field enum layout ergonomics)
2. LSP: wire hover types from real sema
3. Generic type inference for `Iter_Map` without explicit `<T,U>`
4. Match arm guards (`p if cond => …`)
+81
View File
@@ -0,0 +1,81 @@
// Multi-stmt match arms (block bodies) + block-as-expression
import Std::Io::{PrintLine, PrintInt};
import Std::Test::{Test_AssertEqInt, Test_Pass};
struct Point {
x: int,
y: int,
}
enum Option {
Some(int),
None
}
func Classify(n: int) -> int {
match n {
1 => {
let a: int = 10;
a + 1
},
2 => {
PrintLine("two");
let b: int = 20;
b * 2
},
_ => 0
}
}
func Main() -> int {
Test_AssertEqInt(Classify(1), 11);
Test_AssertEqInt(Classify(2), 40);
Test_AssertEqInt(Classify(9), 0);
// Block as expression outside match
let r: int = {
let x: int = 5;
let y: int = 6;
x + y
};
Test_AssertEqInt(r, 11);
// Tuple pattern + block body
let t: (int, int) = (3, 4);
let u: int = match t {
(a, b) => {
let prod: int = a * b;
prod
},
_ => 0
};
Test_AssertEqInt(u, 12);
// Struct pattern + block body
let p: Point = Point { x: 2, y: 5 };
let z: int = match p {
Point { x: px, y: py } => {
let sum: int = px + py;
sum
},
_ => -1
};
Test_AssertEqInt(z, 7);
// Enum payload + block
let opt: Option = Option { tag: Option_Some };
opt.data.Some_0 = 7;
let v: int = match opt {
Option::Some(n) => {
let doubled: int = n + n;
doubled
},
Option::None => 0
};
Test_AssertEqInt(v, 14);
PrintInt(Classify(2));
PrintLine("");
Test_Pass("match_block");
return 0;
}
+127 -44
View File
@@ -805,25 +805,25 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
var arg: *Pattern = pat.patArgs;
var ai: int = 0;
while arg != null as *Pattern {
if arg.kind == pkIdent {
var ftype: String = "int";
if ai == 0 { ftype = fieldType0; }
else if ai == 1 { ftype = fieldType1; }
let fieldName: String = String_Concat(String_Concat(variantName, "_"), String_FromInt(ai as int64));
var ftype: String = "int";
if ai == 0 { ftype = fieldType0; }
else if ai == 1 { ftype = fieldType1; }
let fieldName: String = String_Concat(String_Concat(variantName, "_"), String_FromInt(ai as int64));
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fPtr.kind = hFieldPtr;
fPtr.line = line;
fPtr.column = col;
fPtr.strValue = fieldName;
fPtr.child1 = payloadBase;
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fLoad.kind = hLoad;
fLoad.line = line;
fLoad.column = col;
fLoad.child1 = fPtr;
fLoad.typeName = ftype;
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fPtr.kind = hFieldPtr;
fPtr.line = line;
fPtr.column = col;
fPtr.strValue = fieldName;
fPtr.child1 = payloadBase;
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fLoad.kind = hLoad;
fLoad.line = line;
fLoad.column = col;
fLoad.child1 = fPtr;
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;
@@ -842,7 +842,6 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
store.child2 = fLoad;
alloca.child3 = store;
// Define in scope so body idents resolve
var bsym: Symbol;
bsym.kind = skVar;
bsym.name = arg.patIdent;
@@ -861,6 +860,19 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
tail.child3 = alloca;
tail = store;
}
} else if arg.kind == pkTuple || arg.kind == pkStruct || arg.kind == pkEnum {
// Nested pattern on payload field
let nested: *HirNode = Lcx_PatternBindings(ctx, fLoad, arg, subjectEnumName, subjectHasData, line, col);
if nested != null as *HirNode {
if head == null as *HirNode {
head = nested;
tail = nested;
while tail.child3 != null as *HirNode { tail = tail.child3; }
} else {
tail.child3 = nested;
while tail.child3 != null as *HirNode { tail = tail.child3; }
}
}
}
arg = arg.patNext;
ai = ai + 1;
@@ -872,6 +884,8 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
func Lcx_IsMatchYield(n: *HirNode) -> bool {
if n == null as *HirNode { return false; }
if n.kind != hBlock { return false; }
// strValue must be a real temp name — null/"" is a plain statement block
if n.strValue == null as String { return false; }
return !String_Eq(n.strValue, "");
}
@@ -957,7 +971,7 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
// Pattern bindings before body (so body idents resolve)
let bindHead: *HirNode = Lcx_PatternBindings(ctx, subject, cur.pattern, subjectEnumName, subjectHasData, line, col);
let bodyHir: *HirNode = Lcx_LowerExpr(ctx, cur.body);
// result = body
// result = body (expand block/match yield: run stmts then store result var)
let storeNode: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
storeNode.kind = hStore;
storeNode.line = line;
@@ -966,20 +980,32 @@ func Lcx_LowerMatch(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
resVar.kind = hVar;
resVar.strValue = resultName;
storeNode.child1 = resVar;
storeNode.child2 = bodyHir;
var bodyPrefix: *HirNode = null as *HirNode;
if Lcx_IsMatchYield(bodyHir) {
storeNode.child2 = Lcx_YieldVarOf(bodyHir);
bodyHir.strValue = "";
bodyPrefix = bodyHir.child1;
} else {
storeNode.child2 = bodyHir;
}
// armBlock = bindings... → storeNode
// armBlock = bindings → body stmts → store
let armBlock: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
armBlock.kind = hBlock;
armBlock.line = line;
armBlock.column = col;
if bindHead != null as *HirNode {
armBlock.child1 = bindHead;
// find tail of bind chain
var bt: *HirNode = bindHead;
while bt.child3 != null as *HirNode {
bt = bt.child3;
}
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;
} else {
armBlock.child1 = storeNode;
@@ -2158,6 +2184,7 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
}
// Block expression (boolValue = true means unsafe block)
// retTypeKind -2 → yield last expression as block value
if kind == ekBlock {
if expr.refBlock != null as *Block {
if expr.boolValue {
@@ -2165,12 +2192,12 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
let oldRelease: bool = ctx.releaseFunc;
ctx.checkedFunc = false;
ctx.releaseFunc = false;
let blockNode: *HirNode = Lcx_LowerBlock(ctx, expr.refBlock, -1);
let blockNode: *HirNode = Lcx_LowerBlock(ctx, expr.refBlock, -2);
ctx.checkedFunc = oldChecked;
ctx.releaseFunc = oldRelease;
return blockNode;
} else {
return Lcx_LowerBlock(ctx, expr.refBlock, -1);
return Lcx_LowerBlock(ctx, expr.refBlock, -2);
}
}
return n;
@@ -3053,27 +3080,30 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
if block == null as *Block { return null as *HirNode; }
if block.stmtCount == 0 { return null as *HirNode; }
// Build a linked list of HirNodes via child3:
// node1 (stmt1) → child3 → node2 (stmt2) → child3 → node3 (stmt3) → null
// child3 is safe for chaining because:
// hStore: child1=alloca, child2=value, child3 unused
// hReturn: child1=value, child2/child3 unused
// hCall: child1=arg1, child2=arg2, child3 unused
// retTypeKind:
// >= 0 → function body; last skExpr becomes return
// -1 → statement block (if/while/for); no return, no yield
// -2 → block-as-expression (match arm / let { ... }); yield last skExpr
let asExpr: bool = retTypeKind == -2;
var yieldName: String = "";
if asExpr {
ctx.varCounter = ctx.varCounter + 1;
yieldName = String_Concat("__blk_", String_FromInt(ctx.varCounter as int64));
}
// Build a linked list of HirNodes via child3
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 {
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
// Last expression statement in a non-void function → implicit return
if isLast && !asExpr && 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;
@@ -3083,7 +3113,6 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
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;
@@ -3093,7 +3122,6 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
}
lastInBlock.child3 = retNode;
}
// Clear yield marker so emit treats it as plain block
exprNode.strValue = "";
lowered = exprNode;
} else if exprNode.kind == hReturn {
@@ -3107,6 +3135,53 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
lowered = retNode;
}
}
} else if isLast && asExpr && stmt.kind == skExpr && stmt.child1 != null as *Expr {
// Block-as-expression: last expr is the yield value
let exprNode: *HirNode = Lcx_LowerExpr(ctx, stmt.child1);
// alloca yield temp
let allocaN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
allocaN.kind = hAlloca;
allocaN.line = stmt.line;
allocaN.column = stmt.column;
allocaN.strValue = yieldName;
allocaN.typeName = "int";
if exprNode != null as *HirNode && exprNode.typeName != null as String
&& !String_Eq(exprNode.typeName, "") {
allocaN.typeName = exprNode.typeName;
}
// store: yield = value (handle nested match yield)
let storeN: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
storeN.kind = hStore;
storeN.line = stmt.line;
storeN.column = stmt.column;
let yv: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
yv.kind = hVar;
yv.strValue = yieldName;
storeN.child1 = yv;
if exprNode != null as *HirNode && Lcx_IsMatchYield(exprNode) {
// Expand nested match stmts then store its result var
let yvar: *HirNode = Lcx_YieldVarOf(exprNode);
storeN.child2 = yvar;
exprNode.strValue = "";
// chain: exprNode stmts → alloca → store
var lastIn: *HirNode = exprNode.child1;
if lastIn == null as *HirNode {
exprNode.child1 = allocaN;
allocaN.child3 = storeN;
lowered = exprNode;
} else {
while lastIn.child3 != null as *HirNode {
lastIn = lastIn.child3;
}
lastIn.child3 = allocaN;
allocaN.child3 = storeN;
lowered = exprNode;
}
} else {
storeN.child2 = exprNode;
allocaN.child3 = storeN;
lowered = allocaN;
}
} else {
lowered = Lcx_LowerStmt(ctx, stmt);
}
@@ -3115,8 +3190,12 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
firstNode = lowered;
prevNode = lowered;
} else {
// Walk to end of chain (lowered may itself be a multi-node chain)
prevNode.child3 = lowered;
prevNode = lowered;
while prevNode.child3 != null as *HirNode {
prevNode = prevNode.child3;
}
}
}
stmt = stmt.nextStmt;
@@ -3129,6 +3208,10 @@ func Lcx_LowerBlock(ctx: *LowerCtx, block: *Block, retTypeKind: int) -> *HirNode
n.column = block.column;
n.boolValue = true;
n.child1 = firstNode;
if asExpr && !String_Eq(yieldName, "") {
n.strValue = yieldName;
n.typeName = "int";
}
return n;
}
+8
View File
@@ -661,6 +661,14 @@ func parserParsePrimary(p: *Parser) -> *Expr {
return parserParseClosure(p);
}
// Block expression { stmts; value }
if kind == tkLBrace {
let e: *Expr = parserMakeExpr(ekBlock, line, col);
e.boolValue = false;
e.refBlock = parserParseBlock(p);
return e;
}
// unsafe { ... } — unsafe block expression
if kind == tkUnsafe {
discard parserAdvance(p);
+49 -27
View File
@@ -446,10 +446,10 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
var arg: *Pattern = pat.patArgs;
var ai: int = 0;
while arg != null as *Pattern {
var ftype: String = "int";
if ai == 0 { ftype = fieldType0; }
else if ai == 1 { ftype = fieldType1; }
if arg.kind == pkIdent {
var ftype: String = "int";
if ai == 0 { ftype = fieldType0; }
else if ai == 1 { ftype = fieldType1; }
var bsym: Symbol;
Sema_ZeroInitSymbol(&bsym);
bsym.kind = skVar;
@@ -467,6 +467,14 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
bsym.isPublic = false;
bsym.decl = null as *Decl;
discard Scope_Define(sema.scope, bsym);
} else {
// Nested pattern: synthesize a fake subject expr with payload type
let fake: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
let fte: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
fte.kind = tekNamed;
fte.typeName = ftype;
fake.refType = fte;
Sema_BindPattern(sema, arg, fake);
}
arg = arg.patNext;
ai = ai + 1;
@@ -478,19 +486,17 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
var ei: int = 0;
var elem: *Pattern = pat.patArgs;
while elem != null as *Pattern {
if elem.kind == pkIdent {
var ety: String = "int";
// Tuple_int_int → fields are ints by default; prefer subject type args if present
if subject != null as *Expr && subject.refType != null as *TypeExpr {
if subject.refType.kind == tekTuple {
// typeArgName0 / typeArgName1 store element type names when available
if ei == 0 && !String_Eq(subject.refType.typeArgName0, "") {
ety = subject.refType.typeArgName0;
} else if ei == 1 && !String_Eq(subject.refType.typeArgName1, "") {
ety = subject.refType.typeArgName1;
}
var ety: String = "int";
if subject != null as *Expr && subject.refType != null as *TypeExpr {
if subject.refType.kind == tekTuple {
if ei == 0 && !String_Eq(subject.refType.typeArgName0, "") {
ety = subject.refType.typeArgName0;
} else if ei == 1 && !String_Eq(subject.refType.typeArgName1, "") {
ety = subject.refType.typeArgName1;
}
}
}
if elem.kind == pkIdent {
var bsym: Symbol;
Sema_ZeroInitSymbol(&bsym);
bsym.kind = skVar;
@@ -505,6 +511,13 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
bsym.refType = te;
bsym.isMutable = false;
discard Scope_Define(sema.scope, bsym);
} else {
let fake: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
let fte: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
fte.kind = tekNamed;
fte.typeName = ety;
fake.refType = fte;
Sema_BindPattern(sema, elem, fake);
}
elem = elem.patNext;
ei = ei + 1;
@@ -519,23 +532,25 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
}
var field: *Pattern = pat.patArgs;
while field != null as *Pattern {
if field.kind == pkIdent {
var ftype: String = "int";
if !String_Eq(structName, "") {
let ssym: Symbol = Scope_Lookup(sema.scope, structName);
if ssym.decl != null as *Decl && ssym.decl.kind == dkStruct && ssym.decl.fields != null as *StructField {
var fi: int = 0;
while fi < ssym.decl.fieldCount {
let sf: StructField = ssym.decl.fields[fi];
if String_Eq(sf.name, field.patFieldName) {
if sf.refFieldType != null as *TypeExpr && !String_Eq(sf.refFieldType.typeName, "") {
ftype = sf.refFieldType.typeName;
}
var ftype: String = "int";
if !String_Eq(structName, "") {
let ssym: Symbol = Scope_Lookup(sema.scope, structName);
if ssym.decl != null as *Decl && ssym.decl.kind == dkStruct && ssym.decl.fields != null as *StructField {
var fi: int = 0;
while fi < ssym.decl.fieldCount {
let sf: StructField = ssym.decl.fields[fi];
var fname: String = field.patFieldName;
if String_Eq(fname, "") { fname = field.patIdent; }
if String_Eq(sf.name, fname) {
if sf.refFieldType != null as *TypeExpr && !String_Eq(sf.refFieldType.typeName, "") {
ftype = sf.refFieldType.typeName;
}
fi = fi + 1;
}
fi = fi + 1;
}
}
}
if field.kind == pkIdent {
var bsym: Symbol;
Sema_ZeroInitSymbol(&bsym);
bsym.kind = skVar;
@@ -550,6 +565,13 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
bsym.refType = te;
bsym.isMutable = false;
discard Scope_Define(sema.scope, bsym);
} else {
let fake: *Expr = bux_alloc(sizeof(Expr)) as *Expr;
let fte: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
fte.kind = tekNamed;
fte.typeName = ftype;
fake.refType = fte;
Sema_BindPattern(sema, field, fake);
}
field = field.patNext;
}