feat: add generics support with monomorphization
- Add ekGenericCall to AST for generic function calls (Max<int>)
- Parse generic type arguments in parsePostfix
- Support generic calls in sema with type parameter substitution
- Implement monomorphization in hir_lower:
- Collect generic function declarations
- Find all generic call sites
- Generate specialized versions with mangled names (Max_int)
- Substitute type parameters with concrete types
- Add generics.bux example
Example:
func Max<T>(a: T, b: T) -> T {
if a > b { return a; }
else { return b; }
}
let m: int = Max<int>(10, 20); // Generates Max_int
This commit is contained in:
@@ -0,0 +1,26 @@
|
|||||||
|
// Generics - Generic functions and structs
|
||||||
|
extern func Std_Io_PrintLine(s: String);
|
||||||
|
extern func Std_Io_PrintInt(n: int);
|
||||||
|
|
||||||
|
func Max<T>(a: T, b: T) -> T {
|
||||||
|
if a > b {
|
||||||
|
return a;
|
||||||
|
} else {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() -> int {
|
||||||
|
let m1: int = Max<int>(10, 20);
|
||||||
|
let m2: int = Max<int>(5, 3);
|
||||||
|
|
||||||
|
Std_Io_PrintLine("Max(10, 20) = ");
|
||||||
|
Std_Io_PrintInt(m1);
|
||||||
|
Std_Io_PrintLine("");
|
||||||
|
|
||||||
|
Std_Io_PrintLine("Max(5, 3) = ");
|
||||||
|
Std_Io_PrintInt(m2);
|
||||||
|
Std_Io_PrintLine("");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -104,6 +104,7 @@ type
|
|||||||
ekTernary
|
ekTernary
|
||||||
ekRange
|
ekRange
|
||||||
ekCall
|
ekCall
|
||||||
|
ekGenericCall
|
||||||
ekIndex
|
ekIndex
|
||||||
ekField
|
ekField
|
||||||
ekStructInit
|
ekStructInit
|
||||||
@@ -160,6 +161,9 @@ type
|
|||||||
of ekCall:
|
of ekCall:
|
||||||
exprCallCallee*: Expr
|
exprCallCallee*: Expr
|
||||||
exprCallArgs*: seq[Expr]
|
exprCallArgs*: seq[Expr]
|
||||||
|
of ekGenericCall:
|
||||||
|
exprGenericCallee*: string
|
||||||
|
exprGenericTypeArgs*: seq[TypeExpr]
|
||||||
of ekIndex:
|
of ekIndex:
|
||||||
exprIndexObj*: Expr
|
exprIndexObj*: Expr
|
||||||
exprIndexIdx*: Expr
|
exprIndexIdx*: Expr
|
||||||
|
|||||||
+165
-20
@@ -8,6 +8,7 @@ type
|
|||||||
methodTable*: Table[string, seq[MethodInfo]]
|
methodTable*: Table[string, seq[MethodInfo]]
|
||||||
currentFuncRetType*: Type
|
currentFuncRetType*: Type
|
||||||
varCounter*: int
|
varCounter*: int
|
||||||
|
typeSubst*: Table[string, Type] # Type parameter substitution for generics
|
||||||
|
|
||||||
proc freshName(ctx: var LowerCtx): string =
|
proc freshName(ctx: var LowerCtx): string =
|
||||||
inc ctx.varCounter
|
inc ctx.varCounter
|
||||||
@@ -18,6 +19,7 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
|||||||
result.globalScope = sema.globalScope
|
result.globalScope = sema.globalScope
|
||||||
result.methodTable = sema.methodTable
|
result.methodTable = sema.methodTable
|
||||||
result.varCounter = 0
|
result.varCounter = 0
|
||||||
|
result.typeSubst = initTable[string, Type]()
|
||||||
|
|
||||||
# Forward declarations
|
# Forward declarations
|
||||||
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode
|
||||||
@@ -170,6 +172,23 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
|||||||
return HirNode(kind: hCallIndirect, callIndirectCallee: callee,
|
return HirNode(kind: hCallIndirect, callIndirectCallee: callee,
|
||||||
callIndirectArgs: args, typ: typ, loc: loc)
|
callIndirectArgs: args, typ: typ, loc: loc)
|
||||||
|
|
||||||
|
# Generic function call: Max<int>(10, 20) → Max_int(10, 20)
|
||||||
|
if expr.exprCallCallee.kind == ekGenericCall:
|
||||||
|
let baseName = expr.exprCallCallee.exprGenericCallee
|
||||||
|
var typeSuffix = ""
|
||||||
|
for i, targ in expr.exprCallCallee.exprGenericTypeArgs:
|
||||||
|
if i > 0:
|
||||||
|
typeSuffix.add("_")
|
||||||
|
if targ.kind == tekNamed:
|
||||||
|
typeSuffix.add(targ.typeName)
|
||||||
|
else:
|
||||||
|
typeSuffix.add("unknown")
|
||||||
|
let mangledName = baseName & "_" & typeSuffix
|
||||||
|
var args: seq[HirNode] = @[]
|
||||||
|
for arg in expr.exprCallArgs:
|
||||||
|
args.add(ctx.lowerExpr(arg))
|
||||||
|
return hirCall(mangledName, args, typ, loc)
|
||||||
|
|
||||||
# Regular function call
|
# Regular function call
|
||||||
var calleeName = ""
|
var calleeName = ""
|
||||||
if expr.exprCallCallee.kind == ekIdent:
|
if expr.exprCallCallee.kind == ekIdent:
|
||||||
@@ -390,20 +409,27 @@ proc lowerBlock(ctx: var LowerCtx, blk: Block): HirNode =
|
|||||||
typ: makeVoid(), loc: blk.loc)
|
typ: makeVoid(), loc: blk.loc)
|
||||||
|
|
||||||
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
||||||
|
# Set up type substitution for generic functions
|
||||||
|
let oldSubst = ctx.typeSubst
|
||||||
|
|
||||||
var params: seq[tuple[name: string, typ: Type]] = @[]
|
var params: seq[tuple[name: string, typ: Type]] = @[]
|
||||||
for p in decl.declFuncParams:
|
for p in decl.declFuncParams:
|
||||||
var pType = makeUnknown()
|
var pType = makeUnknown()
|
||||||
if p.ptype != nil:
|
if p.ptype != nil:
|
||||||
case p.ptype.kind
|
case p.ptype.kind
|
||||||
of tekNamed:
|
of tekNamed:
|
||||||
case p.ptype.typeName
|
# Check if this is a type parameter
|
||||||
of "int", "int32": pType = makeInt()
|
if ctx.typeSubst.hasKey(p.ptype.typeName):
|
||||||
of "int64": pType = makeInt64()
|
pType = ctx.typeSubst[p.ptype.typeName]
|
||||||
of "float64": pType = makeFloat64()
|
else:
|
||||||
of "float32": pType = makeFloat32()
|
case p.ptype.typeName
|
||||||
of "bool": pType = makeBool()
|
of "int", "int32": pType = makeInt()
|
||||||
of "Point", "Self": pType = makeNamed(p.ptype.typeName)
|
of "int64": pType = makeInt64()
|
||||||
else: pType = makeNamed(p.ptype.typeName)
|
of "float64": pType = makeFloat64()
|
||||||
|
of "float32": pType = makeFloat32()
|
||||||
|
of "bool": pType = makeBool()
|
||||||
|
of "Point", "Self": pType = makeNamed(p.ptype.typeName)
|
||||||
|
else: pType = makeNamed(p.ptype.typeName)
|
||||||
of tekPointer: pType = makePointer(makeUnknown())
|
of tekPointer: pType = makePointer(makeUnknown())
|
||||||
else: discard
|
else: discard
|
||||||
params.add((p.name, pType))
|
params.add((p.name, pType))
|
||||||
@@ -412,13 +438,17 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
|||||||
if decl.declFuncReturnType != nil:
|
if decl.declFuncReturnType != nil:
|
||||||
case decl.declFuncReturnType.kind
|
case decl.declFuncReturnType.kind
|
||||||
of tekNamed:
|
of tekNamed:
|
||||||
case decl.declFuncReturnType.typeName
|
# Check if this is a type parameter
|
||||||
of "int", "int32": retType = makeInt()
|
if ctx.typeSubst.hasKey(decl.declFuncReturnType.typeName):
|
||||||
of "int64": retType = makeInt64()
|
retType = ctx.typeSubst[decl.declFuncReturnType.typeName]
|
||||||
of "float64": retType = makeFloat64()
|
else:
|
||||||
of "float32": retType = makeFloat32()
|
case decl.declFuncReturnType.typeName
|
||||||
of "bool": retType = makeBool()
|
of "int", "int32": retType = makeInt()
|
||||||
else: retType = makeNamed(decl.declFuncReturnType.typeName)
|
of "int64": retType = makeInt64()
|
||||||
|
of "float64": retType = makeFloat64()
|
||||||
|
of "float32": retType = makeFloat32()
|
||||||
|
of "bool": retType = makeBool()
|
||||||
|
else: retType = makeNamed(decl.declFuncReturnType.typeName)
|
||||||
of tekPointer: retType = makePointer(makeUnknown())
|
of tekPointer: retType = makePointer(makeUnknown())
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
@@ -428,6 +458,9 @@ proc lowerFunc*(ctx: var LowerCtx, decl: Decl): HirFunc =
|
|||||||
result = HirFunc(name: decl.declFuncName, params: params, retType: retType,
|
result = HirFunc(name: decl.declFuncName, params: params, retType: retType,
|
||||||
body: body, isPublic: decl.isPublic)
|
body: body, isPublic: decl.isPublic)
|
||||||
|
|
||||||
|
# Restore old substitution
|
||||||
|
ctx.typeSubst = oldSubst
|
||||||
|
|
||||||
proc lowerModule*(module: Module, sema: Sema): HirModule =
|
proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||||
var ctx = initLowerCtx(module, sema)
|
var ctx = initLowerCtx(module, sema)
|
||||||
var funcs: seq[HirFunc] = @[]
|
var funcs: seq[HirFunc] = @[]
|
||||||
@@ -436,14 +469,126 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
|||||||
var enums: seq[tuple[name: string, variants: seq[HirEnumVariant]]] = @[]
|
var enums: seq[tuple[name: string, variants: seq[HirEnumVariant]]] = @[]
|
||||||
var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[]
|
var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[]
|
||||||
|
|
||||||
|
# First pass: collect generic functions
|
||||||
|
var genericFuncs = initTable[string, Decl]()
|
||||||
|
for decl in module.items:
|
||||||
|
if decl.kind == dkFunc and decl.declFuncTypeParams.len > 0:
|
||||||
|
genericFuncs[decl.declFuncName] = decl
|
||||||
|
|
||||||
|
# Second pass: find all generic calls and monomorphize
|
||||||
|
proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] =
|
||||||
|
if expr == nil: return @[]
|
||||||
|
result = @[]
|
||||||
|
case expr.kind
|
||||||
|
of ekCall:
|
||||||
|
if expr.exprCallCallee.kind == ekGenericCall:
|
||||||
|
result.add((expr.exprCallCallee.exprGenericCallee, expr.exprCallCallee.exprGenericTypeArgs))
|
||||||
|
result.add(findGenericCalls(expr.exprCallCallee))
|
||||||
|
for arg in expr.exprCallArgs:
|
||||||
|
result.add(findGenericCalls(arg))
|
||||||
|
of ekGenericCall:
|
||||||
|
result.add((expr.exprGenericCallee, expr.exprGenericTypeArgs))
|
||||||
|
of ekBinary:
|
||||||
|
result.add(findGenericCalls(expr.exprBinaryLeft))
|
||||||
|
result.add(findGenericCalls(expr.exprBinaryRight))
|
||||||
|
of ekUnary:
|
||||||
|
result.add(findGenericCalls(expr.exprUnaryOperand))
|
||||||
|
of ekAssign:
|
||||||
|
result.add(findGenericCalls(expr.exprAssignTarget))
|
||||||
|
result.add(findGenericCalls(expr.exprAssignValue))
|
||||||
|
of ekBlock:
|
||||||
|
if expr.exprBlock != nil:
|
||||||
|
for stmt in expr.exprBlock.stmts:
|
||||||
|
case stmt.kind
|
||||||
|
of skLet: result.add(findGenericCalls(stmt.stmtLetInit))
|
||||||
|
of skReturn: result.add(findGenericCalls(stmt.stmtReturnValue))
|
||||||
|
of skExpr: result.add(findGenericCalls(stmt.stmtExpr))
|
||||||
|
of skIf:
|
||||||
|
result.add(findGenericCalls(stmt.stmtIfCond))
|
||||||
|
of skWhile:
|
||||||
|
result.add(findGenericCalls(stmt.stmtWhileCond))
|
||||||
|
else: discard
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
# Collect all generic instantiations
|
||||||
|
var instantiations: seq[tuple[name: string, typeArgs: seq[TypeExpr]]] = @[]
|
||||||
|
for decl in module.items:
|
||||||
|
if decl.kind == dkFunc and decl.declFuncBody != nil:
|
||||||
|
for stmt in decl.declFuncBody.stmts:
|
||||||
|
case stmt.kind
|
||||||
|
of skLet:
|
||||||
|
instantiations.add(findGenericCalls(stmt.stmtLetInit))
|
||||||
|
of skReturn:
|
||||||
|
instantiations.add(findGenericCalls(stmt.stmtReturnValue))
|
||||||
|
of skExpr:
|
||||||
|
instantiations.add(findGenericCalls(stmt.stmtExpr))
|
||||||
|
of skIf:
|
||||||
|
instantiations.add(findGenericCalls(stmt.stmtIfCond))
|
||||||
|
of skWhile:
|
||||||
|
instantiations.add(findGenericCalls(stmt.stmtWhileCond))
|
||||||
|
else: discard
|
||||||
|
|
||||||
|
# Generate monomorphized functions
|
||||||
|
var generated = initTable[string, bool]()
|
||||||
|
for inst in instantiations:
|
||||||
|
let baseName = inst.name
|
||||||
|
if genericFuncs.hasKey(baseName):
|
||||||
|
var typeSuffix = ""
|
||||||
|
for i, targ in inst.typeArgs:
|
||||||
|
if i > 0: typeSuffix.add("_")
|
||||||
|
if targ.kind == tekNamed:
|
||||||
|
typeSuffix.add(targ.typeName)
|
||||||
|
else:
|
||||||
|
typeSuffix.add("unknown")
|
||||||
|
let mangledName = baseName & "_" & typeSuffix
|
||||||
|
if not generated.hasKey(mangledName):
|
||||||
|
# Generate specialized version
|
||||||
|
let genericDecl = genericFuncs[baseName]
|
||||||
|
|
||||||
|
# Build type substitution table
|
||||||
|
var subst = initTable[string, Type]()
|
||||||
|
for j, tp in genericDecl.declFuncTypeParams:
|
||||||
|
if j < inst.typeArgs.len:
|
||||||
|
let targ = inst.typeArgs[j]
|
||||||
|
if targ.kind == tekNamed:
|
||||||
|
case targ.typeName
|
||||||
|
of "int", "int32": subst[tp] = makeInt()
|
||||||
|
of "int64": subst[tp] = makeInt64()
|
||||||
|
of "float64": subst[tp] = makeFloat64()
|
||||||
|
of "float32": subst[tp] = makeFloat32()
|
||||||
|
of "bool": subst[tp] = makeBool()
|
||||||
|
else: subst[tp] = makeNamed(targ.typeName)
|
||||||
|
|
||||||
|
# Create specialized declaration
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set substitution and lower
|
||||||
|
ctx.typeSubst = subst
|
||||||
|
funcs.add(ctx.lowerFunc(specDecl))
|
||||||
|
ctx.typeSubst = initTable[string, Type]() # Clear substitution
|
||||||
|
generated[mangledName] = true
|
||||||
|
|
||||||
|
# Third pass: lower all non-generic functions
|
||||||
for decl in module.items:
|
for decl in module.items:
|
||||||
case decl.kind
|
case decl.kind
|
||||||
of dkFunc:
|
of dkFunc:
|
||||||
if decl.declFuncBody != nil:
|
if decl.declFuncTypeParams.len == 0: # Skip generic functions
|
||||||
funcs.add(ctx.lowerFunc(decl))
|
if decl.declFuncBody != nil:
|
||||||
else:
|
funcs.add(ctx.lowerFunc(decl))
|
||||||
# Extern function (no body)
|
else:
|
||||||
externFuncs.add(ctx.lowerFunc(decl))
|
# Extern function (no body)
|
||||||
|
externFuncs.add(ctx.lowerFunc(decl))
|
||||||
of dkImpl:
|
of dkImpl:
|
||||||
for methodDecl in decl.declImplMethods:
|
for methodDecl in decl.declImplMethods:
|
||||||
if methodDecl.kind == dkFunc:
|
if methodDecl.kind == dkFunc:
|
||||||
|
|||||||
@@ -441,6 +441,20 @@ proc parsePostfix(p: var Parser): Expr =
|
|||||||
discard p.advance()
|
discard p.advance()
|
||||||
discard p.expect(tkRParen, "expected ')' to close call")
|
discard p.expect(tkRParen, "expected ')' to close call")
|
||||||
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args)
|
left = Expr(kind: ekCall, loc: loc, exprCallCallee: left, exprCallArgs: args)
|
||||||
|
of tkLt:
|
||||||
|
# Generic type arguments: Max<int>(10, 20)
|
||||||
|
if left.kind == ekIdent:
|
||||||
|
discard p.advance()
|
||||||
|
var typeArgs: seq[TypeExpr] = @[]
|
||||||
|
while not p.check(tkGt) and not p.isAtEnd:
|
||||||
|
typeArgs.add(p.parseType())
|
||||||
|
if p.check(tkComma):
|
||||||
|
discard p.advance()
|
||||||
|
discard p.expect(tkGt, "expected '>' to close type arguments")
|
||||||
|
# Store type args in the identifier for later use
|
||||||
|
left = Expr(kind: ekGenericCall, loc: loc, exprGenericCallee: left.exprIdent, exprGenericTypeArgs: typeArgs)
|
||||||
|
else:
|
||||||
|
break
|
||||||
of tkLBracket:
|
of tkLBracket:
|
||||||
# Index expression
|
# Index expression
|
||||||
discard p.advance()
|
discard p.advance()
|
||||||
|
|||||||
@@ -332,6 +332,29 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
sema.emitError(expr.loc, "internal error: nil callee in call expression")
|
sema.emitError(expr.loc, "internal error: nil callee in call expression")
|
||||||
return makeUnknown()
|
return makeUnknown()
|
||||||
|
|
||||||
|
# Check for generic function call: Max<int>(10, 20)
|
||||||
|
if expr.exprCallCallee.kind == ekGenericCall:
|
||||||
|
let sym = scope.lookup(expr.exprCallCallee.exprGenericCallee)
|
||||||
|
if sym == nil:
|
||||||
|
sema.emitError(expr.loc, &"undeclared identifier '{expr.exprCallCallee.exprGenericCallee}'")
|
||||||
|
return makeUnknown()
|
||||||
|
if sym.typ != nil and sym.typ.kind == tkFunc:
|
||||||
|
# Get the return type and substitute type parameters
|
||||||
|
let retType = sym.typ.inner[^1]
|
||||||
|
if retType.kind == tkNamed:
|
||||||
|
# Check if this is a type parameter
|
||||||
|
let sym2 = sema.globalScope.lookup(expr.exprCallCallee.exprGenericCallee)
|
||||||
|
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc:
|
||||||
|
let typeParams = sym2.decl.declFuncTypeParams
|
||||||
|
for i, tp in typeParams:
|
||||||
|
if retType.name == tp and i < expr.exprCallCallee.exprGenericTypeArgs.len:
|
||||||
|
# Substitute with concrete type
|
||||||
|
let concreteType = expr.exprCallCallee.exprGenericTypeArgs[i]
|
||||||
|
if concreteType.kind == tekNamed:
|
||||||
|
return sema.resolveType(concreteType)
|
||||||
|
return retType
|
||||||
|
return makeUnknown()
|
||||||
|
|
||||||
# Check for method call: obj.method(args)
|
# Check for method call: obj.method(args)
|
||||||
if expr.exprCallCallee.kind == ekField:
|
if expr.exprCallCallee.kind == ekField:
|
||||||
let receiver = sema.checkExpr(expr.exprCallCallee.exprFieldObj, scope)
|
let receiver = sema.checkExpr(expr.exprCallCallee.exprFieldObj, scope)
|
||||||
@@ -390,6 +413,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
|||||||
else:
|
else:
|
||||||
sema.emitError(expr.loc, &"cannot call non-function type {calleeType.toString}")
|
sema.emitError(expr.loc, &"cannot call non-function type {calleeType.toString}")
|
||||||
return makeUnknown()
|
return makeUnknown()
|
||||||
|
of ekGenericCall:
|
||||||
|
# Generic function call: Max<int>(10, 20)
|
||||||
|
# For now, just look up the function and return its return type
|
||||||
|
let sym = scope.lookup(expr.exprGenericCallee)
|
||||||
|
if sym == nil:
|
||||||
|
sema.emitError(expr.loc, &"undeclared identifier '{expr.exprGenericCallee}'")
|
||||||
|
return makeUnknown()
|
||||||
|
if sym.typ != nil and sym.typ.kind == tkFunc:
|
||||||
|
return sym.typ.inner[^1]
|
||||||
|
return makeUnknown()
|
||||||
of ekIndex:
|
of ekIndex:
|
||||||
let obj = sema.checkExpr(expr.exprIndexObj, scope)
|
let obj = sema.checkExpr(expr.exprIndexObj, scope)
|
||||||
let idx = sema.checkExpr(expr.exprIndexIdx, scope)
|
let idx = sema.checkExpr(expr.exprIndexIdx, scope)
|
||||||
|
|||||||
Reference in New Issue
Block a user