diff --git a/src/ast.nim b/src/ast.nim index 3dc4a70..3f3d9d0 100644 --- a/src/ast.nim +++ b/src/ast.nim @@ -47,6 +47,7 @@ type sliceSize*: Expr ## nil for unsized slices T[] of tekOwn, tekPointer, tekRef, tekMutRef: pointerPointee*: TypeExpr + refLifetime*: string ## only meaningful for tekRef/tekMutRef of tekDynRef: dynInterface*: string of tekTuple: @@ -300,6 +301,7 @@ type TypeParam* = object name*: string bounds*: seq[string] ## e.g. ["Comparable"] for + isLifetime*: bool ## true for lifetime params like 'a # --------------------------------------------------------------------------- # Declarations diff --git a/src/hir_lower.nim b/src/hir_lower.nim index c1d0869..f902c30 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -564,13 +564,30 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode = else: discard if calleeName != "": var typeSuffix = "" - for i, targ in expr.exprCallInferredTypeArgs: - if i > 0: - typeSuffix.add("_") - if targ.kind == tekNamed: - typeSuffix.add(targ.typeName) - else: - typeSuffix.add("unknown") + var typeArgIdx = 0 + if ctx.genericFuncs.hasKey(calleeName): + let genericDecl = ctx.genericFuncs[calleeName] + for j, tp in genericDecl.declFuncTypeParams: + if tp.isLifetime: continue + if typeArgIdx > 0: + 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 args = ctx.lowerCallArgs(expr.exprCallCallee, expr.exprCallArgs) return hirCall(mangledName, args, typ, loc) @@ -1072,14 +1089,17 @@ proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: return baseMethodName var subst = initTable[string, Type]() var typeSuffix = "" + var typeArgIdx = 0 for i, tp in genericDecl.declFuncTypeParams: - if i > 0: typeSuffix.add("_") - if i < typeArgs.len: - let argType = ctx.resolveTypeExpr(typeArgs[i]) + if tp.isLifetime: continue + if typeArgIdx > 0: typeSuffix.add("_") + if typeArgIdx < typeArgs.len: + let argType = ctx.resolveTypeExpr(typeArgs[typeArgIdx]) subst[tp.name] = argType typeSuffix.add(argType.toString) else: typeSuffix.add("unknown") + inc(typeArgIdx) let mangledName = baseMethodName & "_" & typeSuffix if not ctx.generatedFuncInsts.hasKey(mangledName): var specDecl = Decl( @@ -1225,21 +1245,29 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = for inst in instantiations: let baseName = inst.name if ctx.genericFuncs.hasKey(baseName): + let genericDecl = ctx.genericFuncs[baseName] var typeSuffix = "" - for i, targ in inst.typeArgs: - if i > 0: typeSuffix.add("_") - if targ.kind == tekNamed: - typeSuffix.add(targ.typeName) + var nonLifetimeIdx = 0 + for j, tp in genericDecl.declFuncTypeParams: + if tp.isLifetime: continue + 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: typeSuffix.add("unknown") + inc(nonLifetimeIdx) let mangledName = baseName & "_" & typeSuffix if not generated.hasKey(mangledName): # Generate specialized version - let genericDecl = ctx.genericFuncs[baseName] # Build type substitution table var subst = initTable[string, Type]() for j, tp in genericDecl.declFuncTypeParams: + if tp.isLifetime: continue if j < inst.typeArgs.len: let targ = inst.typeArgs[j] if targ.kind == tekNamed: diff --git a/src/lexer.nim b/src/lexer.nim index 7568924..1c81619 100644 --- a/src/lexer.nim +++ b/src/lexer.nim @@ -542,7 +542,25 @@ proc nextToken(lex: var Lexer): Token = return lex.scanChar(startLoc, 3) 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): return lex.scanIdent(startLoc) diff --git a/src/parser.nim b/src/parser.nim index 2fcf400..dfdb19b 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -209,20 +209,23 @@ proc parseBaseType(p: var Parser): TypeExpr = return TypeExpr(kind: tekNamed, loc: loc, typeName: name) of tkOwn: discard p.advance() - return TypeExpr(kind: tekOwn, loc: loc, pointerPointee: p.parseBaseType()) + return TypeExpr(kind: tekOwn, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "") of tkStar: discard p.advance() - return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType()) + return TypeExpr(kind: tekPointer, loc: loc, pointerPointee: p.parseBaseType(), refLifetime: "") of tkAmp: discard p.advance() + var lt = "" + if p.check(tkLifetime): + lt = p.advance().text if p.check(tkMut): 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): discard p.advance() let ifaceName = p.expect(tkIdent, "expected interface name after 'dyn'").text 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: discard p.advance() var elems: seq[TypeExpr] = @[] @@ -595,6 +598,8 @@ proc parseUnary(p: var Parser): Expr = case p.peek() of tkBang, tkMinus, tkTilde, tkStar, tkAmp: let op = p.advance().kind + if op == tkAmp and p.check(tkMut): + discard p.advance() # mut let operand = p.parseUnary() return Expr(kind: ekUnary, loc: loc, exprUnaryOp: op, exprUnaryOperand: operand) of tkPlusPlus, tkMinusMinus: @@ -944,7 +949,15 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] = if p.check(tkLt): discard p.advance() 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] = @[] if p.check(tkColon): discard p.advance() @@ -960,7 +973,7 @@ proc parseTypeParams(p: var Parser): seq[TypeParam] = else: break bounds.add(boundName) - result.add(TypeParam(name: name, bounds: bounds)) + result.add(TypeParam(name: name, bounds: bounds, isLifetime: isLifetime)) if p.check(tkComma): discard p.advance() discard p.expect(tkGt, "expected '>' to close type parameters") diff --git a/src/sema.nim b/src/sema.nim index 95c5321..fad30bc 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -115,7 +115,10 @@ proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool = return false of tekSlice: 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) of tekDynRef: return false @@ -145,17 +148,17 @@ proc typeToTypeExpr(t: Type): TypeExpr = of tkNamed: TypeExpr(kind: tekNamed, typeName: t.name) of tkPointer: if t.inner.len > 0: - TypeExpr(kind: tekPointer, pointerPointee: typeToTypeExpr(t.inner[0])) + TypeExpr(kind: tekPointer, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0])) else: TypeExpr(kind: tekNamed, typeName: "void") of tkRef: if t.inner.len > 0: - TypeExpr(kind: tekRef, pointerPointee: typeToTypeExpr(t.inner[0])) + TypeExpr(kind: tekRef, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0])) else: TypeExpr(kind: tekNamed, typeName: "void") of tkMutRef: if t.inner.len > 0: - TypeExpr(kind: tekMutRef, pointerPointee: typeToTypeExpr(t.inner[0])) + TypeExpr(kind: tekMutRef, refLifetime: "", pointerPointee: typeToTypeExpr(t.inner[0])) else: 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 = @[] for tp in funcDecl.declFuncTypeParams: 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 for i, param in funcDecl.declFuncParams: 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}: continue 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: - inferred = argTypes[i] - elif inferred != argTypes[i]: + inferred = argType + elif inferred != argType: # Check if one is assignable to the other (wider type wins) if argTypes[i].isAssignableTo(inferred): discard # inferred stays the same @@ -187,7 +209,7 @@ proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type], else: sema.emitError(loc, &"conflicting types for type parameter '{tpName}': " & - &"{inferred.toString} vs {argTypes[i].toString}") + &"{inferred.toString} vs {argType.toString}") return @[] if inferred != nil and not inferred.isUnknown: result.add(typeToTypeExpr(inferred)) diff --git a/src/token.nim b/src/token.nim index e108347..44e4225 100644 --- a/src/token.nim +++ b/src/token.nim @@ -58,6 +58,7 @@ type tkStaticAssert # static_assert tkComptime # comptime tkDyn # dyn + tkLifetime # 'a (lifetime parameter) ##Punctuation tkLParen # ( @@ -265,6 +266,7 @@ proc tokenKindName*(kind: TokenKind): string = of tkStaticAssert: "'static_assert'" of tkComptime: "'comptime'" of tkDyn: "'dyn'" + of tkLifetime: "lifetime" of tkLParen: "'('" of tkRParen: "')'" of tkLBrace: "'{'"