feat: pattern bindings, empty closures, match-as-expr, string interp

Sessions 10–12 from QUALITY_PLAN:

- Pattern payload bindings (Some(value) => value) in bootstrap and selfhost
- Empty-param closures via || (tkPipePipe) with loop/return bodies
- Expression-form match: let x = match …; newline before arms
- f"…" string interpolation desugared to String_Concat + conversions
- Lexer preserves \{ \} for literal braces in f-strings
- Bootstrap fix: f"plain" strips the f prefix after escape processing
- Examples: pattern_matching, closure_control, match_let, string_interp

Selfhost-loop remains binary-identical; all examples and error goldens pass.
This commit is contained in:
2026-07-18 00:58:44 +03:00
parent f619316470
commit db41ba4d84
18 changed files with 1192 additions and 67 deletions
+56 -13
View File
@@ -757,26 +757,67 @@ proc checkTraitBounds(sema: var Sema, funcDecl: Decl, inferredTypes: seq[Type],
if not sema.typeImplements(inferredTypes[i], bound):
sema.emitError(loc, &"type '{inferredTypes[i].toString}' does not implement trait '{bound}'")
proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope) =
## Add pattern-bound identifiers to scope with unknown type (best-effort)
proc extractPatternBindings(sema: var Sema, pat: Pattern, scope: Scope, subjectType: Type = nil) =
## Add pattern-bound identifiers to scope. For enum payloads, resolve field types
## from the matched enum variant so arm bodies type-check correctly.
if pat == nil: return
case pat.kind
of pkIdent:
let sym = Symbol(kind: skVar, name: pat.patIdent, typ: makeUnknown(), isMutable: false)
let bindTy = if subjectType != nil and not subjectType.isUnknown: subjectType else: makeUnknown()
let sym = Symbol(kind: skVar, name: pat.patIdent, typ: bindTy, isMutable: false)
discard scope.define(sym)
of pkEnum:
for arg in pat.patEnumArgs:
sema.extractPatternBindings(arg, scope)
# Resolve variant field types from enum declaration
var enumName = ""
var variantName = ""
if pat.patEnumPath.len >= 2:
enumName = pat.patEnumPath[0]
variantName = pat.patEnumPath[^1]
elif pat.patEnumPath.len == 1:
variantName = pat.patEnumPath[0]
if subjectType != nil and subjectType.kind == tkNamed:
enumName = subjectType.name
var fieldTypes: seq[Type] = @[]
var namedFieldTypes: Table[string, Type]
if enumName != "":
let enumSym = sema.globalScope.lookup(enumName)
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
for v in enumSym.decl.declEnumVariants:
if v.name == variantName:
for f in v.fields:
fieldTypes.add(sema.resolveType(f))
for nf in v.namedFields:
namedFieldTypes[nf.name] = sema.resolveType(nf.ftype)
break
for i, arg in pat.patEnumArgs:
let argTy = if i < fieldTypes.len: fieldTypes[i] else: makeUnknown()
if arg.kind == pkIdent:
let sym = Symbol(kind: skVar, name: arg.patIdent, typ: argTy, isMutable: false)
discard scope.define(sym)
else:
sema.extractPatternBindings(arg, scope, argTy)
for nf in pat.patEnumNamed:
sema.extractPatternBindings(nf.pattern, scope)
let argTy = if namedFieldTypes.hasKey(nf.name): namedFieldTypes[nf.name] else: makeUnknown()
if nf.pattern.kind == pkIdent:
let sym = Symbol(kind: skVar, name: nf.pattern.patIdent, typ: argTy, isMutable: false)
discard scope.define(sym)
else:
sema.extractPatternBindings(nf.pattern, scope, argTy)
of pkTuple:
for elem in pat.patTupleElements:
sema.extractPatternBindings(elem, scope)
for i, elem in pat.patTupleElements:
let elemTy = if subjectType != nil and subjectType.kind == tkTuple and i < subjectType.inner.len:
subjectType.inner[i]
else: makeUnknown()
if elem.kind == pkIdent:
let sym = Symbol(kind: skVar, name: elem.patIdent, typ: elemTy, isMutable: false)
discard scope.define(sym)
else:
sema.extractPatternBindings(elem, scope, elemTy)
of pkStruct:
for f in pat.patStructFields:
sema.extractPatternBindings(f.pattern, scope)
of pkGuarded:
sema.extractPatternBindings(pat.patGuardedInner, scope)
sema.extractPatternBindings(pat.patGuardedInner, scope, subjectType)
else:
discard
@@ -1381,11 +1422,11 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
lastType = sema.checkStmt(stmt, blockScope)
return lastType
of ekMatch:
discard sema.checkExpr(expr.exprMatchSubject, scope)
let subjectType = sema.checkExpr(expr.exprMatchSubject, scope)
var resultType = makeUnknown()
for arm in expr.exprMatchArms:
var armScope = newScope(scope)
sema.extractPatternBindings(arm.pattern, armScope)
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
let armType = sema.checkExpr(arm.body, armScope)
if resultType.isUnknown:
resultType = armType
@@ -1541,9 +1582,11 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
discard sema.checkStmt(Stmt(kind: skExpr, loc: stmt.stmtForBody.loc, stmtExpr: Expr(kind: ekBlock, loc: stmt.stmtForBody.loc, exprBlock: stmt.stmtForBody)), forScope)
return makeVoid()
of skMatch:
discard sema.checkExpr(stmt.stmtMatchSubject, scope)
let subjectType = sema.checkExpr(stmt.stmtMatchSubject, scope)
for arm in stmt.stmtMatchArms:
discard sema.checkExpr(arm.body, scope)
var armScope = newScope(scope)
sema.extractPatternBindings(arm.pattern, armScope, subjectType)
discard sema.checkExpr(arm.body, armScope)
return makeVoid()
of skReturn:
if stmt.stmtReturnValue != nil: