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:
2026-05-31 10:33:08 +03:00
parent 7ee6a73ea4
commit 9f733aca7d
9 changed files with 136 additions and 15 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ SRC := src/main.nim
OUT := buxc OUT := buxc
BUILD_DIR := build 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 .PHONY: all build dev test clean test-examples
+25
View File
@@ -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
View File
@@ -13,6 +13,9 @@ type
pendingStmts*: seq[HirNode] pendingStmts*: seq[HirNode]
typeSubst*: Table[string, Type] # Type parameter substitution for generics typeSubst*: Table[string, Type] # Type parameter substitution for generics
importTable*: Table[string, string] # Local name → fully qualified name for imports 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 = proc freshName(ctx: var LowerCtx): string =
inc ctx.varCounter inc ctx.varCounter
@@ -118,11 +121,71 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
result.pendingStmts = @[] result.pendingStmts = @[]
result.typeSubst = initTable[string, Type]() result.typeSubst = initTable[string, Type]()
result.importTable = initTable[string, string]() 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 = proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
if te == nil: return makeUnknown() if te == nil: return makeUnknown()
case te.kind case te.kind
of tekNamed: 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 case te.typeName
of "void": return makeVoid() of "void": return makeVoid()
of "bool": return makeBool() of "bool": return makeBool()
@@ -252,7 +315,11 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
return ctx.resolveTypeExpr(f.ftype) return ctx.resolveTypeExpr(f.ftype)
else: return makeUnknown() else: return makeUnknown()
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: of ekSlice:
if expr.exprSliceElements.len > 0: if expr.exprSliceElements.len > 0:
return makeSlice(ctx.resolveExprType(expr.exprSliceElements[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]] = @[] var fields: seq[tuple[name: string, value: HirNode]] = @[]
for f in expr.exprStructInitFields: for f in expr.exprStructInitFields:
fields.add((f.name, ctx.lowerExpr(f.value))) 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) structInitFields: fields, typ: typ, loc: loc)
of ekSlice: of ekSlice:
@@ -801,11 +876,13 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
discard discard
# First pass: collect generic functions # First pass: collect generic functions and generic structs
var genericFuncs = initTable[string, Decl]() var genericFuncs = initTable[string, Decl]()
for decl in module.items: for decl in module.items:
if decl.kind == dkFunc and decl.declFuncTypeParams.len > 0: if decl.kind == dkFunc and decl.declFuncTypeParams.len > 0:
genericFuncs[decl.declFuncName] = decl 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 # Second pass: find all generic calls and monomorphize
proc findGenericCalls(expr: Expr): seq[tuple[name: string, typeArgs: seq[TypeExpr]]] = 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 hf.name = decl.declImplTypeName & "_" & hf.name
funcs.add(hf) funcs.add(hf)
of dkStruct: of dkStruct:
if decl.declStructTypeParams.len == 0: # Skip generic structs — monomorphized separately
var fields: seq[tuple[name: string, typ: Type]] = @[] var fields: seq[tuple[name: string, typ: Type]] = @[]
for f in decl.declStructFields: for f in decl.declStructFields:
let fType = if f.ftype != nil: ctx.resolveTypeExpr(f.ftype) else: makeUnknown() 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)) consts.add((decl.declConstName, typ, value))
else: discard 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) result = HirModule(funcs: funcs, externFuncs: externFuncs, structs: structs, enums: enums, consts: consts)
+12 -3
View File
@@ -503,7 +503,7 @@ proc parsePostfix(p: var Parser): Expr =
discard p.advance() discard p.advance()
left = Expr(kind: ekTry, loc: loc, exprTryOperand: left, exprTryType: nil) left = Expr(kind: ekTry, loc: loc, exprTryOperand: left, exprTryType: nil)
of tkLBrace: of tkLBrace:
if p.structInitAllowed and left.kind in {ekIdent, ekPath}: if p.structInitAllowed and left.kind in {ekIdent, ekPath, ekGenericCall}:
discard p.advance() discard p.advance()
var fields: seq[tuple[name: string, value: Expr]] = @[] var fields: seq[tuple[name: string, value: Expr]] = @[]
while not p.check(tkRBrace) and not p.isAtEnd: while not p.check(tkRBrace) and not p.isAtEnd:
@@ -514,8 +514,17 @@ proc parsePostfix(p: var Parser): Expr =
if p.check(tkComma): if p.check(tkComma):
discard p.advance() discard p.advance()
discard p.expect(tkRBrace, "expected '}'") discard p.expect(tkRBrace, "expected '}'")
let typeName = if left.kind == ekIdent: left.exprIdent else: left.exprPath.join("::") var typeName = ""
left = Expr(kind: ekStructInit, loc: loc, exprStructInitName: typeName, exprStructInitFields: fields) 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: else:
break break
else: else:
+2 -2
View File
@@ -427,7 +427,7 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
for i in 0 ..< argTypes.len: for i in 0 ..< argTypes.len:
let paramIdx = i + 1 # skip self let paramIdx = i + 1 # skip self
if paramIdx < expectedParams.len: 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}") sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[paramIdx].toString}, got {argTypes[i].toString}")
return minfo.retType 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}") sema.emitError(expr.loc, &"expected {expectedParams.len} arguments, got {argTypes.len}")
else: else:
for i in 0 ..< argTypes.len: 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}") sema.emitError(expr.loc, &"argument {i+1}: expected {expectedParams[i].toString}, got {argTypes[i].toString}")
return calleeType.inner[^1] return calleeType.inner[^1]
elif calleeType.kind == tkUnknown: elif calleeType.kind == tkUnknown:
+6 -1
View File
@@ -81,14 +81,19 @@ proc isUnknown*(t: Type): bool = t.kind == tkUnknown
proc isVoid*(t: Type): bool = t.kind == tkVoid proc isVoid*(t: Type): bool = t.kind == tkVoid
proc isBool*(t: Type): bool = t.kind in {tkBool, tkBool8, tkBool16, tkBool32} proc isBool*(t: Type): bool = t.kind in {tkBool, tkBool8, tkBool16, tkBool32}
proc isNumeric*(t: Type): bool = proc isNumeric*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt, t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt, tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt,
tkFloat32, tkFloat64} tkFloat32, tkFloat64}
proc isInteger*(t: Type): bool = proc isInteger*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt, t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt,
tkUInt8, tkUInt16, tkUInt32, tkUInt64, tkUInt} 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 = proc isSigned*(t: Type): bool =
if t.kind in {tkUnknown, tkNamed, tkTypeParam}: return true
t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt} t.kind in {tkInt8, tkInt16, tkInt32, tkInt64, tkInt}
proc isPointer*(t: Type): bool = t.kind == tkPointer proc isPointer*(t: Type): bool = t.kind == tkPointer
proc isSlice*(t: Type): bool = t.kind == tkSlice proc isSlice*(t: Type): bool = t.kind == tkSlice
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.