Phase 5-7: generic inference, extend Type<T>, String stdlib, Map<K,V>, self-hosting audit, docs update
This commit is contained in:
@@ -162,6 +162,7 @@ type
|
||||
of ekCall:
|
||||
exprCallCallee*: Expr
|
||||
exprCallArgs*: seq[Expr]
|
||||
exprCallInferredTypeArgs*: seq[TypeExpr] ## filled by sema for inferred generic calls
|
||||
of ekGenericCall:
|
||||
exprGenericCallee*: string
|
||||
exprGenericTypeArgs*: seq[TypeExpr]
|
||||
@@ -337,6 +338,7 @@ type
|
||||
declInterfaceMethods*: seq[Decl] ## FuncDecl signatures only
|
||||
of dkImpl:
|
||||
declImplTypeName*: string
|
||||
declImplTypeParams*: seq[string] ## type parameters for generic impl: extend Box<T>
|
||||
declImplInterface*: string ## empty if not for interface
|
||||
declImplMethods*: seq[Decl]
|
||||
of dkModule:
|
||||
|
||||
@@ -508,6 +508,32 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
return hirCall(mangledName, args, typ, loc)
|
||||
|
||||
# Inferred generic function call: Max(10, 20) → Max_int(10, 20)
|
||||
if expr.exprCallInferredTypeArgs.len > 0:
|
||||
var calleeName = ""
|
||||
case expr.exprCallCallee.kind
|
||||
of ekIdent:
|
||||
calleeName = expr.exprCallCallee.exprIdent
|
||||
if ctx.importTable.hasKey(calleeName):
|
||||
calleeName = ctx.importTable[calleeName]
|
||||
of ekPath:
|
||||
calleeName = expr.exprCallCallee.exprPath.join("_")
|
||||
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")
|
||||
let mangledName = calleeName & "_" & typeSuffix
|
||||
var args: seq[HirNode] = @[]
|
||||
for arg in expr.exprCallArgs:
|
||||
args.add(ctx.lowerExpr(arg))
|
||||
return hirCall(mangledName, args, typ, loc)
|
||||
|
||||
# Regular function call
|
||||
var calleeName = ""
|
||||
if expr.exprCallCallee.kind == ekIdent:
|
||||
@@ -1004,6 +1030,12 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
ctx.genericFuncs[decl.declFuncName] = decl
|
||||
if decl.kind == dkStruct and decl.declStructTypeParams.len > 0:
|
||||
ctx.genericStructs[decl.declStructName] = decl
|
||||
if decl.kind == dkImpl and decl.declImplTypeParams.len > 0:
|
||||
let typeName = decl.declImplTypeName
|
||||
for methodDecl in decl.declImplMethods:
|
||||
if methodDecl.kind == dkFunc:
|
||||
let mangledName = typeName & "_" & methodDecl.declFuncName
|
||||
ctx.genericFuncs[mangledName] = methodDecl
|
||||
|
||||
# Second pass: find all generic calls and monomorphize
|
||||
proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] =
|
||||
@@ -1013,6 +1045,14 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
of ekCall:
|
||||
if expr.exprCallCallee.kind == ekGenericCall:
|
||||
result.add((expr.exprCallCallee.exprGenericCallee, expr.exprCallCallee.exprGenericTypeArgs))
|
||||
elif expr.exprCallInferredTypeArgs.len > 0:
|
||||
var calleeName = ""
|
||||
case expr.exprCallCallee.kind
|
||||
of ekIdent: calleeName = expr.exprCallCallee.exprIdent
|
||||
of ekPath: calleeName = expr.exprCallCallee.exprPath.join("::")
|
||||
else: discard
|
||||
if calleeName != "":
|
||||
result.add((calleeName, expr.exprCallInferredTypeArgs))
|
||||
result.add(findGenericCalls(expr.exprCallCallee))
|
||||
for arg in expr.exprCallArgs:
|
||||
result.add(findGenericCalls(arg))
|
||||
@@ -1126,6 +1166,9 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
of dkImpl:
|
||||
for methodDecl in decl.declImplMethods:
|
||||
if methodDecl.kind == dkFunc:
|
||||
# Skip generic methods — they are monomorphized via generateMethodInstance
|
||||
if methodDecl.declFuncTypeParams.len > 0:
|
||||
continue
|
||||
var hf = ctx.lowerFunc(methodDecl)
|
||||
hf.name = decl.declImplTypeName & "_" & hf.name
|
||||
funcs.add(hf)
|
||||
|
||||
@@ -1015,6 +1015,7 @@ proc parseImplDecl(p: var Parser): Decl =
|
||||
let loc = p.currentLoc
|
||||
discard p.expect(tkExtend, "expected 'extend'")
|
||||
let typeName = p.expect(tkIdent, "expected type name").text
|
||||
let typeParams = p.parseTypeParams()
|
||||
var interfaceName = ""
|
||||
if p.check(tkFor):
|
||||
discard p.advance()
|
||||
@@ -1030,6 +1031,7 @@ proc parseImplDecl(p: var Parser): Decl =
|
||||
methods.add(p.parseFuncDecl(false, false, ParsedAttrs()))
|
||||
discard p.expect(tkRBrace, "expected '}' to close impl block")
|
||||
return Decl(kind: dkImpl, loc: loc, declImplTypeName: typeName,
|
||||
declImplTypeParams: typeParams,
|
||||
declImplInterface: interfaceName, declImplMethods: methods)
|
||||
|
||||
proc parseModuleDecl(p: var Parser, isPublic: bool): Decl =
|
||||
|
||||
+135
@@ -47,6 +47,89 @@ proc hasErrors*(res: SemaResult): bool =
|
||||
return true
|
||||
return false
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic type inference helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
proc typeExprReferencesTypeParam(te: TypeExpr, name: string): bool =
|
||||
## Recursively check if a TypeExpr tree references a given type parameter name.
|
||||
if te == nil: return false
|
||||
case te.kind
|
||||
of tekNamed:
|
||||
if te.typeName == name: return true
|
||||
for arg in te.typeArgs:
|
||||
if typeExprReferencesTypeParam(arg, name): return true
|
||||
of tekPath:
|
||||
return false
|
||||
of tekSlice:
|
||||
return typeExprReferencesTypeParam(te.sliceElement, name)
|
||||
of tekPointer:
|
||||
return typeExprReferencesTypeParam(te.pointerPointee, name)
|
||||
of tekTuple:
|
||||
for elem in te.tupleElements:
|
||||
if typeExprReferencesTypeParam(elem, name): return true
|
||||
of tekSelf:
|
||||
return false
|
||||
|
||||
proc typeToTypeExpr(t: Type): TypeExpr =
|
||||
## Convert a resolved Type back to a TypeExpr for storage in inferred type args.
|
||||
case t.kind
|
||||
of tkInt: TypeExpr(kind: tekNamed, typeName: "int")
|
||||
of tkInt8: TypeExpr(kind: tekNamed, typeName: "int8")
|
||||
of tkInt16: TypeExpr(kind: tekNamed, typeName: "int16")
|
||||
of tkInt32: TypeExpr(kind: tekNamed, typeName: "int32")
|
||||
of tkInt64: TypeExpr(kind: tekNamed, typeName: "int64")
|
||||
of tkUInt: TypeExpr(kind: tekNamed, typeName: "uint")
|
||||
of tkUInt8: TypeExpr(kind: tekNamed, typeName: "uint8")
|
||||
of tkUInt16: TypeExpr(kind: tekNamed, typeName: "uint16")
|
||||
of tkUInt32: TypeExpr(kind: tekNamed, typeName: "uint32")
|
||||
of tkUInt64: TypeExpr(kind: tekNamed, typeName: "uint64")
|
||||
of tkFloat32: TypeExpr(kind: tekNamed, typeName: "float32")
|
||||
of tkFloat64: TypeExpr(kind: tekNamed, typeName: "float64")
|
||||
of tkBool: TypeExpr(kind: tekNamed, typeName: "bool")
|
||||
of tkStr: TypeExpr(kind: tekNamed, typeName: "String")
|
||||
of tkNamed: TypeExpr(kind: tekNamed, typeName: t.name)
|
||||
of tkPointer:
|
||||
if t.inner.len > 0:
|
||||
TypeExpr(kind: tekPointer, pointerPointee: typeToTypeExpr(t.inner[0]))
|
||||
else:
|
||||
TypeExpr(kind: tekNamed, typeName: "void")
|
||||
of tkVoid: TypeExpr(kind: tekNamed, typeName: "void")
|
||||
else: TypeExpr(kind: tekNamed, typeName: t.toString)
|
||||
|
||||
proc inferTypeArgs(sema: var Sema, funcDecl: Decl, argTypes: seq[Type],
|
||||
loc: SourceLocation): seq[TypeExpr] =
|
||||
## Infer type arguments from argument types for a generic function call.
|
||||
## Returns empty seq if inference fails for any type parameter.
|
||||
result = @[]
|
||||
for tpName in funcDecl.declFuncTypeParams:
|
||||
var inferred: Type = nil
|
||||
for i, param in funcDecl.declFuncParams:
|
||||
if i >= argTypes.len: break
|
||||
# Skip pointer params — type param is inside the pointee and we cannot
|
||||
# structurally extract it (e.g., *Map<K,V> → arg is *Map<int,String>)
|
||||
if param.ptype.kind == tekPointer:
|
||||
continue
|
||||
if typeExprReferencesTypeParam(param.ptype, tpName):
|
||||
if inferred == nil:
|
||||
inferred = argTypes[i]
|
||||
elif inferred != argTypes[i]:
|
||||
# Check if one is assignable to the other (wider type wins)
|
||||
if argTypes[i].isAssignableTo(inferred):
|
||||
discard # inferred stays the same
|
||||
elif inferred.isAssignableTo(argTypes[i]):
|
||||
inferred = argTypes[i]
|
||||
else:
|
||||
sema.emitError(loc,
|
||||
&"conflicting types for type parameter '{tpName}': " &
|
||||
&"{inferred.toString} vs {argTypes[i].toString}")
|
||||
return @[]
|
||||
if inferred != nil and not inferred.isUnknown:
|
||||
result.add(typeToTypeExpr(inferred))
|
||||
else:
|
||||
# Cannot infer this type parameter from arguments
|
||||
return @[]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Type resolution from AST TypeExpr
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -229,10 +312,19 @@ proc collectGlobals*(sema: var Sema) =
|
||||
of dkImpl:
|
||||
# Register methods for the type
|
||||
let typeName = decl.declImplTypeName
|
||||
let implTypeParams = decl.declImplTypeParams
|
||||
if not sema.methodTable.hasKey(typeName):
|
||||
sema.methodTable[typeName] = @[]
|
||||
# If impl has type params, temporarily add them to type table
|
||||
var addedTypeParams: seq[string] = @[]
|
||||
for tp in implTypeParams:
|
||||
sema.typeTable[tp] = makeTypeParam(tp)
|
||||
addedTypeParams.add(tp)
|
||||
for methodDecl in decl.declImplMethods:
|
||||
if methodDecl.kind == dkFunc:
|
||||
# Propagate impl type params to method for HIR lowering
|
||||
if implTypeParams.len > 0:
|
||||
methodDecl.declFuncTypeParams = implTypeParams
|
||||
var params: seq[Type] = @[]
|
||||
for p in methodDecl.declFuncParams:
|
||||
params.add(sema.resolveType(p.ptype))
|
||||
@@ -252,7 +344,13 @@ proc collectGlobals*(sema: var Sema) =
|
||||
let sym = Symbol(kind: skFunc, name: mangledName, decl: methodDecl,
|
||||
isPublic: true)
|
||||
sym.typ = makeFunc(params, retType)
|
||||
if implTypeParams.len > 0:
|
||||
# Register as generic function for monomorphization
|
||||
sym.decl = methodDecl
|
||||
discard sema.globalScope.define(sym)
|
||||
# Clean up type parameters
|
||||
for tp in addedTypeParams:
|
||||
sema.typeTable.del(tp)
|
||||
else:
|
||||
discard
|
||||
|
||||
@@ -485,6 +583,39 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
for i in 0 ..< argTypes.len:
|
||||
if not argTypes[i].isAssignableTo(expectedParams[i]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
|
||||
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[i].toString}, got {argTypes[i].toString}")
|
||||
|
||||
# Check for inferred generic function call (no explicit type args)
|
||||
var calleeDecl: Decl = nil
|
||||
case expr.exprCallCallee.kind
|
||||
of ekIdent:
|
||||
let sym = scope.lookup(expr.exprCallCallee.exprIdent)
|
||||
if sym != nil: calleeDecl = sym.decl
|
||||
of ekPath:
|
||||
let fullName = expr.exprCallCallee.exprPath.join("::")
|
||||
let sym = scope.lookup(fullName)
|
||||
if sym != nil: calleeDecl = sym.decl
|
||||
else: discard
|
||||
|
||||
if calleeDecl != nil and calleeDecl.kind == dkFunc and
|
||||
calleeDecl.declFuncTypeParams.len > 0 and
|
||||
expr.exprCallInferredTypeArgs.len == 0 and
|
||||
expr.exprCallCallee.kind != ekGenericCall:
|
||||
let inferred = sema.inferTypeArgs(calleeDecl, argTypes, expr.loc)
|
||||
if inferred.len == calleeDecl.declFuncTypeParams.len:
|
||||
expr.exprCallInferredTypeArgs = inferred
|
||||
# Substitute return type using inferred type args
|
||||
if calleeDecl.declFuncReturnType != nil:
|
||||
var added: seq[string] = @[]
|
||||
for i, tp in calleeDecl.declFuncTypeParams:
|
||||
if i < inferred.len:
|
||||
let concrete = sema.resolveType(inferred[i])
|
||||
sema.typeTable[tp] = concrete
|
||||
added.add(tp)
|
||||
let retType = sema.resolveType(calleeDecl.declFuncReturnType)
|
||||
for tp in added:
|
||||
sema.typeTable.del(tp)
|
||||
return retType
|
||||
|
||||
return calleeType.inner[^1]
|
||||
elif calleeType.kind == tkUnknown:
|
||||
return makeUnknown()
|
||||
@@ -708,6 +839,10 @@ proc checkStmt(sema: var Sema, stmt: Stmt, scope: Scope): Type =
|
||||
proc checkFunc(sema: var Sema, decl: Decl) =
|
||||
if decl.declFuncBody == nil:
|
||||
return
|
||||
# Skip body type-checking for generic functions — their bodies contain
|
||||
# type parameters that cannot be fully resolved until monomorphization.
|
||||
if decl.declFuncTypeParams.len > 0:
|
||||
return
|
||||
var funcScope = newScope(sema.globalScope)
|
||||
# Add type parameters to type table for resolution
|
||||
var addedTypeParams: seq[string] = @[]
|
||||
|
||||
Reference in New Issue
Block a user