diff --git a/Makefile b/Makefile index 6754d47..d82bcf6 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/examples/generics_struct.bux b/examples/generics_struct.bux new file mode 100644 index 0000000..fe500f4 --- /dev/null +++ b/examples/generics_struct.bux @@ -0,0 +1,25 @@ +// Generic structs - Box and Pair +import Std::Io::{PrintLine, PrintInt}; + +struct Box { + value: T, +} + +struct Pair { + first: T, + second: U, +} + +func Main() -> int { + let b: Box = Box { value: 42 }; + PrintLine("Box value:"); + PrintInt(b.value); + PrintLine(""); + + let p: Pair = Pair { first: 10, second: "hello" }; + PrintLine("Pair first:"); + PrintInt(p.first); + PrintLine(""); + + return 0; +} diff --git a/src/hir_lower.nim b/src/hir_lower.nim index 654c794..d5b1c5f 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -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,11 +1009,12 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = hf.name = decl.declImplTypeName & "_" & hf.name funcs.add(hf) of dkStruct: - 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() - fields.add((f.name, fType)) - structs.add((decl.declStructName, fields)) + 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() + fields.add((f.name, fType)) + structs.add((decl.declStructName, fields)) of dkEnum: var variants: seq[HirEnumVariant] = @[] for v in decl.declEnumVariants: @@ -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) diff --git a/src/parser.nim b/src/parser.nim index 24d3de4..7389120 100644 --- a/src/parser.nim +++ b/src/parser.nim @@ -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: diff --git a/src/sema.nim b/src/sema.nim index ea95f23..98c2a77 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -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: diff --git a/src/types.nim b/src/types.nim index 1741029..0280664 100644 --- a/src/types.nim +++ b/src/types.nim @@ -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 diff --git a/tests/hir_test b/tests/hir_test index 1b0c7d6..114a8aa 100755 Binary files a/tests/hir_test and b/tests/hir_test differ diff --git a/tests/parser_test b/tests/parser_test index bdde0f7..a55e33c 100755 Binary files a/tests/parser_test and b/tests/parser_test differ diff --git a/tests/sema_test b/tests/sema_test index 3a9e248..38da2ce 100755 Binary files a/tests/sema_test and b/tests/sema_test differ