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:
2026-05-31 00:22:03 +03:00
parent cf074bec89
commit aa3433b5a9
5 changed files with 245 additions and 23 deletions
+34 -1
View File
@@ -331,7 +331,30 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
if expr.exprCallCallee == nil:
sema.emitError(expr.loc, "internal error: nil callee in call expression")
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)
if expr.exprCallCallee.kind == ekField:
let receiver = sema.checkExpr(expr.exprCallCallee.exprFieldObj, scope)
@@ -390,6 +413,16 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
else:
sema.emitError(expr.loc, &"cannot call non-function type {calleeType.toString}")
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:
let obj = sema.checkExpr(expr.exprIndexObj, scope)
let idx = sema.checkExpr(expr.exprIndexIdx, scope)