Phase 8.2.4: Lifetime syntax ('a) support

- Lexer tokenizes 'a as tkLifetime
- Parser accepts lifetimes in type params <'a> and reference types &'a T / &'a mut T
- AST TypeExpr has refLifetime field for tekRef/tekMutRef
- TypeParam gains isLifetime flag for generic monomorphization
- Sema infers lifetime params as no-op dummy values; unwraps pointee types for params inside refs
- HIR lower skips lifetime params when generating mangled names and substitution tables
- &mut expr syntax supported in parser (consumed as part of unary &)
This commit is contained in:
2026-06-01 11:32:40 +03:00
parent 5830548d54
commit bf9e73d56e
6 changed files with 114 additions and 29 deletions
+2
View File
@@ -47,6 +47,7 @@ type
sliceSize*: Expr ## nil for unsized slices T[] sliceSize*: Expr ## nil for unsized slices T[]
of tekOwn, tekPointer, tekRef, tekMutRef: of tekOwn, tekPointer, tekRef, tekMutRef:
pointerPointee*: TypeExpr pointerPointee*: TypeExpr
refLifetime*: string ## only meaningful for tekRef/tekMutRef
of tekDynRef: of tekDynRef:
dynInterface*: string dynInterface*: string
of tekTuple: of tekTuple:
@@ -300,6 +301,7 @@ type
TypeParam* = object TypeParam* = object
name*: string name*: string
bounds*: seq[string] ## e.g. ["Comparable"] for <T: Comparable> bounds*: seq[string] ## e.g. ["Comparable"] for <T: Comparable>
isLifetime*: bool ## true for lifetime params like 'a
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Declarations # Declarations
+43 -15
View File
@@ -564,13 +564,30 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
else: discard else: discard
if calleeName != "": if calleeName != "":
var typeSuffix = "" var typeSuffix = ""
for i, targ in expr.exprCallInferredTypeArgs: var typeArgIdx = 0
if i > 0: if ctx.genericFuncs.hasKey(calleeName):
typeSuffix.add("_") let genericDecl = ctx.genericFuncs[calleeName]
if targ.kind == tekNamed: for j, tp in genericDecl.declFuncTypeParams:
typeSuffix.add(targ.typeName) if tp.isLifetime: continue
else: if typeArgIdx > 0:
typeSuffix.add("unknown") typeSuffix.add("_")
if j < expr.exprCallInferredTypeArgs.len:
let targ = expr.exprCallInferredTypeArgs[j]
if targ.kind == tekNamed:
typeSuffix.add(targ.typeName)
else:
typeSuffix.add("unknown")
else:
typeSuffix.add("unknown")
inc(typeArgIdx)
else:
for i, targ in expr.exprCallInferredTypeArgs:
if i > 0:
typeSuffix.add("_")
if targ.kind == tekNamed:
typeSuffix.add(targ.typeName)
else:
typeSuffix.add("unknown")
let mangledName = calleeName & "_" & typeSuffix let mangledName = calleeName & "_" & typeSuffix
let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs) let args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs)
return hirCall(mangledName, args, typ, loc) return hirCall(mangledName, args, typ, loc)
@@ -1072,14 +1089,17 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs:
return baseMethodName return baseMethodName
var subst = initTable[string, Type]() var subst = initTable[string, Type]()
var typeSuffix = "" var typeSuffix = ""
var typeArgIdx = 0
for i, tp in genericDecl.declFuncTypeParams: for i, tp in genericDecl.declFuncTypeParams:
if i > 0: typeSuffix.add("_") if tp.isLifetime: continue
if i < typeArgs.len: if typeArgIdx > 0: typeSuffix.add("_")
let argType = ctx.resolveTypeExpr(typeArgs[i]) if typeArgIdx < typeArgs.len:
let argType = ctx.resolveTypeExpr(typeArgs[typeArgIdx])
subst[tp.name] = argType subst[tp.name] = argType
typeSuffix.add(argType.toString) typeSuffix.add(argType.toString)
else: else:
typeSuffix.add("unknown") typeSuffix.add("unknown")
inc(typeArgIdx)
let mangledName = baseMethodName & "_" & typeSuffix let mangledName = baseMethodName & "_" & typeSuffix
if not ctx.generatedFuncInsts.hasKey(mangledName): if not ctx.generatedFuncInsts.hasKey(mangledName):
var specDecl = Decl( var specDecl = Decl(
@@ -1225,21 +1245,29 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
for inst in instantiations: for inst in instantiations:
let baseName = inst.name let baseName = inst.name
if ctx.genericFuncs.hasKey(baseName): if ctx.genericFuncs.hasKey(baseName):
let genericDecl = ctx.genericFuncs[baseName]
var typeSuffix = "" var typeSuffix = ""
for i, targ in inst.typeArgs: var nonLifetimeIdx = 0
if i > 0: typeSuffix.add("_") for j, tp in genericDecl.declFuncTypeParams:
if targ.kind == tekNamed: if tp.isLifetime: continue
typeSuffix.add(targ.typeName) if nonLifetimeIdx > 0: typeSuffix.add("_")
if j < inst.typeArgs.len:
let targ = inst.typeArgs[j]
if targ.kind == tekNamed:
typeSuffix.add(targ.typeName)
else:
typeSuffix.add("unknown")
else: else:
typeSuffix.add("unknown") typeSuffix.add("unknown")
inc(nonLifetimeIdx)
let mangledName = baseName & "_" & typeSuffix let mangledName = baseName & "_" & typeSuffix
if not generated.hasKey(mangledName): if not generated.hasKey(mangledName):
# Generate specialized version # Generate specialized version
let genericDecl = ctx.genericFuncs[baseName]
# Build type substitution table # Build type substitution table
var subst = initTable[string, Type]() var subst = initTable[string, Type]()
for j, tp in genericDecl.declFuncTypeParams: for j, tp in genericDecl.declFuncTypeParams:
if tp.isLifetime: continue
if j < inst.typeArgs.len: if j < inst.typeArgs.len:
let targ = inst.typeArgs[j] let targ = inst.typeArgs[j]
if targ.kind == tekNamed: if targ.kind == tekNamed:
+19 -1
View File
@@ -542,7 +542,25 @@ proc nextToken(lex: var Lexer): Token =
return lex.scanChar(startLoc, 3) return lex.scanChar(startLoc, 3)
if c == '\'': if c == '\'':
return lex.scanChar(startLoc, 0) # Check if this is a lifetime (e.g., 'a) or char literal (e.g., 'a')
# Lifetime: ' followed by identifier chars, no closing '
# Char literal: ' followed by one char/escape, then closing '
let afterQuote = lex.peek(1)
if isIdentStart(afterQuote):
# Could be lifetime or char literal like 'x'
# If next char after ident start is NOT ', it's a lifetime
# (for char literals, the char/escape is consumed and then ')
# Simple heuristic: if peek(2) is ', it's a char literal; else lifetime
if lex.peek(2) == '\'':
return lex.scanChar(startLoc, 0)
else:
# Lifetime: consume ' and then identifier chars
discard lex.advance() # '
while not lex.isAtEnd() and isIdentChar(lex.peek()):
discard lex.advance()
return lex.makeToken(tkLifetime, startLoc, startPos)
else:
return lex.scanChar(startLoc, 0)
if isIdentStart(c): if isIdentStart(c):
return lex.scanIdent(startLoc) return lex.scanIdent(startLoc)
+19 -6
View File
@@ -209,20 +209,23 @@ proc parseBaseType(p: var Parser): TypeExpr =
return TypeExpr(kind: tekNamed, loc: loc, typeName: name) return TypeExpr(kind: tekNamed, loc: loc, typeName: name)
of tkOwn: of tkOwn:
discard p.advance() discard p.advance()
return TypeExpr(kind: tekOwn, loc: loc, pointerPointee: p.parseBaseType()) return TypeExpr(kind: tekOwn, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "")
of tkStar: of tkStar:
discard p.advance() discard p.advance()
return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType()) return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "")
of tkAmp: of tkAmp:
discard p.advance() discard p.advance()
var lt = ""
if p.check(tkLifetime):
lt = p.advance().text
if p.check(tkMut): if p.check(tkMut):
discard p.advance() discard p.advance()
return TypeExpr(kind: tekMutRef, loc: loc, pointerPointee: p.parseBaseType()) return TypeExpr(kind: tekMutRef, loc: loc, refLifetime: lt, pointerPointee: p.parseBaseType())
if p.check(tkDyn): if p.check(tkDyn):
discard p.advance() discard p.advance()
let ifaceName = p.expect(tkIdent, "expected interface name after 'dyn'").text let ifaceName = p.expect(tkIdent, "expected interface name after 'dyn'").text
return TypeExpr(kind: tekDynRef, loc: loc, dynInterface: ifaceName) return TypeExpr(kind: tekDynRef, loc: loc, dynInterface: ifaceName)
return TypeExpr(kind: tekRef, loc: loc, pointerPointee: p.parseBaseType()) return TypeExpr(kind: tekRef, loc: loc, refLifetime: lt, pointerPointee: p.parseBaseType())
of tkLParen: of tkLParen:
discard p.advance() discard p.advance()
var elems: seq[TypeExpr] = @[] var elems: seq[TypeExpr] = @[]
@@ -595,6 +598,8 @@ proc parseUnary(p: var Parser): Expr =
case p.peek() case p.peek()
of tkBang, tkMinus, tkTilde, tkStar, tkAmp: of tkBang, tkMinus, tkTilde, tkStar, tkAmp:
let op = p.advance().kind let op = p.advance().kind
if op == tkAmp and p.check(tkMut):
discard p.advance() # mut
let operand = p.parseUnary() let operand = p.parseUnary()
return Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand) return Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand)
of tkPlusPlus, tkMinusMinus: of tkPlusPlus, tkMinusMinus:
@@ -944,7 +949,15 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] =
if p.check(tkLt): if p.check(tkLt):
discard p.advance() discard p.advance()
while not p.check(tkGt) and not p.isAtEnd: while not p.check(tkGt) and not p.isAtEnd:
let name = p.expect(tkIdent, "expected type parameter name").text var name = ""
var isLifetime = false
if p.check(tkIdent):
name = p.advance().text
elif p.check(tkLifetime):
name = p.advance().text
isLifetime = true
else:
discard p.expect(tkIdent, "expected type parameter name")
var bounds: seq[string] = @[] var bounds: seq[string] = @[]
if p.check(tkColon): if p.check(tkColon):
discard p.advance() discard p.advance()
@@ -960,7 +973,7 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] =
else: else:
break break
bounds.add(boundName) bounds.add(boundName)
result.add(TypeParam(name: name, bounds: bounds)) result.add(TypeParam(name: name, bounds: bounds, isLifetime: isLifetime))
if p.check(tkComma): if p.check(tkComma):
discard p.advance() discard p.advance()
discard p.expect(tkGt, "expected '>' to close type parameters") discard p.expect(tkGt, "expected '>' to close type parameters")
+29 -7
View File
@@ -115,7 +115,10 @@ proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool =
return false return false
of tekSlice: of tekSlice:
return typeExprReferencesTypeParam(te.sliceElement, name) return typeExprReferencesTypeParam(te.sliceElement, name)
of tekOwn, tekPointer, tekRef, tekMutRef: of tekOwn, tekPointer:
return typeExprReferencesTypeParam(te.pointerPointee, name)
of tekRef, tekMutRef:
if te.refLifetime == name: return true
return typeExprReferencesTypeParam(te.pointerPointee, name) return typeExprReferencesTypeParam(te.pointerPointee, name)
of tekDynRef: of tekDynRef:
return false return false
@@ -145,17 +148,17 @@ proc typeToTypeExpr(t: Type): TypeExpr =
of tkNamed: TypeExpr(kind: tekNamed, typeName: t.name) of tkNamed: TypeExpr(kind: tekNamed, typeName: t.name)
of tkPointer: of tkPointer:
if t.inner.len > 0: if t.inner.len > 0:
TypeExpr(kind: tekPointer, pointerPointee: typeToTypeExpr(t.inner[0])) TypeExpr(kind: tekPointer, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0]))
else: else:
TypeExpr(kind: tekNamed, typeName: "void") TypeExpr(kind: tekNamed, typeName: "void")
of tkRef: of tkRef:
if t.inner.len > 0: if t.inner.len > 0:
TypeExpr(kind: tekRef, pointerPointee: typeToTypeExpr(t.inner[0])) TypeExpr(kind: tekRef, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0]))
else: else:
TypeExpr(kind: tekNamed, typeName: "void") TypeExpr(kind: tekNamed, typeName: "void")
of tkMutRef: of tkMutRef:
if t.inner.len > 0: if t.inner.len > 0:
TypeExpr(kind: tekMutRef, pointerPointee: typeToTypeExpr(t.inner[0])) TypeExpr(kind: tekMutRef, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0]))
else: else:
TypeExpr(kind: tekNamed, typeName: "void") TypeExpr(kind: tekNamed, typeName: "void")
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void") of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
@@ -168,6 +171,19 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
result = @[] result = @[]
for tp in funcDecl.declFuncTypeParams: for tp in funcDecl.declFuncTypeParams:
let tpName = tp.name let tpName = tp.name
# Lifetime params are inferred from ref lifetime positions
if tp.isLifetime:
var found = false
for i, param in funcDecl.declFuncParams:
if i >= argTypes.len: break
if param.ptype.kind in {tekRef, tekMutRef} and param.ptype.refLifetime == tpName:
found = true
break
if found:
result.add(TypeExpr(kind: tekNamed, typeName: "lifetime"))
continue
# If not found in refs, treat as uninferrable
return @[]
var inferred: Type = nil var inferred: Type = nil
for i, param in funcDecl.declFuncParams: for i, param in funcDecl.declFuncParams:
if i >= argTypes.len: break if i >= argTypes.len: break
@@ -176,9 +192,15 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
if param.ptype.kind in {tekOwn, tekPointer}: if param.ptype.kind in {tekOwn, tekPointer}:
continue continue
if typeExprReferencesTypeParam(param.ptype, tpName): if typeExprReferencesTypeParam(param.ptype, tpName):
var argType = argTypes[i]
# If type param is inside a ref/pointer pointee, unwrap the arg type
if param.ptype.kind in {tekRef, tekMutRef, tekPointer} and
typeExprReferencesTypeParam(param.ptype.pointerPointee, tpName) and
argType.isPointer and argType.inner.len > 0:
argType = argType.inner[0]
if inferred == nil: if inferred == nil:
inferred = argTypes[i] inferred = argType
elif inferred != argTypes[i]: elif inferred != argType:
# Check if one is assignable to the other (wider type wins) # Check if one is assignable to the other (wider type wins)
if argTypes[i].isAssignableTo(inferred): if argTypes[i].isAssignableTo(inferred):
discard # inferred stays the same discard # inferred stays the same
@@ -187,7 +209,7 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
else: else:
sema.emitError(loc, sema.emitError(loc,
&"conflicting types for type parameter '{tpName}': " & &"conflicting types for type parameter '{tpName}': " &
&"{inferred.toString} vs {argTypes[i].toString}") &"{inferred.toString} vs {argType.toString}")
return @[] return @[]
if inferred != nil and not inferred.isUnknown: if inferred != nil and not inferred.isUnknown:
result.add(typeToTypeExpr(inferred)) result.add(typeToTypeExpr(inferred))
+2
View File
@@ -58,6 +58,7 @@ type
tkStaticAssert # static_assert tkStaticAssert # static_assert
tkComptime # comptime tkComptime # comptime
tkDyn # dyn tkDyn # dyn
tkLifetime # 'a (lifetime parameter)
##Punctuation ##Punctuation
tkLParen # ( tkLParen # (
@@ -265,6 +266,7 @@ proc tokenKindName*(kind: TokenKind): string =
of tkStaticAssert: "'static_assert'" of tkStaticAssert: "'static_assert'"
of tkComptime: "'comptime'" of tkComptime: "'comptime'"
of tkDyn: "'dyn'" of tkDyn: "'dyn'"
of tkLifetime: "lifetime"
of tkLParen: "'('" of tkLParen: "'('"
of tkRParen: "')'" of tkRParen: "')'"
of tkLBrace: "'{'" of tkLBrace: "'{'"