feat: generic struct monomorphization + tkUnknown fix
- Add generic struct instantiation (Box<int>, Pair<T,U>) in HIR lowering - Fix tkUnknown/tkNamed/tkTypeParam ambiguity in sema.nim (qualify with TypeKind) - Add generics_struct example - Update Makefile with new example
This commit is contained in:
@@ -3,7 +3,7 @@ SRC := src/main.nim
|
||||
OUT := buxc
|
||||
BUILD_DIR := build
|
||||
|
||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics pattern_matching strings map result_option try_operator
|
||||
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics pattern_matching strings map result_option try_operator generics_struct
|
||||
|
||||
.PHONY: all build dev test clean test-examples
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Generic structs - Box<T> and Pair<T, U>
|
||||
import Std::Io::{PrintLine, PrintInt};
|
||||
|
||||
struct Box<T> {
|
||||
value: T,
|
||||
}
|
||||
|
||||
struct Pair<T, U> {
|
||||
first: T,
|
||||
second: U,
|
||||
}
|
||||
|
||||
func Main() -> int {
|
||||
let b: Box<int> = Box<int> { value: 42 };
|
||||
PrintLine("Box value:");
|
||||
PrintInt(b.value);
|
||||
PrintLine("");
|
||||
|
||||
let p: Pair<int, String> = Pair<int, String> { first: 10, second: "hello" };
|
||||
PrintLine("Pair first:");
|
||||
PrintInt(p.first);
|
||||
PrintLine("");
|
||||
|
||||
return 0;
|
||||
}
|
||||
+85
-3
@@ -13,6 +13,9 @@ type
|
||||
pendingStmts*: seq[HirNode]
|
||||
typeSubst*: Table[string, Type] # Type parameter substitution for generics
|
||||
importTable*: Table[string, string] # Local name → fully qualified name for imports
|
||||
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]]]]
|
||||
|
||||
proc freshName(ctx: var LowerCtx): string =
|
||||
inc ctx.varCounter
|
||||
@@ -118,11 +121,71 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
|
||||
result.pendingStmts = @[]
|
||||
result.typeSubst = initTable[string, Type]()
|
||||
result.importTable = initTable[string, string]()
|
||||
result.genericStructs = initTable[string, Decl]()
|
||||
result.generatedStructInsts = initTable[string, bool]()
|
||||
result.extraStructs = @[]
|
||||
|
||||
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type
|
||||
|
||||
proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type]): Type =
|
||||
if te == nil: return makeUnknown()
|
||||
case te.kind
|
||||
of tekNamed:
|
||||
if subst.hasKey(te.typeName):
|
||||
return subst[te.typeName]
|
||||
if te.typeArgs.len > 0 and ctx.genericStructs.hasKey(te.typeName):
|
||||
var suffix = ""
|
||||
for i, arg in te.typeArgs:
|
||||
if i > 0: suffix.add("_")
|
||||
let argType = substituteType(ctx, arg, subst)
|
||||
suffix.add(argType.toString)
|
||||
let mangledName = te.typeName & "_" & suffix
|
||||
if not ctx.generatedStructInsts.hasKey(mangledName):
|
||||
let genericDecl = ctx.genericStructs[te.typeName]
|
||||
var fields: seq[tuple[name: string, typ: Type]] = @[]
|
||||
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
|
||||
return makeNamed(mangledName)
|
||||
return ctx.resolveTypeExpr(te)
|
||||
of tekPointer:
|
||||
return makePointer(substituteType(ctx, te.pointerPointee, subst))
|
||||
of tekSlice:
|
||||
return makeSlice(substituteType(ctx, te.sliceElement, subst))
|
||||
of tekTuple:
|
||||
var elems: seq[Type] = @[]
|
||||
for e in te.tupleElements:
|
||||
elems.add(substituteType(ctx, e, subst))
|
||||
return makeTuple(elems)
|
||||
else:
|
||||
return ctx.resolveTypeExpr(te)
|
||||
|
||||
proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
||||
if te == nil: return makeUnknown()
|
||||
case te.kind
|
||||
of tekNamed:
|
||||
if te.typeArgs.len > 0 and ctx.genericStructs.hasKey(te.typeName):
|
||||
var suffix = ""
|
||||
for i, arg in te.typeArgs:
|
||||
if i > 0: suffix.add("_")
|
||||
let argType = ctx.resolveTypeExpr(arg)
|
||||
suffix.add(argType.toString)
|
||||
let mangledName = te.typeName & "_" & suffix
|
||||
if not ctx.generatedStructInsts.hasKey(mangledName):
|
||||
let genericDecl = ctx.genericStructs[te.typeName]
|
||||
var fields: seq[tuple[name: string, typ: Type]] = @[]
|
||||
var subst = initTable[string, Type]()
|
||||
for j, tp in genericDecl.declStructTypeParams:
|
||||
if j < te.typeArgs.len:
|
||||
subst[tp] = ctx.resolveTypeExpr(te.typeArgs[j])
|
||||
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
|
||||
return makeNamed(mangledName)
|
||||
case te.typeName
|
||||
of "void": return makeVoid()
|
||||
of "bool": return makeBool()
|
||||
@@ -252,7 +315,11 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
return ctx.resolveTypeExpr(f.ftype)
|
||||
else: return makeUnknown()
|
||||
return makeUnknown()
|
||||
of ekStructInit: return makeNamed(expr.exprStructInitName)
|
||||
of ekStructInit:
|
||||
if expr.exprStructInitTypeArgs.len > 0:
|
||||
let te = TypeExpr(kind: tekNamed, loc: expr.loc, typeName: expr.exprStructInitName, typeArgs: expr.exprStructInitTypeArgs)
|
||||
return ctx.resolveTypeExpr(te)
|
||||
return makeNamed(expr.exprStructInitName)
|
||||
of ekSlice:
|
||||
if expr.exprSliceElements.len > 0:
|
||||
return makeSlice(ctx.resolveExprType(expr.exprSliceElements[0]))
|
||||
@@ -405,7 +472,15 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
var fields: seq[tuple[name: string, value: HirNode]] = @[]
|
||||
for f in expr.exprStructInitFields:
|
||||
fields.add((f.name, ctx.lowerExpr(f.value)))
|
||||
return HirNode(kind: hStructInit, structInitName: expr.exprStructInitName,
|
||||
var structName = expr.exprStructInitName
|
||||
if expr.exprStructInitTypeArgs.len > 0:
|
||||
var suffix = ""
|
||||
for i, targ in expr.exprStructInitTypeArgs:
|
||||
if i > 0: suffix.add("_")
|
||||
let argType = ctx.resolveTypeExpr(targ)
|
||||
suffix.add(argType.toString)
|
||||
structName = structName & "_" & suffix
|
||||
return HirNode(kind: hStructInit, structInitName: structName,
|
||||
structInitFields: fields, typ: typ, loc: loc)
|
||||
|
||||
of ekSlice:
|
||||
@@ -801,11 +876,13 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
discard
|
||||
|
||||
|
||||
# First pass: collect generic functions
|
||||
# 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
|
||||
if decl.kind == dkStruct and decl.declStructTypeParams.len > 0:
|
||||
ctx.genericStructs[decl.declStructName] = decl
|
||||
|
||||
# Second pass: find all generic calls and monomorphize
|
||||
proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] =
|
||||
@@ -932,6 +1009,7 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
hf.name = decl.declImplTypeName & "_" & hf.name
|
||||
funcs.add(hf)
|
||||
of dkStruct:
|
||||
if decl.declStructTypeParams.len == 0: # Skip generic structs — monomorphized separately
|
||||
var fields: seq[tuple[name: string, typ: Type]] = @[]
|
||||
for f in decl.declStructFields:
|
||||
let fType = if f.ftype != nil: ctx.resolveTypeExpr(f.ftype) else: makeUnknown()
|
||||
@@ -980,4 +1058,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
consts.add((decl.declConstName, typ, value))
|
||||
else: discard
|
||||
|
||||
# Add monomorphized generic structs
|
||||
for s in ctx.extraStructs:
|
||||
structs.add(s)
|
||||
|
||||
result = HirModule(funcs: funcs, externFuncs: externFuncs, structs: structs, enums: enums, consts: consts)
|
||||
|
||||
+12
-3
@@ -503,7 +503,7 @@ proc parsePostfix(p: var Parser): Expr =
|
||||
discard p.advance()
|
||||
left = Expr(kind: ekTry, loc: loc, exprTryOperand: left, exprTryType: nil)
|
||||
of tkLBrace:
|
||||
if p.structInitAllowed and left.kind in {ekIdent, ekPath}:
|
||||
if p.structInitAllowed and left.kind in {ekIdent, ekPath, ekGenericCall}:
|
||||
discard p.advance()
|
||||
var fields: seq[tuple[name: string, value: Expr]] = @[]
|
||||
while not p.check(tkRBrace) and not p.isAtEnd:
|
||||
@@ -514,8 +514,17 @@ proc parsePostfix(p: var Parser): Expr =
|
||||
if p.check(tkComma):
|
||||
discard p.advance()
|
||||
discard p.expect(tkRBrace, "expected '}'")
|
||||
let typeName = if left.kind == ekIdent: left.exprIdent else: left.exprPath.join("::")
|
||||
left = Expr(kind: ekStructInit, loc: loc, exprStructInitName: typeName, exprStructInitFields: fields)
|
||||
var typeName = ""
|
||||
var typeArgs: seq[TypeExpr] = @[]
|
||||
if left.kind == ekIdent:
|
||||
typeName = left.exprIdent
|
||||
elif left.kind == ekPath:
|
||||
typeName = left.exprPath.join("::")
|
||||
elif left.kind == ekGenericCall:
|
||||
typeName = left.exprGenericCallee
|
||||
typeArgs = left.exprGenericTypeArgs
|
||||
left = Expr(kind: ekStructInit, loc: loc, exprStructInitName: typeName,
|
||||
exprStructInitTypeArgs: typeArgs, exprStructInitFields: fields)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
|
||||
+2
-2
@@ -427,7 +427,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
for i in 0 ..< argTypes.len:
|
||||
let paramIdx = i + 1 # skip self
|
||||
if paramIdx < expectedParams.len:
|
||||
if not argTypes[i].isAssignableTo(expectedParams[paramIdx]):
|
||||
if not argTypes[i].isAssignableTo(expectedParams[paramIdx]) and not (argTypes[i].kind in {TypeKind.tkUnknown, TypeKind.tkNamed, TypeKind.tkTypeParam}):
|
||||
sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[paramIdx].toString}, got {argTypes[i].toString}")
|
||||
return minfo.retType
|
||||
|
||||
@@ -451,7 +451,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
|
||||
sema.emitError(expr.loc, &"expected {expectedParams.len} arguments, got {argTypes.len}")
|
||||
else:
|
||||
for i in 0 ..< argTypes.len:
|
||||
if not argTypes[i].isAssignableTo(expectedParams[i]):
|
||||
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}")
|
||||
return calleeType.inner[^1]
|
||||
elif calleeType.kind == tkUnknown:
|
||||
|
||||
+6
-1
@@ -81,14 +81,19 @@ proc isUnknown*(t: Type): bool = t.kind == tkUnknown
|
||||
proc isVoid*(t: Type): bool = t.kind == tkVoid
|
||||
proc isBool*(t: Type): bool = t.kind in {tkBool, tkBool8, tkBool16, tkBool32}
|
||||
proc isNumeric*(t: Type): bool =
|
||||
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
|
||||
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
|
||||
tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt,
|
||||
tkFloat32, tkFloat64}
|
||||
proc isInteger*(t: Type): bool =
|
||||
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
|
||||
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
|
||||
tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt}
|
||||
proc isFloat*(t: Type): bool = t.kind in {tkFloat32, tkFloat64}
|
||||
proc isFloat*(t: Type): bool =
|
||||
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
|
||||
t.kind in {tkFloat32, tkFloat64}
|
||||
proc isSigned*(t: Type): bool =
|
||||
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
|
||||
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt}
|
||||
proc isPointer*(t: Type): bool = t.kind == tkPointer
|
||||
proc isSlice*(t: Type): bool = t.kind == tkSlice
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user