feat: struct and tuple patterns in match (bootstrap + selfhost)

Support destructuring in match arms:
- Tuple: (a, b) binds subject._0 / _1
- Struct: Point { x: px, y: py } and shorthand Point { x, y }

Bootstrap: matchPatternBindings for pkTuple/pkStruct; register local
tuple typedefs from function bodies. Selfhost: parse, Sema_BindPattern,
Lcx_PatternBindings with scope defines. Fix operator-overload path that
crashed when typeName was null after pattern binds.

Example: examples/struct_tuple_pat.bux. Selfhost-loop IDENTICAL.
This commit is contained in:
2026-07-18 01:15:44 +03:00
parent eac78f28c1
commit e3ca724bfa
12 changed files with 494 additions and 16 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
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
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
+47
View File
@@ -217,6 +217,53 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
result.add(hirStore(hirVar(nf.pattern.patIdent, fieldTy, loc), fieldLoad, loc))
of pkGuarded:
result.add(ctx.matchPatternBindings(subject, pattern.patGuardedInner, subjectEnumName, subjectHasData, loc))
of pkTuple:
# (a, b) => bind a = subject._0, b = subject._1
for i, elem in pattern.patTupleElements:
if elem == nil:
continue
let fieldName = "_" & $i
let fieldTy = if subject.typ != nil and subject.typ.kind == tkTuple and i < subject.typ.inner.len:
subject.typ.inner[i]
else: makeInt()
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: fieldName,
typ: makePointer(fieldTy), loc: loc)
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
if elem.kind == pkIdent:
if elem.patIdent notin ctx.patternBoundNames:
result.add(hirAlloca(elem.patIdent, fieldTy, loc))
ctx.patternBoundNames.incl(elem.patIdent)
result.add(hirStore(hirVar(elem.patIdent, fieldTy, loc), fieldLoad, loc))
else:
# Nested patterns: recurse with field as subject
result.add(ctx.matchPatternBindings(fieldLoad, elem, subjectEnumName, subjectHasData, loc))
of pkStruct:
# Point { x: px, y: py } => px = subject.x, py = subject.y
var structName = pattern.patStructName
if structName.len == 0 and subject.typ != nil and subject.typ.kind == tkNamed:
structName = subject.typ.name
var fieldTypes = initTable[string, Type]()
if structName.len > 0:
let ssym = ctx.globalScope.lookup(structName)
if ssym != nil and ssym.decl != nil and ssym.decl.kind == dkStruct:
for f in ssym.decl.declStructFields:
fieldTypes[f.name] = ctx.resolveTypeExpr(f.ftype)
for entry in pattern.patStructFields:
let fname = entry.name
let fpat = entry.pattern
if fpat == nil:
continue
let fieldTy = if fieldTypes.hasKey(fname): fieldTypes[fname] else: makeInt()
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: subject, fieldName: fname,
typ: makePointer(fieldTy), loc: loc)
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
if fpat.kind == pkIdent:
if fpat.patIdent notin ctx.patternBoundNames:
result.add(hirAlloca(fpat.patIdent, fieldTy, loc))
ctx.patternBoundNames.incl(fpat.patIdent)
result.add(hirStore(hirVar(fpat.patIdent, fieldTy, loc), fieldLoad, loc))
else:
result.add(ctx.matchPatternBindings(fieldLoad, fpat, subjectEnumName, subjectHasData, loc))
else:
discard
+50
View File
@@ -767,10 +767,60 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
else:
discard
proc walkHirForTuples(n: HirNode) =
if n == nil: return
registerTuple(n.typ)
case n.kind
of hAlloca:
registerTuple(n.allocaType)
of hBlock:
for s in n.blockStmts: walkHirForTuples(s)
walkHirForTuples(n.blockExpr)
of hIf:
walkHirForTuples(n.ifCond)
walkHirForTuples(n.ifThen)
walkHirForTuples(n.ifElse)
of hWhile:
walkHirForTuples(n.whileCond)
walkHirForTuples(n.whileBody)
of hLoop:
walkHirForTuples(n.loopBody)
of hReturn:
walkHirForTuples(n.returnValue)
of hStore:
walkHirForTuples(n.storePtr)
walkHirForTuples(n.storeValue)
of hAssign:
walkHirForTuples(n.assignTarget)
walkHirForTuples(n.assignValue)
of hBinary:
walkHirForTuples(n.binaryLeft)
walkHirForTuples(n.binaryRight)
of hUnary:
walkHirForTuples(n.unaryOperand)
of hCall:
for a in n.callArgs: walkHirForTuples(a)
of hCallIndirect:
walkHirForTuples(n.callIndirectCallee)
for a in n.callIndirectArgs: walkHirForTuples(a)
of hLoad:
walkHirForTuples(n.loadPtr)
of hFieldPtr:
walkHirForTuples(n.fieldPtrBase)
of hFieldAccess:
walkHirForTuples(n.fieldAccessBase)
of hStructInit:
for f in n.structInitFields: walkHirForTuples(f.value)
of hTupleInit:
for e in n.tupleInitElements: walkHirForTuples(e)
else:
discard
for f in module.funcs:
registerTuple(f.retType)
for p in f.params:
registerTuple(p.typ)
walkHirForTuples(f.body)
for ef in module.externFuncs:
registerTuple(ef.retType)
for p in ef.params:
+7 -3
View File
@@ -324,7 +324,7 @@ proc parsePrimaryPattern(p: var Parser): Pattern =
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: args, patEnumNamed: named)
return Pattern(kind: pkEnum, loc: loc, patEnumPath: path, patEnumArgs: @[], patEnumNamed: @[])
elif p.check(tkLBrace):
# Struct pattern: Point { x: 0, y: 0 }
# Struct pattern: Point { x: px, y: py } or shorthand Point { x, y }
discard p.advance()
var fields: seq[tuple[name: string, pattern: Pattern]] = @[]
while not p.check(tkRBrace) and not p.isAtEnd:
@@ -333,8 +333,12 @@ proc parsePrimaryPattern(p: var Parser): Pattern =
if p.check(tkRBrace) or p.isAtEnd:
break
let fieldName = p.expect(tkIdent, "expected field name in struct pattern").text
discard p.expect(tkColon, "expected ':' after field name in pattern")
fields.add((fieldName, p.parsePattern()))
if p.check(tkColon):
discard p.advance()
fields.add((fieldName, p.parsePattern()))
else:
# Shorthand: { x } means { x: x }
fields.add((fieldName, Pattern(kind: pkIdent, loc: loc, patIdent: fieldName)))
if p.check(tkComma):
discard p.advance()
discard p.expect(tkRBrace, "expected '}' to close struct pattern")
+17 -2
View File
@@ -814,8 +814,23 @@ proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope, subjectT
else:
sema.extractPatternBindings(elem, scope, elemTy)
of pkStruct:
for f in pat.patStructFields:
sema.extractPatternBindings(f.pattern, scope)
# Resolve field types from struct declaration when possible
var fieldTypes = initTable[string, Type]()
var structName = pat.patStructName
if structName.len == 0 and subjectType != nil and subjectType.kind == tkNamed:
structName = subjectType.name
if structName.len > 0:
let ssym = sema.globalScope.lookup(structName)
if ssym != nil and ssym.decl != nil and ssym.decl.kind == dkStruct:
for f in ssym.decl.declStructFields:
fieldTypes[f.name] = sema.resolveType(f.ftype)
for entry in pat.patStructFields:
let fty = if fieldTypes.hasKey(entry.name): fieldTypes[entry.name] else: makeUnknown()
if entry.pattern != nil and entry.pattern.kind == pkIdent:
let sym = Symbol(kind: skVar, name: entry.pattern.patIdent, typ: fty, isMutable: false)
discard scope.define(sym)
else:
sema.extractPatternBindings(entry.pattern, scope, fty)
of pkGuarded:
sema.extractPatternBindings(pat.patGuardedInner, scope, subjectType)
else:
+14 -1
View File
@@ -383,7 +383,20 @@ Supported patterns:
- Identifier catch-all: `name` (binds whole subject)
- Range: `1..9`, `1..=9`
- Enum tags + **payload bindings**: `Option::Some(value)`, `Pair::Two(a, b)`
- Struct / tuple / guard patterns: parsed; full lowering still evolving
- **Tuple patterns**: `(a, b)` → binds `subject._0`, `subject._1`
- **Struct patterns**: `Point { x: px, y: py }` or shorthand `Point { x, y }`
- Guard patterns: parsed; full lowering still evolving
```bux
match pair {
(a, b) => a + b,
_ => 0
}
match p {
Point { x, y } => x * 10 + y,
_ => -1
}
```
---
+15 -3
View File
@@ -261,9 +261,21 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
---
## Сесия 15 (struct/tuple patterns)
1. **Tuple patterns:** `match t { (a, b) => a + b }` — bind `subject._0` / `._1`
2. **Struct patterns:** `Point { x: px, y: py }` + shorthand `Point { x, y }`
3. Bootstrap: `matchPatternBindings` for pkTuple/pkStruct; field types from struct decl; range registration of local tuple typedefs
4. Selfhost: parse `()` / `Name { … }` patterns; `Sema_BindPattern` + `Lcx_PatternBindings` with Scope_Define
5. Fix: operator-overload path treated `String_Eq(null, "")` as non-empty → crash on `a + b` after pattern bind
6. Example: `examples/struct_tuple_pat.bux`
7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓
---
## Следващи стъпки
1. Struct/tuple patterns (`Point { x, y }`, `(a, b)`) + nested bindings
2. Match arm multi-stmt bodies (beyond single expr)
3. LSP: wire hover types from real sema (replace lightweight index where possible)
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>`
+53
View File
@@ -0,0 +1,53 @@
// Struct and tuple patterns in match: (a, b), Point { x, y }
import Std::Io::{PrintLine, PrintInt};
import Std::Test::{Test_AssertEqInt, Test_Pass};
struct Point {
x: int,
y: int,
}
func SumPair(t: (int, int)) -> int {
match t {
(a, b) => a + b,
_ => 0
}
}
func PointCode(p: Point) -> int {
match p {
Point { x: px, y: py } => px * 10 + py,
_ => -1
}
}
func PointShorthand(p: Point) -> int {
// { x, y } means { x: x, y: y }
match p {
Point { x, y } => x + y,
_ => 0
}
}
func Main() -> int {
let t: (int, int) = (10, 20);
Test_AssertEqInt(SumPair(t), 30);
let lit: (int, int) = (7, 8);
let s: int = match lit {
(a, b) => a * b,
_ => 0
};
Test_AssertEqInt(s, 56);
let p: Point = Point { x: 3, y: 4 };
Test_AssertEqInt(PointCode(p), 34);
Test_AssertEqInt(PointShorthand(p), 7);
PrintInt(SumPair(t));
PrintLine("");
PrintInt(PointCode(p));
PrintLine("");
Test_Pass("struct_tuple_pat");
return 0;
}
+3 -2
View File
@@ -76,10 +76,11 @@ struct Pattern {
patLitText: String, // for pkLiteral (token text)
patRangeInclusive: bool, // for pkRange
patEnumPath: String, // for pkEnum: "Enum::Variant"
patStructName: String, // for pkStruct
patStructName: String, // for pkStruct (type name)
patFieldName: String, // for struct field entry: field name in Point { x: a }
patChild1: *Pattern, // range lo / nested
patChild2: *Pattern, // range hi / nested
patArgs: *Pattern, // pkEnum payload args (head)
patArgs: *Pattern, // pkEnum/pkTuple/pkStruct field list (head)
patNext: *Pattern, // next sibling in patArgs list
}
+145 -4
View File
@@ -569,9 +569,11 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
line: uint32, col: uint32) -> *HirNode {
if pat == null as *Pattern { return null as *HirNode; }
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, "") {
// keep int default for catch-all unless subject has a type name
ty = subject.typeName;
}
let alloca: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
alloca.kind = hAlloca;
@@ -589,8 +591,144 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
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;
}
// Tuple: (a, b) → a = subject._0; b = subject._1
if pat.kind == pkTuple {
var head: *HirNode = null as *HirNode;
var tail: *HirNode = null as *HirNode;
var ei: int = 0;
var elem: *Pattern = pat.patArgs;
while elem != null as *Pattern {
if elem.kind == pkIdent && !String_Eq(elem.patIdent, "_") {
var fieldName: String = "_0";
if ei == 1 { fieldName = "_1"; }
else if ei == 2 { fieldName = "_2"; }
else if ei == 3 { fieldName = "_3"; }
else if ei > 3 { fieldName = String_Concat("_", String_FromInt(ei 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 = subject;
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fLoad.kind = hLoad;
fLoad.line = line;
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;
}
}
elem = elem.patNext;
ei = ei + 1;
}
return head;
}
// Struct: Point { x: a } → a = subject.x
if pat.kind == pkStruct {
var head: *HirNode = null as *HirNode;
var tail: *HirNode = null as *HirNode;
var field: *Pattern = pat.patArgs;
while field != null as *Pattern {
if field.kind == pkIdent && !String_Eq(field.patIdent, "_") {
var fname: String = field.patFieldName;
if String_Eq(fname, "") { fname = field.patIdent; }
let fPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fPtr.kind = hFieldPtr;
fPtr.line = line;
fPtr.column = col;
fPtr.strValue = fname;
fPtr.child1 = subject;
let fLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
fLoad.kind = hLoad;
fLoad.line = line;
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;
}
}
field = field.patNext;
}
return head;
}
if pat.kind != pkEnum || !subjectHasData { return null as *HirNode; }
var enumName: String = "";
@@ -1115,12 +1253,15 @@ func Lcx_LowerExpr(ctx: *LowerCtx, expr: *Expr) -> *HirNode {
if expr.child1 != null as *Expr && expr.child1.refType != null as *TypeExpr {
let refTe: *TypeExpr = expr.child1.refType;
if refTe.kind == tekNamed {
receiverTypeName = refTe.typeName;
if refTe.typeName != null as String { receiverTypeName = refTe.typeName; }
} else if refTe.kind == tekPointer && refTe.pointerPointee != null as *TypeExpr && refTe.pointerPointee.kind == tekNamed {
receiverTypeName = refTe.pointerPointee.typeName;
if refTe.pointerPointee.typeName != null as String {
receiverTypeName = refTe.pointerPointee.typeName;
}
}
}
if !String_Eq(receiverTypeName, "") {
// Note: String_Eq(null, "") is false — must also reject null type names
if receiverTypeName != null as String && !String_Eq(receiverTypeName, "") {
let funcName: String = String_Concat(String_Concat(receiverTypeName, "_"), opMethodName);
let sym: Symbol = Scope_Lookup(ctx.scope, funcName);
if sym.kind == skFunc && sym.decl != null as *Decl {
+60
View File
@@ -694,6 +694,7 @@ func parserMakePattern(kind: int, line: uint32, col: uint32) -> *Pattern {
pat.patRangeInclusive = false;
pat.patEnumPath = "";
pat.patStructName = "";
pat.patFieldName = "";
pat.patChild1 = null as *Pattern;
pat.patChild2 = null as *Pattern;
pat.patArgs = null as *Pattern;
@@ -767,6 +768,42 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
pat.patArgs = enumArgs;
return pat;
}
// Struct pattern: Point { x: a, y: b } or shorthand Point { x, y }
if parserCheck(p, tkLBrace) {
discard parserAdvance(p);
var fieldHead: *Pattern = null as *Pattern;
var fieldTail: *Pattern = null as *Pattern;
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
while parserCheck(p, tkNewLine) { discard parserAdvance(p); }
if parserCheck(p, tkRBrace) { break; }
let ftok: LexToken = parserExpectIdentOrKeyword(p, "expected field name in struct pattern");
let fieldName: String = ftok.text;
var fieldPat: *Pattern = null as *Pattern;
if parserCheck(p, tkColon) {
discard parserAdvance(p);
fieldPat = parserParsePattern(p);
} else {
// Shorthand { x } → { x: x }
fieldPat = parserMakePattern(pkIdent, line, col);
fieldPat.patIdent = fieldName;
}
fieldPat.patFieldName = fieldName;
if fieldHead == null as *Pattern {
fieldHead = fieldPat;
fieldTail = fieldPat;
} else {
fieldTail.patNext = fieldPat;
fieldTail = fieldPat;
}
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRBrace, "expected '}' to close struct pattern");
let spat: *Pattern = parserMakePattern(pkStruct, line, col);
spat.patStructName = name;
spat.patArgs = fieldHead;
return spat;
}
// Bare name with (args): Variant(...) treated as single-segment enum
if parserCheck(p, tkLParen) {
discard parserAdvance(p);
@@ -796,6 +833,29 @@ func parserParsePrimaryPattern(p: *Parser) -> *Pattern {
return pat;
}
// Tuple pattern: (a, b)
if kind == tkLParen {
discard parserAdvance(p);
var head: *Pattern = null as *Pattern;
var tail: *Pattern = null as *Pattern;
while !parserCheck(p, tkRParen) && parserPeek(p, 0) != tkEndOfFile {
let elem: *Pattern = parserParsePattern(p);
if head == null as *Pattern {
head = elem;
tail = elem;
} else {
tail.patNext = elem;
tail = elem;
}
if parserCheck(p, tkComma) { discard parserAdvance(p); }
else { break; }
}
discard parserExpect(p, tkRParen, "expected ')' to close tuple pattern");
let tpat: *Pattern = parserMakePattern(pkTuple, line, col);
tpat.patArgs = head;
return tpat;
}
parserEmitDiag(p, line, col, "expected pattern");
return parserMakePattern(pkWildcard, line, col);
}
+82
View File
@@ -473,6 +473,88 @@ func Sema_BindPattern(sema: *Sema, pat: *Pattern, subject: *Expr) {
}
return;
}
// Tuple pattern: (a, b) — bind elements from subject tuple types
if pat.kind == pkTuple {
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 bsym: Symbol;
Sema_ZeroInitSymbol(&bsym);
bsym.kind = skVar;
bsym.name = elem.patIdent;
bsym.typeName = ety;
bsym.typeKind = tyInt;
if String_Eq(ety, "String") || String_Eq(ety, "str") { bsym.typeKind = tyStr; }
else if String_Eq(ety, "bool") { bsym.typeKind = tyBool; }
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = ety;
bsym.refType = te;
bsym.isMutable = false;
discard Scope_Define(sema.scope, bsym);
}
elem = elem.patNext;
ei = ei + 1;
}
return;
}
// Struct pattern: Point { x: a, y: b }
if pat.kind == pkStruct {
var structName: String = pat.patStructName;
if String_Eq(structName, "") && subject != null as *Expr && subject.refType != null as *TypeExpr {
structName = subject.refType.typeName;
}
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;
}
}
fi = fi + 1;
}
}
}
var bsym: Symbol;
Sema_ZeroInitSymbol(&bsym);
bsym.kind = skVar;
bsym.name = field.patIdent;
bsym.typeName = ftype;
bsym.typeKind = tyInt;
if String_Eq(ftype, "String") || String_Eq(ftype, "str") { bsym.typeKind = tyStr; }
else if String_Eq(ftype, "bool") { bsym.typeKind = tyBool; }
let te: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
te.kind = tekNamed;
te.typeName = ftype;
bsym.refType = te;
bsym.isMutable = false;
discard Scope_Define(sema.scope, bsym);
}
field = field.patNext;
}
return;
}
}
func Sema_IsMutRefDeref(target: *Expr) -> bool {