feat(stdlib): generic Iter_Map/Filter/Fold with func monomorphization

Add Iter_Map<T,U>, Filter, Fold, Any, All, ForEach over fat function
pointers; keep Iter_MapInt and friends as thin aliases.

Compiler fixes required for non-int returns and capturing closures:
- bootstrap: resolve fat-func call return type under mono typeSubst
- bootstrap: type-check args of Foo<T>(...) so closures capture correctly
- selfhost: substitute type params inside tekFunc (BuxFn_U_T → concrete)
- selfhost: emit cstr fat typedefs with #ifndef redefinition guards

Example: examples/iter_generic.bux (int↔String map, fold, closures).
Selfhost-loop remains binary-identical.
This commit is contained in:
2026-07-18 01:07:41 +03:00
parent 26631252c0
commit eac78f28c1
9 changed files with 267 additions and 87 deletions
+7 -1
View File
@@ -548,9 +548,15 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
return makeUnknown()
else: return ctx.resolveExprType(expr.exprUnaryOperand)
of ekCall:
# Local / param fat-func values (after monomorphization typeSubst) — e.g. f: func(T)->U
# Must run before the global-only lookup so generic HOFs get the correct return type.
if expr.exprCallCallee.kind in {ekIdent, ekPath}:
let calType = ctx.resolveExprType(expr.exprCallCallee)
if calType != nil and calType.kind == tkFunc and calType.inner.len > 0:
return calType.inner[^1]
if expr.exprCallCallee.kind == ekIdent:
let sym = ctx.globalScope.lookup(expr.exprCallCallee.exprIdent)
if sym != nil and sym.typ != nil and sym.typ.kind == tkFunc:
if sym != nil and sym.typ != nil and sym.typ.kind == tkFunc and sym.typ.inner.len > 0:
return sym.typ.inner[^1]
if expr.exprCallCallee.kind == ekField:
let recvType = ctx.resolveExprType(expr.exprCallCallee.exprFieldObj)
+23 -20
View File
@@ -1063,31 +1063,34 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
sema.emitError(expr.loc, "internal error: nil callee in call expression")
return makeUnknown()
# Check for generic function call: Max<int>(10, 20)
# Check for generic function call: Max<int>(10, 20) or Iter_Map<int, String>(…)
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:
let retType = sym.typ.inner[^1]
let sym2 = sema.globalScope.lookup(expr.exprCallCallee.exprGenericCallee)
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc and
sym2.decl.declFuncTypeParams.len > 0 and
sym2.decl.declFuncReturnType != nil:
let typeParams = sym2.decl.declFuncTypeParams
var added: seq[string] = @[]
for i, tp in typeParams:
if i < expr.exprCallCallee.exprGenericTypeArgs.len:
let concrete = sema.resolveType(expr.exprCallCallee.exprGenericTypeArgs[i])
sema.typeTable[tp.name] = concrete
added.add(tp.name)
let resolvedRet = sema.resolveType(sym2.decl.declFuncReturnType)
for tp in added:
sema.typeTable.del(tp)
return resolvedRet
return retType
return makeUnknown()
# Still type-check args (closures need capture analysis, etc.)
# Bind type params while checking so `func(T)->U` params resolve.
let sym2 = sema.globalScope.lookup(expr.exprCallCallee.exprGenericCallee)
var added: seq[string] = @[]
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc and
sym2.decl.declFuncTypeParams.len > 0:
let typeParams = sym2.decl.declFuncTypeParams
for i, tp in typeParams:
if i < expr.exprCallCallee.exprGenericTypeArgs.len:
let concrete = sema.resolveType(expr.exprCallCallee.exprGenericTypeArgs[i])
sema.typeTable[tp.name] = concrete
added.add(tp.name)
discard sema.checkExprList(expr.exprCallArgs, scope)
var resolvedRet = makeUnknown()
if sym2 != nil and sym2.decl != nil and sym2.decl.kind == dkFunc and
sym2.decl.declFuncReturnType != nil:
resolvedRet = sema.resolveType(sym2.decl.declFuncReturnType)
elif sym.typ != nil and sym.typ.kind == tkFunc and sym.typ.inner.len > 0:
resolvedRet = sym.typ.inner[^1]
for tp in added:
sema.typeTable.del(tp)
return resolvedRet
# Check for method call: obj.method(args)
if expr.exprCallCallee.kind == ekField: