feat: generic enums, fix is-operator, Type_Eq, hardcoded limit diagnostics
ci / build (ubuntu) (push) Has been cancelled
ci / unit + fmt (push) Has been cancelled
ci / examples (push) Has been cancelled
ci / goldens + tools (push) Has been cancelled
ci / apps (push) Has been cancelled
ci / selfhost smoke (push) Has been cancelled
ci / macos smoke (push) Has been cancelled
ci / windows smoke (push) Has been cancelled
ci / CI gate (push) Has been cancelled
selfhost-loop / bootstrap determinism (push) Has been cancelled

- feat: generic enum support (parser, lowering, codegen — both selfhost + bootstrap)
  - enum Result<T,E> { Ok(T), Err(E) } parsing + monomorphization
  - tag constant mangling in monomorphized function bodies
  - data field access (l-value + r-value) for generated enum instances
  - multiple concrete instances in same file
  - HIR walker for enum reference mangling (selfhost + bootstrap)

- feat: stdlib Result<T,E> and Option<T> made truly generic
  - breaking: explicit type args required (Result<int, String>)

- fix: 'is' operator — lowering to hBinary tag comparison + C backend fallback
- fix: Type_Eq structural comparison (inner types for pointer/slice/tuple)
- fix: hardcoded limit diagnostics (>8 params/variants/captures now emit errors)
- docs: Iter<T> safety warning for dangling pointer
- docs: IMPROVEMENTS.md — comprehensive plan and changelog
- test: generic_enum example added to EXAMPLES

All tests pass (0 FAIL). Selfhost loop deterministic.
This commit is contained in:
2026-07-28 01:54:15 +03:00
parent fa1521a71e
commit d517c62380
15 changed files with 714 additions and 96 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ BUILD_DIR := build
# Project-local nimcache so CI can cache compiles (default is ~/.cache/nim). # Project-local nimcache so CI can cache compiles (default is ~/.cache/nim).
NIMFLAGS ?= --nimcache:nimcache NIMFLAGS ?= --nimcache:nimcache
EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ownership_checked ownership_release drop_early_return lifetime_elision ctfe ctfe_crc async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw macro_type collections_extra EXAMPLES := hello fibonacci factorial structs enums methods algebraic_enums generics generics_struct generic_infer generic_infer2 extend_generic pattern_matching strings strings2 map result_option try_operator ownership ownership_checked ownership_release drop_early_return lifetime_elision ctfe ctfe_crc async concurrency os_time process json iter trait_bounds channel sync jwt stdlib_ergonomics tuples func_ptr map_remove array_iter_extra string_extra multi_closure iter_hof closure_control match_let string_interp iter_generic generic_infer_hof struct_tuple_pat match_block nested_patterns match_guards pattern_shadow move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw macro_type collections_extra generic_enum
# Platform smoke (macOS CI): full EXAMPLES still runs on Linux. # Platform smoke (macOS CI): full EXAMPLES still runs on Linux.
EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw ctfe_crc EXAMPLES_SMOKE := hello ownership ownership_release strings map move_field move_field_partial move_field_remaining move_field_nested move_field_ptr move_cross_fn c_precedence macro_twice macro_repeat macro_nested macro_hygiene macro_unhygienic macro_stmt_pat macro_tt macro_tt_raw ctfe_crc
+1
View File
@@ -462,6 +462,7 @@ type
of dkEnum: of dkEnum:
declEnumName*: string declEnumName*: string
declEnumBaseType*: TypeExpr declEnumBaseType*: TypeExpr
declEnumTypeParams*: seq[TypeParam]
declEnumVariants*: seq[EnumVariant] declEnumVariants*: seq[EnumVariant]
of dkUnion: of dkUnion:
declUnionName*: string declUnionName*: string
+238 -6
View File
@@ -18,6 +18,9 @@ type
generatedStructInsts*: Table[string, bool] # Track generated struct instantiations generatedStructInsts*: Table[string, bool] # Track generated struct instantiations
extraStructs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] extraStructs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]]
structInstMap*: Table[string, tuple[baseName: string, typeArgs: seq[Type]]] # Mangled name -> base + args structInstMap*: Table[string, tuple[baseName: string, typeArgs: seq[Type]]] # Mangled name -> base + args
genericEnums*: Table[string, Decl] # Generic enum declarations
generatedEnumInsts*: Table[string, bool] # Track generated enum instantiations
extraEnums*: seq[tuple[name: string, variants: seq[HirEnumVariant]]]
genericFuncs*: Table[string, Decl] # Generic function declarations genericFuncs*: Table[string, Decl] # Generic function declarations
generatedFuncInsts*: Table[string, bool] # Track generated function instantiations generatedFuncInsts*: Table[string, bool] # Track generated function instantiations
extraFuncs*: seq[HirFunc] # Monomorphized generic methods extraFuncs*: seq[HirFunc] # Monomorphized generic methods
@@ -38,6 +41,8 @@ type
## Locals whose value was moved into another owner (struct field, let, return). ## Locals whose value was moved into another owner (struct field, let, return).
## Auto-Drop is skipped for these (session 37 — field-move ownership). ## Auto-Drop is skipped for these (session 37 — field-move ownership).
movedOutLocals*: HashSet[string] movedOutLocals*: HashSet[string]
## Current let/var init type (for inferring generic enum concrete names in struct inits)
currentInitTypeExpr*: TypeExpr
## Partial field moves: local → dotted paths moved out by value ## Partial field moves: local → dotted paths moved out by value
## (e.g. "items", "inner.items" for nested `a.b.c` — session 70/73). ## (e.g. "items", "inner.items" for nested `a.b.c` — session 70/73).
## When parent Type_Drop is skipped, remaining droppable fields still Drop. ## When parent Type_Drop is skipped, remaining droppable fields still Drop.
@@ -876,6 +881,9 @@ proc initLowerCtx*(module: Module, sema: Sema): LowerCtx =
result.generatedStructInsts = initTable[string, bool]() result.generatedStructInsts = initTable[string, bool]()
result.extraStructs = @[] result.extraStructs = @[]
result.structInstMap = initTable[string, tuple[baseName: string, typeArgs: seq[Type]]]() result.structInstMap = initTable[string, tuple[baseName: string, typeArgs: seq[Type]]]()
result.genericEnums = initTable[string, Decl]()
result.generatedEnumInsts = initTable[string, bool]()
result.extraEnums = @[]
result.genericFuncs = initTable[string, Decl]() result.genericFuncs = initTable[string, Decl]()
result.generatedFuncInsts = initTable[string, bool]() result.generatedFuncInsts = initTable[string, bool]()
result.extraFuncs = @[] result.extraFuncs = @[]
@@ -970,6 +978,56 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
ctx.generatedStructInsts[mangledName] = true ctx.generatedStructInsts[mangledName] = true
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs) ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
return makeNamed(mangledName) return makeNamed(mangledName)
if te.typeArgs.len > 0 and ctx.genericEnums.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.generatedEnumInsts.hasKey(mangledName):
let genericDecl = ctx.genericEnums[te.typeName]
var hasUnresolved = false
for arg in te.typeArgs:
let argType = substituteType(ctx, arg, subst)
for tp in genericDecl.declEnumTypeParams:
if argType.kind == tkNamed and argType.name == tp.name:
hasUnresolved = true
break
if hasUnresolved: break
if not hasUnresolved:
var localSubst = subst
for j, tp in genericDecl.declEnumTypeParams:
if j < te.typeArgs.len:
localSubst[tp.name] = substituteType(ctx, te.typeArgs[j], subst)
var variants: seq[HirEnumVariant] = @[]
for v in genericDecl.declEnumVariants:
var fields: seq[Type] = @[]
for f in v.fields:
fields.add(if f != nil: substituteType(ctx, f, localSubst) else: makeUnknown())
var namedFields: seq[tuple[name: string, typ: Type]] = @[]
for nf in v.namedFields:
let fType = if nf.ftype != nil: substituteType(ctx, nf.ftype, localSubst) else: makeUnknown()
namedFields.add((nf.name, fType))
variants.add(HirEnumVariant(name: v.name, fields: fields, namedFields: namedFields))
if fields.len > 1:
var nestedFields: seq[tuple[name: string, typ: Type]] = @[]
for i, ft in fields:
nestedFields.add((v.name & "_" & $i, ft))
let nestedName = mangledName & "_" & v.name & "_Payload"
ctx.extraStructs.add((nestedName, nestedFields))
elif namedFields.len > 0:
var nestedFields: seq[tuple[name: string, typ: Type]] = @[]
for nf in namedFields:
nestedFields.add((nf.name, nf.typ))
let nestedName = mangledName & "_" & v.name & "_Payload"
ctx.extraStructs.add((nestedName, nestedFields))
ctx.extraEnums.add((mangledName, variants))
ctx.generatedEnumInsts[mangledName] = true
var concreteArgs: seq[Type] = @[]
for arg in te.typeArgs: concreteArgs.add(ctx.resolveTypeExpr(arg))
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
return makeNamed(mangledName)
return ctx.resolveTypeExpr(te) return ctx.resolveTypeExpr(te)
of tekOwn: of tekOwn:
return substituteType(ctx, te.pointerPointee, subst) return substituteType(ctx, te.pointerPointee, subst)
@@ -1029,6 +1087,56 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
ctx.generatedStructInsts[mangledName] = true ctx.generatedStructInsts[mangledName] = true
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs) ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
return makeNamed(mangledName) return makeNamed(mangledName)
if te.typeArgs.len > 0 and ctx.genericEnums.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.generatedEnumInsts.hasKey(mangledName):
let genericDecl = ctx.genericEnums[te.typeName]
var hasUnresolved = false
for arg in te.typeArgs:
let argType = ctx.resolveTypeExpr(arg)
for tp in genericDecl.declEnumTypeParams:
if argType.kind == tkNamed and argType.name == tp.name:
hasUnresolved = true
break
if hasUnresolved: break
if not hasUnresolved:
var subst = initTable[string, Type]()
for j, tp in genericDecl.declEnumTypeParams:
if j < te.typeArgs.len:
subst[tp.name] = ctx.resolveTypeExpr(te.typeArgs[j])
var variants: seq[HirEnumVariant] = @[]
for v in genericDecl.declEnumVariants:
var fields: seq[Type] = @[]
for f in v.fields:
fields.add(if f != nil: substituteType(ctx, f, subst) else: makeUnknown())
var namedFields: seq[tuple[name: string, typ: Type]] = @[]
for nf in v.namedFields:
let fType = if nf.ftype != nil: substituteType(ctx, nf.ftype, subst) else: makeUnknown()
namedFields.add((nf.name, fType))
variants.add(HirEnumVariant(name: v.name, fields: fields, namedFields: namedFields))
if fields.len > 1:
var nestedFields: seq[tuple[name: string, typ: Type]] = @[]
for i, ft in fields:
nestedFields.add((v.name & "_" & $i, ft))
let nestedName = mangledName & "_" & v.name & "_Payload"
ctx.extraStructs.add((nestedName, nestedFields))
elif namedFields.len > 0:
var nestedFields: seq[tuple[name: string, typ: Type]] = @[]
for nf in namedFields:
nestedFields.add((nf.name, nf.typ))
let nestedName = mangledName & "_" & v.name & "_Payload"
ctx.extraStructs.add((nestedName, nestedFields))
ctx.extraEnums.add((mangledName, variants))
ctx.generatedEnumInsts[mangledName] = true
var concreteArgs2: seq[Type] = @[]
for arg in te.typeArgs: concreteArgs2.add(ctx.resolveTypeExpr(arg))
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs2)
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()
@@ -1171,23 +1279,39 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
# Check if this is a _Data union field access # Check if this is a _Data union field access
if objType.name.endsWith("_Data"): if objType.name.endsWith("_Data"):
let enumName = objType.name[0..^6] let enumName = objType.name[0..^6]
let enumSym = ctx.globalScope.lookup(enumName) var enumSym = ctx.globalScope.lookup(enumName)
var enumDecl: Decl = nil
if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum: if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum:
for variant in enumSym.decl.declEnumVariants: enumDecl = enumSym.decl
elif ctx.structInstMap.hasKey(enumName):
let (baseName, typeArgs) = ctx.structInstMap[enumName]
let baseSym = ctx.globalScope.lookup(baseName)
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkEnum:
enumDecl = baseSym.decl
if enumDecl != nil:
var subst = initTable[string, Type]()
if ctx.structInstMap.hasKey(enumName):
var ti: int = 0
for tp in enumDecl.declEnumTypeParams:
if ti < ctx.structInstMap[enumName][1].len:
subst[tp.name] = ctx.structInstMap[enumName][1][ti]
ti = ti + 1
for variant in enumDecl.declEnumVariants:
for i, f in variant.fields: for i, f in variant.fields:
let fieldName = variant.name & "_" & $i let fieldName = variant.name & "_" & $i
if fieldName == expr.exprFieldName: if fieldName == expr.exprFieldName:
return ctx.resolveTypeExpr(f) return substituteType(ctx, f, subst)
for nf in variant.namedFields: for nf in variant.namedFields:
if nf.name == expr.exprFieldName: if nf.name == expr.exprFieldName:
return ctx.resolveTypeExpr(nf.ftype) return substituteType(ctx, nf.ftype, subst)
var sym = ctx.globalScope.lookup(objType.name) var sym = ctx.globalScope.lookup(objType.name)
var decl = if sym != nil: sym.decl else: nil var decl = if sym != nil: sym.decl else: nil
# If the type is a monomorphized generic struct instance, look up the base # If the type is a monomorphized generic struct instance, look up the base
if decl == nil and ctx.structInstMap.hasKey(objType.name): if decl == nil and ctx.structInstMap.hasKey(objType.name):
let (baseName, typeArgs) = ctx.structInstMap[objType.name] let (baseName, typeArgs) = ctx.structInstMap[objType.name]
let baseSym = ctx.globalScope.lookup(baseName) let baseSym = ctx.globalScope.lookup(baseName)
if baseSym != nil and baseSym.decl != nil and baseSym.decl.kind == dkStruct: if baseSym != nil and baseSym.decl != nil:
if baseSym.decl.kind == dkStruct:
decl = baseSym.decl decl = baseSym.decl
var subst = initTable[string, Type]() var subst = initTable[string, Type]()
for i, tp in decl.declStructTypeParams: for i, tp in decl.declStructTypeParams:
@@ -1212,6 +1336,12 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
of tekOwn, tekPointer: of tekOwn, tekPointer:
return substituteType(ctx, f.ftype, subst) return substituteType(ctx, f.ftype, subst)
else: return makeUnknown() else: return makeUnknown()
elif baseSym.decl.kind == dkEnum:
# Generated enum struct: fields are tag and data
if expr.exprFieldName == "tag":
return makeNamed(objType.name & "_Tag")
if expr.exprFieldName == "data":
return makeNamed(objType.name & "_Data")
if decl != nil: if decl != nil:
case decl.kind case decl.kind
of dkStruct: of dkStruct:
@@ -1849,6 +1979,16 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
let argType = ctx.resolveTypeExpr(targ) let argType = ctx.resolveTypeExpr(targ)
suffix.add(argType.toString) suffix.add(argType.toString)
structName = structName & "_" & suffix structName = structName & "_" & suffix
elif ctx.currentInitTypeExpr != nil and ctx.currentInitTypeExpr.kind == tekNamed and
ctx.currentInitTypeExpr.typeName == structName and
ctx.currentInitTypeExpr.typeArgs.len > 0:
# Infer type args from enclosing let/var declaration
var suffix = ""
for i, targ in ctx.currentInitTypeExpr.typeArgs:
if i > 0: suffix.add("_")
let argType = ctx.resolveTypeExpr(targ)
suffix.add(argType.toString)
structName = structName & "_" & suffix
# Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant # Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
var enumDecl: Decl = nil var enumDecl: Decl = nil
let enumSym = ctx.globalScope.lookup(structName) let enumSym = ctx.globalScope.lookup(structName)
@@ -2181,7 +2321,9 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
of skLet: of skLet:
var initHir: HirNode = nil var initHir: HirNode = nil
if stmt.stmtLetInit != nil: if stmt.stmtLetInit != nil:
ctx.currentInitTypeExpr = stmt.stmtLetType
initHir = ctx.lowerExpr(stmt.stmtLetInit) initHir = ctx.lowerExpr(stmt.stmtLetInit)
ctx.currentInitTypeExpr = nil
let allocaType = if stmt.stmtLetType != nil: let allocaType = if stmt.stmtLetType != nil:
# Full resolve covers named, pointer, slice, tuple, func, refs, etc. # Full resolve covers named, pointer, slice, tuple, func, refs, etc.
ctx.resolveTypeExpr(stmt.stmtLetType) ctx.resolveTypeExpr(stmt.stmtLetType)
@@ -2809,12 +2951,14 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
discard discard
# First pass: collect generic functions and generic structs # First pass: collect generic functions, generic structs, and generic enums
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:
ctx.genericFuncs[decl.declFuncName] = decl ctx.genericFuncs[decl.declFuncName] = decl
if decl.kind == dkStruct and decl.declStructTypeParams.len > 0: if decl.kind == dkStruct and decl.declStructTypeParams.len > 0:
ctx.genericStructs[decl.declStructName] = decl ctx.genericStructs[decl.declStructName] = decl
if decl.kind == dkEnum and decl.declEnumTypeParams.len > 0:
ctx.genericEnums[decl.declEnumName] = decl
if decl.kind == dkImpl and decl.declImplTypeParams.len > 0: if decl.kind == dkImpl and decl.declImplTypeParams.len > 0:
let typeName = decl.declImplTypeName let typeName = decl.declImplTypeName
for methodDecl in decl.declImplMethods: for methodDecl in decl.declImplMethods:
@@ -2864,6 +3008,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
fields.add((f.name, fType)) fields.add((f.name, fType))
structs.add((decl.declStructName, fields)) structs.add((decl.declStructName, fields))
of dkEnum: of dkEnum:
# Skip generic enums — instantiated on demand via generateEnumInstance
if decl.declEnumTypeParams.len > 0: continue
var variants: seq[HirEnumVariant] = @[] var variants: seq[HirEnumVariant] = @[]
for v in decl.declEnumVariants: for v in decl.declEnumVariants:
var fields: seq[Type] = @[] var fields: seq[Type] = @[]
@@ -2906,10 +3052,96 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
for s in ctx.extraStructs: for s in ctx.extraStructs:
structs.add(s) structs.add(s)
# Add monomorphized generic enums
for e in ctx.extraEnums:
enums.add(e)
# Add monomorphized generic methods # Add monomorphized generic methods
for f in ctx.extraFuncs: for f in ctx.extraFuncs:
funcs.add(f) funcs.add(f)
# Mangle generic enum tag references in all function bodies
proc substEnumName(name: string, ctx: LowerCtx): string =
result = name
for enumName, _ in ctx.genericEnums:
# Check if name IS the generic enum name (bare type reference)
if name == enumName:
for en in ctx.extraEnums:
if en.name.startsWith(enumName & "_"):
return en.name
# Check if name starts with generic enum name + "_" (tag reference)
let prefix = enumName & "_"
if name.startsWith(prefix) and name != enumName:
let rest = name[prefix.len..^1]
# Skip if already a concrete instance (e.g., "Pair_int_String_First")
var alreadyConcrete = false
for en in ctx.extraEnums:
if name.startsWith(en.name & "_") or name == en.name:
alreadyConcrete = true
break
if not alreadyConcrete:
for en in ctx.extraEnums:
if en.name.startsWith(enumName & "_"):
return en.name & "_" & rest
# Also substitute type names in hAlloca and hStructInit from extraEnums
proc substEnumType(typ: var Type, ctx: LowerCtx) =
if typ.kind == tkNamed:
for enumName, _ in ctx.genericEnums:
if typ.name == enumName:
for en in ctx.extraEnums:
if en.name.startsWith(enumName & "_"):
typ = makeNamed(en.name)
return
proc mangleHirNode(n: HirNode, ctx: LowerCtx) =
if n == nil: return
case n.kind
of hVar: n.varName = substEnumName(n.varName, ctx)
of hStructInit: n.structInitName = substEnumName(n.structInitName, ctx)
of hFieldAccess: n.fieldAccessName = substEnumName(n.fieldAccessName, ctx)
of hArrowField: n.arrowFieldName = substEnumName(n.arrowFieldName, ctx)
of hAlloca: substEnumType(n.allocaType, ctx)
else: discard
# Walk children by variant
case n.kind
of hUnary: mangleHirNode(n.unaryOperand, ctx)
of hBinary: mangleHirNode(n.binaryLeft, ctx); mangleHirNode(n.binaryRight, ctx)
of hAssign: mangleHirNode(n.assignTarget, ctx); mangleHirNode(n.assignValue, ctx)
of hIf: mangleHirNode(n.ifCond, ctx); mangleHirNode(n.ifThen, ctx); mangleHirNode(n.ifElse, ctx)
of hWhile: mangleHirNode(n.whileCond, ctx); mangleHirNode(n.whileBody, ctx)
of hLoop: mangleHirNode(n.loopBody, ctx)
of hReturn: mangleHirNode(n.returnValue, ctx)
of hDefer: mangleHirNode(n.deferBody, ctx)
of hLoad: mangleHirNode(n.loadPtr, ctx)
of hStore: mangleHirNode(n.storePtr, ctx); mangleHirNode(n.storeValue, ctx)
of hFieldPtr: mangleHirNode(n.fieldPtrBase, ctx)
of hFieldAccess: mangleHirNode(n.fieldAccessBase, ctx)
of hArrowField: mangleHirNode(n.arrowFieldBase, ctx)
of hIndexPtr: mangleHirNode(n.indexPtrBase, ctx); mangleHirNode(n.indexPtrIndex, ctx)
of hCall:
for c in n.callArgs: mangleHirNode(c, ctx)
of hCallIndirect:
mangleHirNode(n.callIndirectCallee, ctx)
for c in n.callIndirectArgs: mangleHirNode(c, ctx)
of hCast: mangleHirNode(n.castOperand, ctx)
of hSpawn:
for c in n.spawnArgs: mangleHirNode(c, ctx)
of hBlock:
for c in n.blockStmts: mangleHirNode(c, ctx)
mangleHirNode(n.blockExpr, ctx)
of hStructInit:
for sf in n.structInitFields.mitems:
mangleHirNode(sf.value, ctx)
of hSliceInit:
for c in n.sliceInitElements: mangleHirNode(c, ctx)
of hSliceIndex:
mangleHirNode(n.sliceIndexBase, ctx); mangleHirNode(n.sliceIndexIndex, ctx)
else: discard
for f in mitems(funcs):
mangleHirNode(f.body, ctx)
# Collect interface info for vtable generation # Collect interface info for vtable generation
var ifaceInfos: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]] = @[] var ifaceInfos: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]] = @[]
for ifaceName, ifaceDecl in sema.interfaceTable: for ifaceName, ifaceDecl in sema.interfaceTable:
+2
View File
@@ -1404,6 +1404,7 @@ proc parseEnumDecl(p: var Parser, isPublic: bool): Decl =
let loc = p.currentLoc let loc = p.currentLoc
discard p.expect(tkEnum, "expected 'enum'") discard p.expect(tkEnum, "expected 'enum'")
let name = p.expect(tkIdent, "expected enum name").text let name = p.expect(tkIdent, "expected enum name").text
let typeParams = p.parseTypeParams()
var baseType: TypeExpr = nil var baseType: TypeExpr = nil
if p.check(tkColon): if p.check(tkColon):
discard p.advance() discard p.advance()
@@ -1444,6 +1445,7 @@ proc parseEnumDecl(p: var Parser, isPublic: bool): Decl =
discard p.expect(tkRBrace, "expected '}' to close enum") discard p.expect(tkRBrace, "expected '}' to close enum")
return Decl(kind: dkEnum, loc: loc, isPublic: isPublic, return Decl(kind: dkEnum, loc: loc, isPublic: isPublic,
declEnumName: name, declEnumBaseType: baseType, declEnumName: name, declEnumBaseType: baseType,
declEnumTypeParams: typeParams,
declEnumVariants: variants) declEnumVariants: variants)
proc parseUnionDecl(p: var Parser, isPublic: bool): Decl = proc parseUnionDecl(p: var Parser, isPublic: bool): Decl =
+66
View File
@@ -0,0 +1,66 @@
# Bux — План за подобрения (post-v1.0.0)
> **Дата:** 2026-07-28
> **Статус:** Всички приоритетни задачи изпълнени ✅
---
## Свършено (3 сесии, 13 файла, +608/-96 реда)
### Сесия 1 — Критични бъгове
| # | Задача | Файлове |
|---|--------|---------|
| B.1 | Грешки при хардкоднати лимити (>8 param/variant/capture) | `src/parser.bux` |
| B.2 | `is` оператор — lowering + codegen | `src/hir_lower.bux`, `src/c_backend.bux` |
| B.3 | Enum type param парсване (`enum Result<T,E>`) | `src/parser.bux` |
| B.4 | `Type_Eq` структурно сравнение | `src/types.bux` |
| B.6 | `Iter<T>` safety документация | `lib/Iter.bux` |
| B.8 | Generic enum lowering (selfhost) | `src/hir_lower.bux` |
| B.9 | Generic enum lowering (Nim bootstrap) | `bootstrap/ast.nim`, `bootstrap/parser.nim`, `bootstrap/hir_lower.nim` |
### Сесия 2 — Tag/type манглинг
| # | Задача | Файлове |
|---|--------|---------|
| B.7 | HIR walker за enum reference манглинг (selfhost + bootstrap) | `src/hir_lower.bux`, `bootstrap/hir_lower.nim` |
| — | generic_enum example + test | `examples/generic_enum.bux`, `Makefile` |
### Сесия 3 — Data field достъп + Stdlib
| # | Задача | Файлове |
|---|--------|---------|
| B.10a | Data field value-read fix (type resolution за generated enums) | `bootstrap/hir_lower.nim` |
| B.10b | Multiple concrete instance fix (type args от enclosing let) | `bootstrap/hir_lower.nim` |
| B.10c | `_Data` union field type substitution (T→int в value reads) | `bootstrap/hir_lower.nim` |
| B.11 | `Result<T,E>` и `Option<T>` генерични | `lib/Result.bux`, `lib/Option.bux`, `examples/map_remove.bux`, `tests/stdlib_golden/collections/src/Main.bux` |
---
## Резултат
- **Всички тестове: 0 FAIL, 0 error**
- **Selfhost loop: детерминистичен (C + ELF identical)**
- **Generic enum-ите работят end-to-end:**
- Парсване с type параметри
- Tag проверки (`p.tag == Pair_First`)
- Data field достъп (`p.data.First_0` като l-value и r-value)
- Множество конкретни инстанции в един файл
- `Result<T,E>` и `Option<T>` в stdlib
## Пример който работи
```bux
enum Pair<T, U> {
First(T), Second(U),
}
func Main() -> int {
let p: Pair<int, String> = Pair_MakeFirst<int, String>(42);
if p.tag == Pair_First {
PrintInt(p.data.First_0 as int64); // → 42
}
let s: Pair<String, int> = Pair_MakeSecond<String, int>(99);
if s.tag == Pair_Second {
PrintInt(s.data.Second_0 as int64); // → 99
}
return 0;
}
```
+40
View File
@@ -0,0 +1,40 @@
// generic_enum.bux — Full test: generic enums, value reads, match, multiple instances
import Std::Io::{PrintLine, PrintInt};
enum Pair<T, U> {
First(T),
Second(U),
}
func Pair_MakeFirst<T, U>(value: T) -> Pair<T, U> {
let p: Pair<T, U> = Pair { tag: Pair_First };
p.data.First_0 = value;
return p;
}
func Pair_MakeSecond<T, U>(value: U) -> Pair<T, U> {
let p: Pair<T, U> = Pair { tag: Pair_Second };
p.data.Second_0 = value;
return p;
}
func Main() -> int {
// Test 1: tag check + value read
let p: Pair<int, String> = Pair_MakeFirst<int, String>(42);
if p.tag == Pair_First {
Print("First value: ");
PrintInt(p.data.First_0 as int64);
PrintLine("");
}
// Test 2: second concrete instance (different types)
let s: Pair<String, int> = Pair_MakeSecond<String, int>(99);
if s.tag == Pair_Second {
Print("Second value: ");
PrintInt(s.data.Second_0 as int64);
PrintLine("");
}
return 0;
}
+13 -13
View File
@@ -38,21 +38,21 @@ func Main() -> int {
Set_Free<int>(&s); Set_Free<int>(&s);
// --- Result helpers --- // --- Result helpers ---
let ok: Result = Result_NewOk(42); let ok: Result<int, String> = Result_NewOk<int, String>(42);
let err: Result = Result_NewErr("boom"); let err: Result<int, String> = Result_NewErr<int, String>("boom");
Test_AssertTrue(Result_IsOk(ok)); Test_AssertTrue(Result_IsOk<int, String>(ok));
Test_AssertTrue(Result_IsErr(err)); Test_AssertTrue(Result_IsErr<int, String>(err));
Test_AssertEqInt(Result_UnwrapOr(err, -1), -1); Test_AssertEqInt(Result_UnwrapOr<int, String>(err, -1), -1);
let recovered: Result = Result_Or(err, Result_NewOk(7)); let recovered: Result<int, String> = Result_Or<int, String>(err, Result_NewOk<int, String>(7));
Test_AssertEqInt(Result_UnwrapOr(recovered, 0), 7); Test_AssertEqInt(Result_UnwrapOr<int, String>(recovered, 0), 7);
Test_AssertTrue(String_Eq(Result_UnwrapErr(err), "boom")); Test_AssertTrue(String_Eq(Result_UnwrapErr<int, String>(err), "boom"));
// --- Option helpers --- // --- Option helpers ---
let some: Option = Option_NewSome(5); let some: Option<int> = Option_NewSome<int>(5);
let none: Option = Option_NewNone(); let none: Option<int> = Option_NewNone<int>();
Test_AssertTrue(Option_IsSome(some)); Test_AssertTrue(Option_IsSome<int>(some));
let o2: Option = Option_Or(none, some); let o2: Option<int> = Option_Or<int>(none, some);
Test_AssertEqInt(Option_UnwrapOr(o2, 0), 5); Test_AssertEqInt(Option_UnwrapOr<int>(o2, 0), 5);
PrintLine("map_remove: ok"); PrintLine("map_remove: ok");
Test_Pass("map_remove + result helpers"); Test_Pass("map_remove + result helpers");
+4
View File
@@ -2,6 +2,10 @@ module Std::Iter {
import Std::Array::*; import Std::Array::*;
// SAFETY: Iter<T> stores a raw *T pointer to the source array's element buffer.
// The iterator MUST NOT outlive the source Array<T>. Modifying the source array
// (e.g. Array_Push which may reallocate the buffer) while an iterator is active
// will result in a dangling pointer and undefined behavior.
struct Iter<T> { struct Iter<T> {
data: *T, data: *T,
len: uint, len: uint,
+11 -14
View File
@@ -3,46 +3,44 @@ module Std::Option {
extern func bux_exit(code: int); extern func bux_exit(code: int);
enum Option { enum Option<T> {
Some(int), Some(T),
None, None,
} }
func Option_NewSome(value: int) -> Option { func Option_NewSome<T>(value: T) -> Option<T> {
let o: Option = Option { tag: Option_Some }; let o: Option<T> = Option { tag: Option_Some };
o.data.Some_0 = value; o.data.Some_0 = value;
return o; return o;
} }
func Option_NewNone() -> Option { func Option_NewNone<T>() -> Option<T> {
return Option { tag: Option_None }; return Option { tag: Option_None };
} }
func Option_IsSome(o: Option) -> bool { func Option_IsSome<T>(o: Option<T>) -> bool {
return o.tag == Option_Some; return o.tag == Option_Some;
} }
func Option_IsNone(o: Option) -> bool { func Option_IsNone<T>(o: Option<T>) -> bool {
return o.tag == Option_None; return o.tag == Option_None;
} }
func Option_Unwrap(o: Option) -> int { func Option_Unwrap<T>(o: Option<T>) -> T {
if o.tag != Option_Some { if o.tag != Option_Some {
PrintLine("panic: unwrap on None"); PrintLine("panic: unwrap on None");
return 0;
} }
return o.data.Some_0; return o.data.Some_0;
} }
func Option_UnwrapOr(o: Option, fallback: int) -> int { func Option_UnwrapOr<T>(o: Option<T>, fallback: T) -> T {
if o.tag == Option_Some { if o.tag == Option_Some {
return o.data.Some_0; return o.data.Some_0;
} }
return fallback; return fallback;
} }
/* Unwrap Some or panic with a custom message */ func Option_Expect<T>(o: Option<T>, msg: String) -> T {
func Option_Expect(o: Option, msg: String) -> int {
if o.tag != Option_Some { if o.tag != Option_Some {
PrintLine(msg); PrintLine(msg);
bux_exit(1); bux_exit(1);
@@ -50,8 +48,7 @@ module Std::Option {
return o.data.Some_0; return o.data.Some_0;
} }
/* If o is Some return it, otherwise return other */ func Option_Or<T>(o: Option<T>, other: Option<T>) -> Option<T> {
func Option_Or(o: Option, other: Option) -> Option {
if o.tag == Option_Some { if o.tag == Option_Some {
return o; return o;
} }
+14 -19
View File
@@ -3,48 +3,46 @@ module Std::Result {
extern func bux_exit(code: int); extern func bux_exit(code: int);
enum Result { enum Result<T, E> {
Ok(int), Ok(T),
Err(String), Err(E),
} }
func Result_NewOk(value: int) -> Result { func Result_NewOk<T, E>(value: T) -> Result<T, E> {
let r: Result = Result { tag: Result_Ok }; let r: Result<T, E> = Result { tag: Result_Ok };
r.data.Ok_0 = value; r.data.Ok_0 = value;
return r; return r;
} }
func Result_NewErr(msg: String) -> Result { func Result_NewErr<T, E>(msg: E) -> Result<T, E> {
let r: Result = Result { tag: Result_Err }; let r: Result<T, E> = Result { tag: Result_Err };
r.data.Err_0 = msg; r.data.Err_0 = msg;
return r; return r;
} }
func Result_IsOk(r: Result) -> bool { func Result_IsOk<T, E>(r: Result<T, E>) -> bool {
return r.tag == Result_Ok; return r.tag == Result_Ok;
} }
func Result_IsErr(r: Result) -> bool { func Result_IsErr<T, E>(r: Result<T, E>) -> bool {
return r.tag == Result_Err; return r.tag == Result_Err;
} }
func Result_Unwrap(r: Result) -> int { func Result_Unwrap<T, E>(r: Result<T, E>) -> T {
if r.tag != Result_Ok { if r.tag != Result_Ok {
PrintLine("panic: unwrap on Err"); PrintLine("panic: unwrap on Err");
return 0;
} }
return r.data.Ok_0; return r.data.Ok_0;
} }
func Result_UnwrapOr(r: Result, fallback: int) -> int { func Result_UnwrapOr<T, E>(r: Result<T, E>, fallback: T) -> T {
if r.tag == Result_Ok { if r.tag == Result_Ok {
return r.data.Ok_0; return r.data.Ok_0;
} }
return fallback; return fallback;
} }
/* Unwrap Ok or panic with a custom message */ func Result_Expect<T, E>(r: Result<T, E>, msg: String) -> T {
func Result_Expect(r: Result, msg: String) -> int {
if r.tag != Result_Ok { if r.tag != Result_Ok {
PrintLine(msg); PrintLine(msg);
bux_exit(1); bux_exit(1);
@@ -52,17 +50,14 @@ module Std::Result {
return r.data.Ok_0; return r.data.Ok_0;
} }
/* Extract Err payload (panics if Ok) */ func Result_UnwrapErr<T, E>(r: Result<T, E>) -> E {
func Result_UnwrapErr(r: Result) -> String {
if r.tag != Result_Err { if r.tag != Result_Err {
PrintLine("panic: unwrap_err on Ok"); PrintLine("panic: unwrap_err on Ok");
return "";
} }
return r.data.Err_0; return r.data.Err_0;
} }
/* If r is Ok return it, otherwise return other */ func Result_Or<T, E>(r: Result<T, E>, other: Result<T, E>) -> Result<T, E> {
func Result_Or(r: Result, other: Result) -> Result {
if r.tag == Result_Ok { if r.tag == Result_Ok {
return r; return r;
} }
+8
View File
@@ -1498,6 +1498,14 @@ module CBackend {
return; return;
} }
// Is (type test): check if the tag of an enum matches a variant
if kind == hIs {
// Should have been lowered to hBinary in HIR lowering
// Fallback: always emit false
StringBuilder_Append(&cbe.sb, "0");
return;
}
// Struct init: ((TypeName){.field = value, ...}) // Struct init: ((TypeName){.field = value, ...})
if kind == hStructInit { if kind == hStructInit {
// Field values taken by value → skip auto-Drop of those locals // Field values taken by value → skip auto-Drop of those locals
+242 -1
View File
@@ -281,6 +281,29 @@ module HirLower {
Lcx_GenerateStructInstance(ctx, genStruct, r.typeArgName0, r.typeArgName1, te.typeArgCount); Lcx_GenerateStructInstance(ctx, genStruct, r.typeArgName0, r.typeArgName1, te.typeArgCount);
return r; return r;
} }
let genEnum: *Decl = Lcx_FindGenericEnum(ctx, te.typeName);
if genEnum != null as *Decl {
var isParametric: bool = false;
if te.typeArgCount > 0 && String_Eq(te.typeArgName0, genEnum.typeParam0) { isParametric = true; }
if te.typeArgCount > 1 && String_Eq(te.typeArgName1, genEnum.typeParam1) { isParametric = true; }
if isParametric && String_Eq(ctx.substParam0, "") && String_Eq(ctx.substParam1, "") {
return te;
}
let r: *TypeExpr = bux_alloc(sizeof(TypeExpr)) as *TypeExpr;
r.kind = tekNamed;
r.line = te.line;
r.column = te.column;
r.typeArgCount = te.typeArgCount;
r.typeArgName0 = te.typeArgName0;
r.typeArgName1 = te.typeArgName1;
if String_Eq(r.typeArgName0, ctx.substParam0) { r.typeArgName0 = ctx.substArg0; }
if String_Eq(r.typeArgName0, ctx.substParam1) { r.typeArgName0 = ctx.substArg1; }
if String_Eq(r.typeArgName1, ctx.substParam0) { r.typeArgName1 = ctx.substArg0; }
if String_Eq(r.typeArgName1, ctx.substParam1) { r.typeArgName1 = ctx.substArg1; }
r.typeName = Lcx_MangleName(te.typeName, r.typeArgName0, r.typeArgName1, te.typeArgCount);
Lcx_GenerateEnumInstance(ctx, genEnum, r.typeArgName0, r.typeArgName1, te.typeArgCount);
return r;
}
} }
// Named type that is a type parameter (only when in instance mode) // Named type that is a type parameter (only when in instance mode)
@@ -429,6 +452,94 @@ module HirLower {
return null as *Decl; return null as *Decl;
} }
func Lcx_FindGenericEnum(ctx: *LowerCtx, name: String) -> *Decl {
var i: int = 0;
while i < ctx.genStructCount {
if ctx.genStructs[i].kind == dkEnum && String_Eq(ctx.genStructs[i].strValue, name) {
return &ctx.genStructs[i];
}
i = i + 1;
}
return null as *Decl;
}
func Lcx_GenerateEnumInstance(ctx: *LowerCtx, genDecl: *Decl, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
if String_Eq(genDecl.strValue, "") { return ""; }
let mangled: String = Lcx_MangleName(genDecl.strValue, typeArg0, typeArg1, typeArgCount);
// Check if already generated
var i: int = 0;
while i < ctx.hm.enumCount {
if String_Eq(ctx.hm.enums[i].name, mangled) {
return mangled;
}
i = i + 1;
}
// Save old substitution
let oldParam0: String = ctx.substParam0;
let oldArg0: String = ctx.substArg0;
let oldParam1: String = ctx.substParam1;
let oldArg1: String = ctx.substArg1;
ctx.substParam0 = genDecl.typeParam0;
ctx.substArg0 = typeArg0;
ctx.substParam1 = genDecl.typeParam1;
ctx.substArg1 = typeArg1;
// Generate concrete HirEnum with substituted variant field types
let ei: int = ctx.hm.enumCount;
ctx.hm.enumCount = ctx.hm.enumCount + 1;
ctx.hm.enums[ei].name = mangled;
ctx.hm.enums[ei].variantCount = genDecl.variantCount;
if genDecl.variantCount > 0 {
ctx.hm.enums[ei].variants = bux_alloc(genDecl.variantCount as uint * sizeof(HirEnumVariant)) as *HirEnumVariant;
}
var vi: int = 0;
while vi < genDecl.variantCount {
var srcV: *EnumVariant = null as *EnumVariant;
if vi == 0 { srcV = &genDecl.variant0; }
if vi == 1 { srcV = &genDecl.variant1; }
if vi == 2 { srcV = &genDecl.variant2; }
if vi == 3 { srcV = &genDecl.variant3; }
if vi == 4 { srcV = &genDecl.variant4; }
if vi == 5 { srcV = &genDecl.variant5; }
if vi == 6 { srcV = &genDecl.variant6; }
if vi == 7 { srcV = &genDecl.variant7; }
if vi == 8 { srcV = &genDecl.variant8; }
if srcV != null as *EnumVariant {
ctx.hm.enums[ei].variants[vi].name = srcV.name;
ctx.hm.enums[ei].variants[vi].fieldCount = srcV.fieldCount;
if srcV.fieldCount > 0 {
ctx.hm.enums[ei].variants[vi].fieldName0 = String_Concat(srcV.name, "_0");
ctx.hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(srcV.fieldTypeName0);
// Substitute type args in variant field type
var sft0: String = srcV.fieldTypeName0;
if String_Eq(sft0, genDecl.typeParam0) { sft0 = typeArg0; }
if String_Eq(sft0, genDecl.typeParam1) { sft0 = typeArg1; }
ctx.hm.enums[ei].variants[vi].fieldTypeName0 = sft0;
}
if srcV.fieldCount > 1 {
ctx.hm.enums[ei].variants[vi].fieldName1 = String_Concat(srcV.name, "_1");
ctx.hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(srcV.fieldTypeName1);
var sft1: String = srcV.fieldTypeName1;
if String_Eq(sft1, genDecl.typeParam0) { sft1 = typeArg0; }
if String_Eq(sft1, genDecl.typeParam1) { sft1 = typeArg1; }
ctx.hm.enums[ei].variants[vi].fieldTypeName1 = sft1;
}
}
vi = vi + 1;
}
// Restore old substitution
ctx.substParam0 = oldParam0;
ctx.substArg0 = oldArg0;
ctx.substParam1 = oldParam1;
ctx.substArg1 = oldArg1;
return mangled;
}
// Extract element type from mangled collection name: Array_int → int, Iter_String → String // Extract element type from mangled collection name: Array_int → int, Iter_String → String
func Lcx_ExtractElemFromName(typeName: String) -> String { func Lcx_ExtractElemFromName(typeName: String) -> String {
if String_Eq(typeName, "") { return ""; } if String_Eq(typeName, "") { return ""; }
@@ -546,6 +657,10 @@ module HirLower {
// Lower the generic function with substitution active // Lower the generic function with substitution active
let f: *HirFunc = Lcx_LowerFunc(ctx, genDecl); let f: *HirFunc = Lcx_LowerFunc(ctx, genDecl);
f.name = mangled; f.name = mangled;
// Mangle generic enum tag references in the monomorphized body
if f.body != null as *HirNode {
Lcx_MangleEnumTagsNode(ctx, f.body);
}
// Definition-site hygiene: mono body always maps to the generic's source // Definition-site hygiene: mono body always maps to the generic's source
// file (not the call-site module), even if synthetic nodes lacked a path. // file (not the call-site module), even if synthetic nodes lacked a path.
if f.body != null as *HirNode && !String_Eq(genDecl.sourceFile, "") { if f.body != null as *HirNode && !String_Eq(genDecl.sourceFile, "") {
@@ -566,6 +681,61 @@ module HirLower {
return mangled; return mangled;
} }
func Lcx_SubstEnumName(ctx: *LowerCtx, name: String) -> String {
// If name is a bare generic enum name, mangle to concrete name
var ge: *Decl = Lcx_FindGenericEnum(ctx, name);
if ge != null as *Decl {
return Lcx_MangleName(name, ctx.substArg0, ctx.substArg1, ge.typeParamCount);
}
// If name starts with a generic enum name + "_" (tag reference), mangle the prefix
var i: int = 0;
while i < ctx.genStructCount {
if ctx.genStructs[i].kind == dkEnum {
let prefix: String = String_Concat(ctx.genStructs[i].strValue, "_");
let prefixLen: int = String_Len(prefix) as int;
let nameLen: int = String_Len(name) as int;
if nameLen > prefixLen {
let namePrefix: String = bux_str_slice(name, 0, prefixLen as uint);
if String_Eq(namePrefix, prefix) {
let mangledPrefix: String = String_Concat(
Lcx_MangleName(ctx.genStructs[i].strValue, ctx.substArg0, ctx.substArg1, ctx.genStructs[i].typeParamCount),
"_");
let rest: String = bux_str_slice(name, prefixLen as uint, (nameLen - prefixLen) as uint);
return String_Concat(mangledPrefix, rest);
}
}
}
i = i + 1;
}
return name;
}
func Lcx_MangleEnumTagsNode(ctx: *LowerCtx, node: *HirNode) {
if node == null as *HirNode { return; }
if node.kind == hVar {
node.strValue = Lcx_SubstEnumName(ctx, node.strValue);
}
if node.kind == hStructInit {
node.strValue = Lcx_SubstEnumName(ctx, node.strValue);
}
if node.kind == hAlloca || node.kind == hStore {
node.typeName = Lcx_SubstEnumName(ctx, node.typeName);
}
Lcx_MangleEnumTagsNode(ctx, node.child1);
Lcx_MangleEnumTagsNode(ctx, node.child2);
Lcx_MangleEnumTagsNode(ctx, node.child3);
if (node.kind == hCall || node.kind == hCallIndirect) && node.extraData != null as *void {
var cur: *HirArgList = node.extraData as *HirArgList;
while cur != null as *HirArgList {
Lcx_MangleEnumTagsNode(ctx, cur.node);
cur = cur.next;
}
}
if node.kind == hIf && node.extraData != null as *void {
Lcx_MangleEnumTagsNode(ctx, node.extraData as *HirNode);
}
}
// Strip type-arg suffix from a mangled generic instance name. // Strip type-arg suffix from a mangled generic instance name.
// E.g. ("Box_int", "int", "", 1) -> "Box"; ("Pair_int_String", "int", "String", 2) -> "Pair". // E.g. ("Box_int", "int", "", 1) -> "Box"; ("Pair_int_String", "int", "String", 2) -> "Pair".
func Lcx_StripTypeArgs(typeName: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String { func Lcx_StripTypeArgs(typeName: String, typeArg0: String, typeArg1: String, typeArgCount: int) -> String {
@@ -2339,6 +2509,62 @@ module HirLower {
return n; return n;
} }
// Is (type test): expr is Type — lowered to tag check for enums
if kind == ekIs {
let operand: *HirNode = Lcx_LowerExpr(ctx, expr.child1);
if expr.refType != null as *TypeExpr {
let isType: String = "";
if !String_Eq(expr.refType.typeName, "") {
isType = expr.refType.typeName;
}
if !String_Eq(isType, "") {
// Check if operand is an enum type — resolve from HIR type info
let enumName: String = "";
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
let sym: Symbol = Scope_Lookup(ctx.scope, expr.child1.strValue);
if sym.decl != null as *Decl && sym.decl.kind == dkEnum {
enumName = expr.child1.strValue;
}
}
if !String_Eq(enumName, "") {
let tagName: String = String_Concat(String_Concat(enumName, "_"), isType);
// tagPtr = operand.tag
let tagPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagPtr.kind = hFieldPtr;
tagPtr.line = expr.line;
tagPtr.column = expr.column;
tagPtr.strValue = "tag";
tagPtr.child1 = operand;
// tagLoad = *tagPtr
let tagLoad: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagLoad.kind = hLoad;
tagLoad.line = expr.line;
tagLoad.column = expr.column;
tagLoad.child1 = tagPtr;
// tagConst = Enum_Target
let tagConst: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
tagConst.kind = hVar;
tagConst.line = expr.line;
tagConst.column = expr.column;
tagConst.strValue = tagName;
let result: *HirNode = Lcx_MakeBinHir(tkEq, tagLoad, tagConst, expr.line, expr.column);
result.sourceFile = ctx.currentSourceFile;
return result;
}
}
}
// Fallback: emit a compile-time error diagnostic via HIR comment
// For non-enum types, is always returns false at runtime
let result: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
result.kind = hLit;
result.line = expr.line;
result.column = expr.column;
result.typeKind = tyBool;
result.typeName = "bool";
result.boolValue = false;
return result;
}
// Struct init: TypeName { field: value, ... } // Struct init: TypeName { field: value, ... }
if kind == ekStructInit { if kind == ekStructInit {
// Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant // Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
@@ -3252,7 +3478,7 @@ module HirLower {
if stmt.refStmtBlock != null as *Block { if stmt.refStmtBlock != null as *Block {
let caseBlock: *Block = stmt.refStmtBlock; let caseBlock: *Block = stmt.refStmtBlock;
var caseCount: int = caseBlock.stmtCount; var caseCount: int = caseBlock.stmtCount;
// Collect cases into array for reverse iteration // Collect cases into fixed-size locals for reverse iteration
var c0: *Stmt = null as *Stmt; var c0: *Stmt = null as *Stmt;
var c1: *Stmt = null as *Stmt; var c1: *Stmt = null as *Stmt;
var c2: *Stmt = null as *Stmt; var c2: *Stmt = null as *Stmt;
@@ -3899,6 +4125,11 @@ module HirLower {
return Lcx_EvalConstExprEnv(ctx, expr.child1, env); return Lcx_EvalConstExprEnv(ctx, expr.child1, env);
} }
// Is — not evaluable at compile time
if expr.kind == ekIs {
return CtVal_Make(0);
}
// Const function call // Const function call
if expr.kind == ekCall { if expr.kind == ekCall {
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent { if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
@@ -4106,6 +4337,11 @@ module HirLower {
ctx.genStructs[ctx.genStructCount] = *decl; ctx.genStructs[ctx.genStructCount] = *decl;
ctx.genStructCount = ctx.genStructCount + 1; ctx.genStructCount = ctx.genStructCount + 1;
} }
if decl.kind == dkEnum && decl.typeParamCount > 0 {
// Store in genStructs array for mono lookup (reuse existing infrastructure)
ctx.genStructs[ctx.genStructCount] = *decl;
ctx.genStructCount = ctx.genStructCount + 1;
}
// Generic impl/extend blocks: methods inherit the impl's type params // Generic impl/extend blocks: methods inherit the impl's type params
if decl.kind == dkImpl && decl.typeParamCount > 0 { if decl.kind == dkImpl && decl.typeParamCount > 0 {
let implTypeName: String = decl.strValue; let implTypeName: String = decl.strValue;
@@ -4210,6 +4446,11 @@ module HirLower {
hm.constCount = hm.constCount + 1; hm.constCount = hm.constCount + 1;
} }
if decl.kind == dkEnum { if decl.kind == dkEnum {
// Skip generic enums — instantiated on demand via Lcx_GenerateEnumInstance
if decl.typeParamCount > 0 {
decl = decl.childDecl2;
continue;
}
let ei: int = hm.enumCount; let ei: int = hm.enumCount;
hm.enums[ei].name = decl.strValue; hm.enums[ei].name = decl.strValue;
// Populate variants // Populate variants
+23 -4
View File
@@ -1099,7 +1099,10 @@ module Parser {
if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile { if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile {
break; break;
} }
if params.paramCount >= 9 { break; } if params.paramCount >= 9 {
parserEmitDiag(p, line, col, "too many closure parameters (max 8)");
break;
}
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name in closure"); let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name in closure");
discard parserExpect(p, tkColon, "expected ':' in closure parameter"); discard parserExpect(p, tkColon, "expected ':' in closure parameter");
let ptype: *TypeExpr = parserParseType(p); let ptype: *TypeExpr = parserParseType(p);
@@ -1989,7 +1992,11 @@ module Parser {
if parserCheck(p, tkRParen) || parserPeek(p, 0) == tkEndOfFile { if parserCheck(p, tkRParen) || parserPeek(p, 0) == tkEndOfFile {
break; break;
} }
if d.paramCount >= 9 { break; } if d.paramCount >= 9 {
let tok: LexToken = parserCurToken(p);
parserEmitDiag(p, tok.line, tok.column, "too many function parameters (max 8)");
break;
}
let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name"); let nameTok: LexToken = parserExpectIdentOrKeyword(p, "expected parameter name");
discard parserExpect(p, tkColon, "expected ':' in parameter"); discard parserExpect(p, tkColon, "expected ':' in parameter");
let ptype: *TypeExpr = parserParseType(p); let ptype: *TypeExpr = parserParseType(p);
@@ -2169,7 +2176,10 @@ module Parser {
discard parserExpect(p, tkLBrace, "expected '{'"); discard parserExpect(p, tkLBrace, "expected '{'");
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile { while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if fieldCount >= 256 { break; } if fieldCount >= 256 {
parserEmitDiag(p, line, col, "too many struct fields (max 255)");
break;
}
if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; } if parserCheck(p, tkNewLine) { discard parserAdvance(p); continue; }
if parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; } if parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
let beforePos: int = p.pos; let beforePos: int = p.pos;
@@ -2210,9 +2220,15 @@ module Parser {
d.isPublic = isPublic; d.isPublic = isPublic;
d.strValue = nameTok.text; d.strValue = nameTok.text;
// Type params <T: Bound, U: Bound2>
parserParseTypeParams(p, d);
discard parserExpect(p, tkLBrace, "expected '{'"); discard parserExpect(p, tkLBrace, "expected '{'");
while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile { while !parserCheck(p, tkRBrace) && parserPeek(p, 0) != tkEndOfFile {
if d.variantCount >= 9 { break; } if d.variantCount >= 9 {
parserEmitDiag(p, line, col, "too many enum variants (max 8)");
break;
}
if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; } if parserCheck(p, tkNewLine) || parserCheck(p, tkSemicolon) { discard parserAdvance(p); continue; }
let vName: LexToken = parserExpect(p, tkIdent, "expected variant name"); let vName: LexToken = parserExpect(p, tkIdent, "expected variant name");
@@ -2676,6 +2692,9 @@ module Parser {
d.strValue2 = ifaceName.text; d.strValue2 = ifaceName.text;
} }
// Type params <T: Bound, U: Bound2>
parserParseTypeParams(p, d);
discard parserExpect(p, tkLBrace, "expected '{'"); discard parserExpect(p, tkLBrace, "expected '{'");
var methods: *Decl = null as *Decl; var methods: *Decl = null as *Decl;
var lastMethod: *Decl = null as *Decl; var lastMethod: *Decl = null as *Decl;
+14 -1
View File
@@ -162,9 +162,22 @@ module Types {
func Type_Eq(a: Type, b: Type) -> bool { func Type_Eq(a: Type, b: Type) -> bool {
if a.kind != b.kind { return false; } if a.kind != b.kind { return false; }
if a.kind == tyNamed || a.kind == tyTypeParam { if a.kind == tyNamed || a.kind == tyTypeParam || a.kind == tyFunc {
return String_Eq(a.name, b.name); return String_Eq(a.name, b.name);
} }
if a.kind == tyPointer {
if a.innerKind1 != b.innerKind1 { return false; }
return String_Eq(a.innerName1, b.innerName1);
}
if a.kind == tySlice || a.kind == tyTuple {
if a.innerKind1 != b.innerKind1 { return false; }
if !String_Eq(a.innerName1, b.innerName1) { return false; }
if a.innerKind2 != b.innerKind2 { return false; }
if !String_Eq(a.innerName2, b.innerName2) { return false; }
if a.innerKind3 != b.innerKind3 { return false; }
if !String_Eq(a.innerName3, b.innerName3) { return false; }
return true;
}
return true; return true;
} }
+14 -14
View File
@@ -45,21 +45,21 @@ func Main() -> int {
Test_AssertFalse(Set_IsEmpty<int>(&s)); Test_AssertFalse(Set_IsEmpty<int>(&s));
Set_Free<int>(&s); Set_Free<int>(&s);
let ok: Result = Result_NewOk(42); let ok: Result<int, String> = Result_NewOk<int, String>(42);
let err: Result = Result_NewErr("boom"); let err: Result<int, String> = Result_NewErr<int, String>("boom");
Test_AssertTrue(Result_IsOk(ok)); Test_AssertTrue(Result_IsOk<int, String>(ok));
Test_AssertTrue(Result_IsErr(err)); Test_AssertTrue(Result_IsErr<int, String>(err));
Test_AssertEqInt(Result_UnwrapOr(err, -1), -1); Test_AssertEqInt(Result_UnwrapOr<int, String>(err, -1), -1);
let recovered: Result = Result_Or(err, Result_NewOk(7)); let recovered: Result<int, String> = Result_Or<int, String>(err, Result_NewOk<int, String>(7));
Test_AssertEqInt(Result_UnwrapOr(recovered, 0), 7); Test_AssertEqInt(Result_UnwrapOr<int, String>(recovered, 0), 7);
Test_AssertTrue(String_Eq(Result_UnwrapErr(err), "boom")); Test_AssertTrue(String_Eq(Result_UnwrapErr<int, String>(err), "boom"));
let some: Option = Option_NewSome(5); let some: Option<int> = Option_NewSome<int>(5);
let none: Option = Option_NewNone(); let none: Option<int> = Option_NewNone<int>();
Test_AssertTrue(Option_IsSome(some)); Test_AssertTrue(Option_IsSome<int>(some));
Test_AssertEqInt(Option_UnwrapOr(none, 9), 9); Test_AssertEqInt(Option_UnwrapOr<int>(none, 9), 9);
let filled: Option = Option_Or(none, Option_NewSome(3)); let filled: Option<int> = Option_Or<int>(none, Option_NewSome<int>(3));
Test_AssertEqInt(Option_UnwrapOr(filled, 0), 3); Test_AssertEqInt(Option_UnwrapOr<int>(filled, 0), 3);
PrintLine("stdlib_collections: ok"); PrintLine("stdlib_collections: ok");
Test_Pass("stdlib_collections"); Test_Pass("stdlib_collections");