feat: generic methods on generic structs + method call auto-addressing

- Auto-register func Type_Method<T>(self: *Type<T>) as methods in sema
- Add type param awareness to sema resolveType for generic function bodies
- Add lazy monomorphization for generic struct method calls in HIR lowering
- Track varTypeExprs in LowerCtx for local variable type inference
- Fix ekSelf in both sema and hir_lower to resolve actual parameter type
- Fix lowerFunc to use substituteType for param/return types (handles pointers to generic structs)
- Auto-address value receivers when method expects pointer (e.g., b.Get() where self: *Box<T>)
- Add C forward declarations for all functions to fix ordering issues
- Relax type checks for tkTypeParam in assignments and arguments
- Update generics_struct example with Box_Get, Box_Set, Pair_GetFirst/Second
This commit is contained in:
2026-05-31 11:57:52 +03:00
parent 9f733aca7d
commit af14f392f6
5 changed files with 220 additions and 35 deletions
+139 -32
View File
@@ -16,6 +16,11 @@ type
genericStructs*: Table[string, Decl] # Generic struct declarations
generatedStructInsts*: Table[string, bool] # Track generated struct instantiations
extraStructs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]]
structInstMap*: Table[string, tuple[baseName: string, typeArgs: seq[Type]]] # Mangled name -> base + args
genericFuncs*: Table[string, Decl] # Generic function declarations
generatedFuncInsts*: Table[string, bool] # Track generated function instantiations
extraFuncs*: seq[HirFunc] # Monomorphized generic methods
varTypeExprs*: Table[string, TypeExpr] # Track variable names -> type expr for generic method inference
proc freshName(ctx: var LowerCtx): string =
inc ctx.varCounter
@@ -124,6 +129,11 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
result.genericStructs = initTable[string, Decl]()
result.generatedStructInsts = initTable[string, bool]()
result.extraStructs = @[]
result.structInstMap = initTable[string, tuple[baseName: string, typeArgs: seq[Type]]]()
result.genericFuncs = initTable[string, Decl]()
result.generatedFuncInsts = initTable[string, bool]()
result.extraFuncs = @[]
result.varTypeExprs = initTable[string, TypeExpr]()
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
@@ -143,11 +153,15 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
if not ctx.generatedStructInsts.hasKey(mangledName):
let genericDecl = ctx.genericStructs[te.typeName]
var fields: seq[tuple[name: string, typ: Type]] = @[]
var concreteArgs: seq[Type] = @[]
for f in genericDecl.declStructFields:
let resolvedType = substituteType(ctx, f.ftype, subst)
fields.add((f.name, resolvedType))
for arg in te.typeArgs:
concreteArgs.add(substituteType(ctx, arg, subst))
ctx.extraStructs.add((mangledName, fields))
ctx.generatedStructInsts[mangledName] = true
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
return makeNamed(mangledName)
return ctx.resolveTypeExpr(te)
of tekPointer:
@@ -177,14 +191,18 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
let genericDecl = ctx.genericStructs[te.typeName]
var fields: seq[tuple[name: string, typ: Type]] = @[]
var subst = initTable[string, Type]()
var concreteArgs: seq[Type] = @[]
for j, tp in genericDecl.declStructTypeParams:
if j < te.typeArgs.len:
subst[tp] = ctx.resolveTypeExpr(te.typeArgs[j])
for arg in te.typeArgs:
concreteArgs.add(ctx.resolveTypeExpr(arg))
for f in genericDecl.declStructFields:
let resolvedType = substituteType(ctx, f.ftype, subst)
fields.add((f.name, resolvedType))
ctx.extraStructs.add((mangledName, fields))
ctx.generatedStructInsts[mangledName] = true
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
return makeNamed(mangledName)
case te.typeName
of "void": return makeVoid()
@@ -234,7 +252,10 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
# Check global scope first
let sym = ctx.globalScope.lookup(expr.exprIdent)
if sym != nil and sym.typ != nil: return sym.typ
# Check current function parameters
# Check local variables and parameters tracked in varTypeExprs
if ctx.varTypeExprs.hasKey(expr.exprIdent):
return ctx.resolveTypeExpr(ctx.varTypeExprs[expr.exprIdent])
# Check current function parameters (fallback for untracked params)
if ctx.currentFuncDecl != nil:
var params: seq[Param] = @[]
case ctx.currentFuncDecl.kind
@@ -259,7 +280,17 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
return makePointer(pointeeType)
else: discard
return makeUnknown()
of ekSelf: return makeNamed("self")
of ekSelf:
# Look up self parameter type from current function
if ctx.currentFuncDecl != nil:
var params: seq[Param] = @[]
case ctx.currentFuncDecl.kind
of dkFunc: params = ctx.currentFuncDecl.declFuncParams
of dkExternFunc: params = ctx.currentFuncDecl.declExtFuncParams
else: discard
if params.len > 0 and params[0].name == "self" and params[0].ptype != nil:
return substituteType(ctx, params[0].ptype, ctx.typeSubst)
return makeNamed("self")
of ekBinary:
let left = ctx.resolveExprType(expr.exprBinaryLeft)
case expr.exprBinaryOp
@@ -347,6 +378,32 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
return makeVoid()
else: return makeUnknown()
proc extractGenericStructInfo(ctx: LowerCtx, te: TypeExpr): tuple[baseName: string, typeArgs: seq[TypeExpr]] =
if te == nil: return ("", @[])
var baseTe = te
if baseTe.kind == tekPointer:
baseTe = baseTe.pointerPointee
if baseTe.kind == tekNamed and baseTe.typeArgs.len > 0 and ctx.genericStructs.hasKey(baseTe.typeName):
return (baseTe.typeName, baseTe.typeArgs)
return ("", @[])
proc getReceiverTypeExpr(ctx: LowerCtx, expr: Expr): TypeExpr =
case expr.kind
of ekIdent:
if ctx.varTypeExprs.hasKey(expr.exprIdent):
return ctx.varTypeExprs[expr.exprIdent]
of ekField:
# For chained field access, try to resolve from the outer object
# This is limited but covers common cases
discard
of ekStructInit:
return TypeExpr(kind: tekNamed, loc: expr.loc, typeName: expr.exprStructInitName,
typeArgs: expr.exprStructInitTypeArgs)
else: discard
return nil
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
if expr == nil: return nil
let loc = expr.loc
@@ -384,18 +441,29 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
# Method call desugaring: obj.method(args) → Type_method(obj, args)
if expr.exprCallCallee.kind == ekField:
let methodName = expr.exprCallCallee.exprFieldName
let receiverExpr = expr.exprCallCallee.exprFieldObj
# Try to find the method in methodTable
for typeName, methods in ctx.methodTable:
for minfo in methods:
if minfo.name == methodName:
# Found the method - desugar to Type_method(receiver, args)
let mangledName = typeName & "_" & methodName
var calleeName = typeName & "_" & methodName
# Check if this is a generic method on a generic struct instance
let recvTypeExpr = ctx.getReceiverTypeExpr(receiverExpr)
let (baseName, typeArgs) = ctx.extractGenericStructInfo(recvTypeExpr)
if baseName != "" and baseName == typeName and minfo.decl.declFuncTypeParams.len > 0:
calleeName = ctx.generateMethodInstance(calleeName, typeArgs)
var args: seq[HirNode] = @[]
args.add(ctx.lowerExpr(expr.exprCallCallee.exprFieldObj))
let loweredReceiver = ctx.lowerExpr(receiverExpr)
let receiverType = ctx.resolveExprType(receiverExpr)
# Auto-address if method expects pointer but receiver is value
if minfo.params.len > 0 and minfo.params[0].kind == tkPointer and receiverType.kind != tkPointer:
args.add(hirUnary(tkAmp, loweredReceiver, makePointer(receiverType), loc))
else:
args.add(loweredReceiver)
for arg in expr.exprCallArgs:
args.add(ctx.lowerExpr(arg))
return hirCall(mangledName, args, typ, loc)
return hirCall(calleeName, args, typ, loc)
# Not a method call - treat as field access + call (function pointer)
let callee = ctx.lowerExpr(expr.exprCallCallee)
@@ -632,6 +700,16 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
let alloca = hirAlloca(stmt.stmtLetName, allocaType, loc)
let varNode = hirVar(stmt.stmtLetName, makePointer(allocaType), loc)
let store = hirStore(varNode, initHir, loc)
# Track type expr for generic method inference
if stmt.stmtLetType != nil:
ctx.varTypeExprs[stmt.stmtLetName] = stmt.stmtLetType
elif stmt.stmtLetInit.kind == ekStructInit and stmt.stmtLetInit.exprStructInitTypeArgs.len > 0:
ctx.varTypeExprs[stmt.stmtLetName] = TypeExpr(
kind: tekNamed,
loc: stmt.stmtLetInit.loc,
typeName: stmt.stmtLetInit.exprStructInitName,
typeArgs: stmt.stmtLetInit.exprStructInitTypeArgs
)
var stmts = ctx.pendingStmts
ctx.pendingStmts = @[]
stmts.add(alloca)
@@ -798,36 +876,25 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
for p in funcParams:
var pType = makeUnknown()
if p.ptype != nil:
case p.ptype.kind
of tekNamed:
# Check if this is a type parameter
if ctx.typeSubst.hasKey(p.ptype.typeName):
pType = ctx.typeSubst[p.ptype.typeName]
else:
pType = ctx.resolveTypeExpr(p.ptype)
of tekPointer:
let pointeeType = ctx.resolveTypeExpr(p.ptype.pointerPointee)
pType = makePointer(pointeeType)
else: discard
pType = substituteType(ctx, p.ptype, ctx.typeSubst)
params.add((p.name, pType))
if p.ptype != nil:
ctx.varTypeExprs[p.name] = p.ptype
var retType = makeVoid()
if funcReturnType != nil:
case funcReturnType.kind
of tekNamed:
# Check if this is a type parameter
if ctx.typeSubst.hasKey(funcReturnType.typeName):
retType = ctx.typeSubst[funcReturnType.typeName]
else:
retType = ctx.resolveTypeExpr(funcReturnType)
of tekPointer:
let pointeeType = ctx.resolveTypeExpr(funcReturnType.pointerPointee)
retType = makePointer(pointeeType)
else: discard
retType = substituteType(ctx, funcReturnType, ctx.typeSubst)
let oldFuncDecl = ctx.currentFuncDecl
let oldFuncRetType = ctx.currentFuncRetType
let oldVarTypeExprs = ctx.varTypeExprs
ctx.currentFuncRetType = retType
ctx.currentFuncDecl = decl
ctx.varTypeExprs = initTable[string, TypeExpr]() # Clear local vars for new function
let body = if funcBody != nil: ctx.lowerBlock(funcBody) else: nil
ctx.currentFuncDecl = oldFuncDecl
ctx.currentFuncRetType = oldFuncRetType
ctx.varTypeExprs = oldVarTypeExprs
result = HirFunc(name: funcName, params: params, retType: retType,
body: body, isPublic: decl.isPublic)
@@ -835,6 +902,43 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
# Restore old substitution
ctx.typeSubst = oldSubst
proc generateMethodInstance(ctx: var LowerCtx, baseMethodName: string, typeArgs: seq[TypeExpr]): string =
if not ctx.genericFuncs.hasKey(baseMethodName):
return baseMethodName
let genericDecl = ctx.genericFuncs[baseMethodName]
if genericDecl.declFuncTypeParams.len == 0:
return baseMethodName
var subst = initTable[string, Type]()
var typeSuffix = ""
for i, tp in genericDecl.declFuncTypeParams:
if i > 0: typeSuffix.add("_")
if i < typeArgs.len:
let argType = ctx.resolveTypeExpr(typeArgs[i])
subst[tp] = argType
typeSuffix.add(argType.toString)
else:
typeSuffix.add("unknown")
let mangledName = baseMethodName & "_" & typeSuffix
if not ctx.generatedFuncInsts.hasKey(mangledName):
var specDecl = Decl(
kind: dkFunc,
loc: genericDecl.loc,
isPublic: genericDecl.isPublic,
declFuncAsm: genericDecl.declFuncAsm,
declFuncCallConv: genericDecl.declFuncCallConv,
declFuncName: mangledName,
declFuncTypeParams: @[],
declFuncParams: genericDecl.declFuncParams,
declFuncReturnType: genericDecl.declFuncReturnType,
declFuncBody: genericDecl.declFuncBody
)
let oldSubst = ctx.typeSubst
ctx.typeSubst = subst
ctx.extraFuncs.add(ctx.lowerFunc(specDecl))
ctx.typeSubst = oldSubst
ctx.generatedFuncInsts[mangledName] = true
return mangledName
proc lowerModule*(module: Module, sema: Sema): HirModule =
var ctx = initLowerCtx(module, sema)
var funcs: seq[HirFunc] = @[]
@@ -877,10 +981,9 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
# First pass: collect generic functions and generic structs
var genericFuncs = initTable[string, Decl]()
for decl in module.items:
if decl.kind == dkFunc and decl.declFuncTypeParams.len > 0:
genericFuncs[decl.declFuncName] = decl
ctx.genericFuncs[decl.declFuncName] = decl
if decl.kind == dkStruct and decl.declStructTypeParams.len > 0:
ctx.genericStructs[decl.declStructName] = decl
@@ -943,7 +1046,7 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
var generated = initTable[string, bool]()
for inst in instantiations:
let baseName = inst.name
if genericFuncs.hasKey(baseName):
if ctx.genericFuncs.hasKey(baseName):
var typeSuffix = ""
for i, targ in inst.typeArgs:
if i > 0: typeSuffix.add("_")
@@ -954,7 +1057,7 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
let mangledName = baseName & "_" & typeSuffix
if not generated.hasKey(mangledName):
# Generate specialized version
let genericDecl = genericFuncs[baseName]
let genericDecl = ctx.genericFuncs[baseName]
# Build type substitution table
var subst = initTable[string, Type]()
@@ -1062,4 +1165,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
for s in ctx.extraStructs:
structs.add(s)
# Add monomorphized generic methods
for f in ctx.extraFuncs:
funcs.add(f)
result = HirModule(funcs: funcs, externFuncs: externFuncs, structs: structs, enums: enums, consts: consts)