diff --git a/Makefile b/Makefile index cc5d586..f4d7c48 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index 5398939..c902111 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -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 diff --git a/bootstrap/lir_c_backend.nim b/bootstrap/lir_c_backend.nim index c2ad825..ee35716 100644 --- a/bootstrap/lir_c_backend.nim +++ b/bootstrap/lir_c_backend.nim @@ -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: diff --git a/bootstrap/parser.nim b/bootstrap/parser.nim index 95aebb5..5767c14 100644 --- a/bootstrap/parser.nim +++ b/bootstrap/parser.nim @@ -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") diff --git a/bootstrap/sema.nim b/bootstrap/sema.nim index 23e9a60..ec1d8c8 100644 --- a/bootstrap/sema.nim +++ b/bootstrap/sema.nim @@ -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: diff --git a/docs/LanguageRef.md b/docs/LanguageRef.md index 8fff3bc..2bfa0e3 100644 --- a/docs/LanguageRef.md +++ b/docs/LanguageRef.md @@ -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 +} +``` --- diff --git a/docs/QUALITY_PLAN.md b/docs/QUALITY_PLAN.md index 19b6271..85031e2 100644 --- a/docs/QUALITY_PLAN.md +++ b/docs/QUALITY_PLAN.md @@ -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 `` diff --git a/examples/struct_tuple_pat.bux b/examples/struct_tuple_pat.bux new file mode 100644 index 0000000..4b79a18 --- /dev/null +++ b/examples/struct_tuple_pat.bux @@ -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; +} diff --git a/src/ast.bux b/src/ast.bux index a607c78..65f526f 100644 --- a/src/ast.bux +++ b/src/ast.bux @@ -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 } diff --git a/src/hir_lower.bux b/src/hir_lower.bux index b0b0e35..ab3f2ce 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -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 { diff --git a/src/parser.bux b/src/parser.bux index c9ffa97..5d10fa1 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -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); } diff --git a/src/sema.bux b/src/sema.bux index ae29a9b..ae03ed9 100644 --- a/src/sema.bux +++ b/src/sema.bux @@ -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 {