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
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:
@@ -5,7 +5,7 @@ BUILD_DIR := build
|
||||
# Project-local nimcache so CI can cache compiles (default is ~/.cache/nim).
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -462,6 +462,7 @@ type
|
||||
of dkEnum:
|
||||
declEnumName*: string
|
||||
declEnumBaseType*: TypeExpr
|
||||
declEnumTypeParams*: seq[TypeParam]
|
||||
declEnumVariants*: seq[EnumVariant]
|
||||
of dkUnion:
|
||||
declUnionName*: string
|
||||
|
||||
+238
-6
@@ -18,6 +18,9 @@ type
|
||||
generatedStructInsts*: Table[string, bool] # Track generated struct instantiations
|
||||
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
|
||||
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
|
||||
generatedFuncInsts*: Table[string, bool] # Track generated function instantiations
|
||||
extraFuncs*: seq[HirFunc] # Monomorphized generic methods
|
||||
@@ -38,6 +41,8 @@ type
|
||||
## Locals whose value was moved into another owner (struct field, let, return).
|
||||
## Auto-Drop is skipped for these (session 37 — field-move ownership).
|
||||
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
|
||||
## (e.g. "items", "inner.items" for nested `a.b.c` — session 70/73).
|
||||
## 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.extraStructs = @[]
|
||||
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.generatedFuncInsts = initTable[string, bool]()
|
||||
result.extraFuncs = @[]
|
||||
@@ -970,6 +978,56 @@ proc substituteType(ctx: var LowerCtx, te: TypeExpr, subst: Table[string, Type])
|
||||
ctx.generatedStructInsts[mangledName] = true
|
||||
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
|
||||
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)
|
||||
of tekOwn:
|
||||
return substituteType(ctx, te.pointerPointee, subst)
|
||||
@@ -1029,6 +1087,56 @@ proc resolveTypeExpr(ctx: var LowerCtx, te: TypeExpr): Type =
|
||||
ctx.generatedStructInsts[mangledName] = true
|
||||
ctx.structInstMap[mangledName] = (te.typeName, concreteArgs)
|
||||
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
|
||||
of "void": return makeVoid()
|
||||
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
|
||||
if objType.name.endsWith("_Data"):
|
||||
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:
|
||||
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:
|
||||
let fieldName = variant.name & "_" & $i
|
||||
if fieldName == expr.exprFieldName:
|
||||
return ctx.resolveTypeExpr(f)
|
||||
return substituteType(ctx, f, subst)
|
||||
for nf in variant.namedFields:
|
||||
if nf.name == expr.exprFieldName:
|
||||
return ctx.resolveTypeExpr(nf.ftype)
|
||||
return substituteType(ctx, nf.ftype, subst)
|
||||
var sym = ctx.globalScope.lookup(objType.name)
|
||||
var decl = if sym != nil: sym.decl else: nil
|
||||
# If the type is a monomorphized generic struct instance, look up the base
|
||||
if decl == nil and ctx.structInstMap.hasKey(objType.name):
|
||||
let (baseName, typeArgs) = ctx.structInstMap[objType.name]
|
||||
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
|
||||
var subst = initTable[string, Type]()
|
||||
for i, tp in decl.declStructTypeParams:
|
||||
@@ -1212,6 +1336,12 @@ proc resolveExprType(ctx: var LowerCtx, expr: Expr): Type =
|
||||
of tekOwn, tekPointer:
|
||||
return substituteType(ctx, f.ftype, subst)
|
||||
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:
|
||||
case decl.kind
|
||||
of dkStruct:
|
||||
@@ -1849,6 +1979,16 @@ proc lowerExpr(ctx: var LowerCtx, expr: Expr): HirNode =
|
||||
let argType = ctx.resolveTypeExpr(targ)
|
||||
suffix.add(argType.toString)
|
||||
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
|
||||
var enumDecl: Decl = nil
|
||||
let enumSym = ctx.globalScope.lookup(structName)
|
||||
@@ -2181,7 +2321,9 @@ proc lowerStmt(ctx: var LowerCtx, stmt: Stmt): HirNode =
|
||||
of skLet:
|
||||
var initHir: HirNode = nil
|
||||
if stmt.stmtLetInit != nil:
|
||||
ctx.currentInitTypeExpr = stmt.stmtLetType
|
||||
initHir = ctx.lowerExpr(stmt.stmtLetInit)
|
||||
ctx.currentInitTypeExpr = nil
|
||||
let allocaType = if stmt.stmtLetType != nil:
|
||||
# Full resolve covers named, pointer, slice, tuple, func, refs, etc.
|
||||
ctx.resolveTypeExpr(stmt.stmtLetType)
|
||||
@@ -2809,12 +2951,14 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
discard
|
||||
|
||||
|
||||
# First pass: collect generic functions and generic structs
|
||||
# First pass: collect generic functions, generic structs, and generic enums
|
||||
for decl in module.items:
|
||||
if decl.kind == dkFunc and decl.declFuncTypeParams.len > 0:
|
||||
ctx.genericFuncs[decl.declFuncName] = decl
|
||||
if decl.kind == dkStruct and decl.declStructTypeParams.len > 0:
|
||||
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:
|
||||
let typeName = decl.declImplTypeName
|
||||
for methodDecl in decl.declImplMethods:
|
||||
@@ -2864,6 +3008,8 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
fields.add((f.name, fType))
|
||||
structs.add((decl.declStructName, fields))
|
||||
of dkEnum:
|
||||
# Skip generic enums — instantiated on demand via generateEnumInstance
|
||||
if decl.declEnumTypeParams.len > 0: continue
|
||||
var variants: seq[HirEnumVariant] = @[]
|
||||
for v in decl.declEnumVariants:
|
||||
var fields: seq[Type] = @[]
|
||||
@@ -2906,10 +3052,96 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
|
||||
for s in ctx.extraStructs:
|
||||
structs.add(s)
|
||||
|
||||
# Add monomorphized generic enums
|
||||
for e in ctx.extraEnums:
|
||||
enums.add(e)
|
||||
|
||||
# Add monomorphized generic methods
|
||||
for f in ctx.extraFuncs:
|
||||
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
|
||||
var ifaceInfos: seq[tuple[name: string, hasAssocTypes: bool, methods: seq[tuple[name: string, params: seq[Type], ret: Type]]]] = @[]
|
||||
for ifaceName, ifaceDecl in sema.interfaceTable:
|
||||
|
||||
@@ -1404,6 +1404,7 @@ proc parseEnumDecl(p: var Parser, isPublic: bool): Decl =
|
||||
let loc = p.currentLoc
|
||||
discard p.expect(tkEnum, "expected 'enum'")
|
||||
let name = p.expect(tkIdent, "expected enum name").text
|
||||
let typeParams = p.parseTypeParams()
|
||||
var baseType: TypeExpr = nil
|
||||
if p.check(tkColon):
|
||||
discard p.advance()
|
||||
@@ -1444,6 +1445,7 @@ proc parseEnumDecl(p: var Parser, isPublic: bool): Decl =
|
||||
discard p.expect(tkRBrace, "expected '}' to close enum")
|
||||
return Decl(kind: dkEnum, loc: loc, isPublic: isPublic,
|
||||
declEnumName: name, declEnumBaseType: baseType,
|
||||
declEnumTypeParams: typeParams,
|
||||
declEnumVariants: variants)
|
||||
|
||||
proc parseUnionDecl(p: var Parser, isPublic: bool): Decl =
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
@@ -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
@@ -38,21 +38,21 @@ func Main() -> int {
|
||||
Set_Free<int>(&s);
|
||||
|
||||
// --- Result helpers ---
|
||||
let ok: Result = Result_NewOk(42);
|
||||
let err: Result = Result_NewErr("boom");
|
||||
Test_AssertTrue(Result_IsOk(ok));
|
||||
Test_AssertTrue(Result_IsErr(err));
|
||||
Test_AssertEqInt(Result_UnwrapOr(err, -1), -1);
|
||||
let recovered: Result = Result_Or(err, Result_NewOk(7));
|
||||
Test_AssertEqInt(Result_UnwrapOr(recovered, 0), 7);
|
||||
Test_AssertTrue(String_Eq(Result_UnwrapErr(err), "boom"));
|
||||
let ok: Result<int, String> = Result_NewOk<int, String>(42);
|
||||
let err: Result<int, String> = Result_NewErr<int, String>("boom");
|
||||
Test_AssertTrue(Result_IsOk<int, String>(ok));
|
||||
Test_AssertTrue(Result_IsErr<int, String>(err));
|
||||
Test_AssertEqInt(Result_UnwrapOr<int, String>(err, -1), -1);
|
||||
let recovered: Result<int, String> = Result_Or<int, String>(err, Result_NewOk<int, String>(7));
|
||||
Test_AssertEqInt(Result_UnwrapOr<int, String>(recovered, 0), 7);
|
||||
Test_AssertTrue(String_Eq(Result_UnwrapErr<int, String>(err), "boom"));
|
||||
|
||||
// --- Option helpers ---
|
||||
let some: Option = Option_NewSome(5);
|
||||
let none: Option = Option_NewNone();
|
||||
Test_AssertTrue(Option_IsSome(some));
|
||||
let o2: Option = Option_Or(none, some);
|
||||
Test_AssertEqInt(Option_UnwrapOr(o2, 0), 5);
|
||||
let some: Option<int> = Option_NewSome<int>(5);
|
||||
let none: Option<int> = Option_NewNone<int>();
|
||||
Test_AssertTrue(Option_IsSome<int>(some));
|
||||
let o2: Option<int> = Option_Or<int>(none, some);
|
||||
Test_AssertEqInt(Option_UnwrapOr<int>(o2, 0), 5);
|
||||
|
||||
PrintLine("map_remove: ok");
|
||||
Test_Pass("map_remove + result helpers");
|
||||
|
||||
@@ -2,6 +2,10 @@ module Std::Iter {
|
||||
|
||||
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> {
|
||||
data: *T,
|
||||
len: uint,
|
||||
|
||||
+11
-14
@@ -3,46 +3,44 @@ module Std::Option {
|
||||
|
||||
extern func bux_exit(code: int);
|
||||
|
||||
enum Option {
|
||||
Some(int),
|
||||
enum Option<T> {
|
||||
Some(T),
|
||||
None,
|
||||
}
|
||||
|
||||
func Option_NewSome(value: int) -> Option {
|
||||
let o: Option = Option { tag: Option_Some };
|
||||
func Option_NewSome<T>(value: T) -> Option<T> {
|
||||
let o: Option<T> = Option { tag: Option_Some };
|
||||
o.data.Some_0 = value;
|
||||
return o;
|
||||
}
|
||||
|
||||
func Option_NewNone() -> Option {
|
||||
func Option_NewNone<T>() -> Option<T> {
|
||||
return Option { tag: Option_None };
|
||||
}
|
||||
|
||||
func Option_IsSome(o: Option) -> bool {
|
||||
func Option_IsSome<T>(o: Option<T>) -> bool {
|
||||
return o.tag == Option_Some;
|
||||
}
|
||||
|
||||
func Option_IsNone(o: Option) -> bool {
|
||||
func Option_IsNone<T>(o: Option<T>) -> bool {
|
||||
return o.tag == Option_None;
|
||||
}
|
||||
|
||||
func Option_Unwrap(o: Option) -> int {
|
||||
func Option_Unwrap<T>(o: Option<T>) -> T {
|
||||
if o.tag != Option_Some {
|
||||
PrintLine("panic: unwrap on None");
|
||||
return 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 {
|
||||
return o.data.Some_0;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/* Unwrap Some or panic with a custom message */
|
||||
func Option_Expect(o: Option, msg: String) -> int {
|
||||
func Option_Expect<T>(o: Option<T>, msg: String) -> T {
|
||||
if o.tag != Option_Some {
|
||||
PrintLine(msg);
|
||||
bux_exit(1);
|
||||
@@ -50,8 +48,7 @@ module Std::Option {
|
||||
return o.data.Some_0;
|
||||
}
|
||||
|
||||
/* If o is Some return it, otherwise return other */
|
||||
func Option_Or(o: Option, other: Option) -> Option {
|
||||
func Option_Or<T>(o: Option<T>, other: Option<T>) -> Option<T> {
|
||||
if o.tag == Option_Some {
|
||||
return o;
|
||||
}
|
||||
|
||||
+14
-19
@@ -3,48 +3,46 @@ module Std::Result {
|
||||
|
||||
extern func bux_exit(code: int);
|
||||
|
||||
enum Result {
|
||||
Ok(int),
|
||||
Err(String),
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E),
|
||||
}
|
||||
|
||||
func Result_NewOk(value: int) -> Result {
|
||||
let r: Result = Result { tag: Result_Ok };
|
||||
func Result_NewOk<T, E>(value: T) -> Result<T, E> {
|
||||
let r: Result<T, E> = Result { tag: Result_Ok };
|
||||
r.data.Ok_0 = value;
|
||||
return r;
|
||||
}
|
||||
|
||||
func Result_NewErr(msg: String) -> Result {
|
||||
let r: Result = Result { tag: Result_Err };
|
||||
func Result_NewErr<T, E>(msg: E) -> Result<T, E> {
|
||||
let r: Result<T, E> = Result { tag: Result_Err };
|
||||
r.data.Err_0 = msg;
|
||||
return r;
|
||||
}
|
||||
|
||||
func Result_IsOk(r: Result) -> bool {
|
||||
func Result_IsOk<T, E>(r: Result<T, E>) -> bool {
|
||||
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;
|
||||
}
|
||||
|
||||
func Result_Unwrap(r: Result) -> int {
|
||||
func Result_Unwrap<T, E>(r: Result<T, E>) -> T {
|
||||
if r.tag != Result_Ok {
|
||||
PrintLine("panic: unwrap on Err");
|
||||
return 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 {
|
||||
return r.data.Ok_0;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/* Unwrap Ok or panic with a custom message */
|
||||
func Result_Expect(r: Result, msg: String) -> int {
|
||||
func Result_Expect<T, E>(r: Result<T, E>, msg: String) -> T {
|
||||
if r.tag != Result_Ok {
|
||||
PrintLine(msg);
|
||||
bux_exit(1);
|
||||
@@ -52,17 +50,14 @@ module Std::Result {
|
||||
return r.data.Ok_0;
|
||||
}
|
||||
|
||||
/* Extract Err payload (panics if Ok) */
|
||||
func Result_UnwrapErr(r: Result) -> String {
|
||||
func Result_UnwrapErr<T, E>(r: Result<T, E>) -> E {
|
||||
if r.tag != Result_Err {
|
||||
PrintLine("panic: unwrap_err on Ok");
|
||||
return "";
|
||||
}
|
||||
return r.data.Err_0;
|
||||
}
|
||||
|
||||
/* If r is Ok return it, otherwise return other */
|
||||
func Result_Or(r: Result, other: Result) -> Result {
|
||||
func Result_Or<T, E>(r: Result<T, E>, other: Result<T, E>) -> Result<T, E> {
|
||||
if r.tag == Result_Ok {
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -1498,6 +1498,14 @@ module CBackend {
|
||||
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, ...})
|
||||
if kind == hStructInit {
|
||||
// Field values taken by value → skip auto-Drop of those locals
|
||||
|
||||
+242
-1
@@ -281,6 +281,29 @@ module HirLower {
|
||||
Lcx_GenerateStructInstance(ctx, genStruct, r.typeArgName0, r.typeArgName1, te.typeArgCount);
|
||||
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)
|
||||
@@ -429,6 +452,94 @@ module HirLower {
|
||||
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
|
||||
func Lcx_ExtractElemFromName(typeName: String) -> String {
|
||||
if String_Eq(typeName, "") { return ""; }
|
||||
@@ -546,6 +657,10 @@ module HirLower {
|
||||
// Lower the generic function with substitution active
|
||||
let f: *HirFunc = Lcx_LowerFunc(ctx, genDecl);
|
||||
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
|
||||
// file (not the call-site module), even if synthetic nodes lacked a path.
|
||||
if f.body != null as *HirNode && !String_Eq(genDecl.sourceFile, "") {
|
||||
@@ -566,6 +681,61 @@ module HirLower {
|
||||
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.
|
||||
// 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 {
|
||||
@@ -2339,6 +2509,62 @@ module HirLower {
|
||||
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, ... }
|
||||
if kind == ekStructInit {
|
||||
// Simple enum init: EnumName { tag: EnumName_Variant } -> EnumName_Variant
|
||||
@@ -3252,7 +3478,7 @@ module HirLower {
|
||||
if stmt.refStmtBlock != null as *Block {
|
||||
let caseBlock: *Block = stmt.refStmtBlock;
|
||||
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 c1: *Stmt = null as *Stmt;
|
||||
var c2: *Stmt = null as *Stmt;
|
||||
@@ -3899,6 +4125,11 @@ module HirLower {
|
||||
return Lcx_EvalConstExprEnv(ctx, expr.child1, env);
|
||||
}
|
||||
|
||||
// Is — not evaluable at compile time
|
||||
if expr.kind == ekIs {
|
||||
return CtVal_Make(0);
|
||||
}
|
||||
|
||||
// Const function call
|
||||
if expr.kind == ekCall {
|
||||
if expr.child1 != null as *Expr && expr.child1.kind == ekIdent {
|
||||
@@ -4106,6 +4337,11 @@ module HirLower {
|
||||
ctx.genStructs[ctx.genStructCount] = *decl;
|
||||
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
|
||||
if decl.kind == dkImpl && decl.typeParamCount > 0 {
|
||||
let implTypeName: String = decl.strValue;
|
||||
@@ -4210,6 +4446,11 @@ module HirLower {
|
||||
hm.constCount = hm.constCount + 1;
|
||||
}
|
||||
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;
|
||||
hm.enums[ei].name = decl.strValue;
|
||||
// Populate variants
|
||||
|
||||
+23
-4
@@ -1099,7 +1099,10 @@ module Parser {
|
||||
if parserCheck(p, tkPipe) || parserPeek(p, 0) == tkEndOfFile {
|
||||
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");
|
||||
discard parserExpect(p, tkColon, "expected ':' in closure parameter");
|
||||
let ptype: *TypeExpr = parserParseType(p);
|
||||
@@ -1989,7 +1992,11 @@ module Parser {
|
||||
if parserCheck(p, tkRParen) || parserPeek(p, 0) == tkEndOfFile {
|
||||
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");
|
||||
discard parserExpect(p, tkColon, "expected ':' in parameter");
|
||||
let ptype: *TypeExpr = parserParseType(p);
|
||||
@@ -2169,7 +2176,10 @@ module Parser {
|
||||
|
||||
discard parserExpect(p, tkLBrace, "expected '{'");
|
||||
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, tkSemicolon) { discard parserAdvance(p); continue; }
|
||||
let beforePos: int = p.pos;
|
||||
@@ -2210,9 +2220,15 @@ module Parser {
|
||||
d.isPublic = isPublic;
|
||||
d.strValue = nameTok.text;
|
||||
|
||||
// Type params <T: Bound, U: Bound2>
|
||||
parserParseTypeParams(p, d);
|
||||
|
||||
discard parserExpect(p, tkLBrace, "expected '{'");
|
||||
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; }
|
||||
let vName: LexToken = parserExpect(p, tkIdent, "expected variant name");
|
||||
|
||||
@@ -2676,6 +2692,9 @@ module Parser {
|
||||
d.strValue2 = ifaceName.text;
|
||||
}
|
||||
|
||||
// Type params <T: Bound, U: Bound2>
|
||||
parserParseTypeParams(p, d);
|
||||
|
||||
discard parserExpect(p, tkLBrace, "expected '{'");
|
||||
var methods: *Decl = null as *Decl;
|
||||
var lastMethod: *Decl = null as *Decl;
|
||||
|
||||
+14
-1
@@ -162,9 +162,22 @@ module Types {
|
||||
|
||||
func Type_Eq(a: Type, b: Type) -> bool {
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,21 +45,21 @@ func Main() -> int {
|
||||
Test_AssertFalse(Set_IsEmpty<int>(&s));
|
||||
Set_Free<int>(&s);
|
||||
|
||||
let ok: Result = Result_NewOk(42);
|
||||
let err: Result = Result_NewErr("boom");
|
||||
Test_AssertTrue(Result_IsOk(ok));
|
||||
Test_AssertTrue(Result_IsErr(err));
|
||||
Test_AssertEqInt(Result_UnwrapOr(err, -1), -1);
|
||||
let recovered: Result = Result_Or(err, Result_NewOk(7));
|
||||
Test_AssertEqInt(Result_UnwrapOr(recovered, 0), 7);
|
||||
Test_AssertTrue(String_Eq(Result_UnwrapErr(err), "boom"));
|
||||
let ok: Result<int, String> = Result_NewOk<int, String>(42);
|
||||
let err: Result<int, String> = Result_NewErr<int, String>("boom");
|
||||
Test_AssertTrue(Result_IsOk<int, String>(ok));
|
||||
Test_AssertTrue(Result_IsErr<int, String>(err));
|
||||
Test_AssertEqInt(Result_UnwrapOr<int, String>(err, -1), -1);
|
||||
let recovered: Result<int, String> = Result_Or<int, String>(err, Result_NewOk<int, String>(7));
|
||||
Test_AssertEqInt(Result_UnwrapOr<int, String>(recovered, 0), 7);
|
||||
Test_AssertTrue(String_Eq(Result_UnwrapErr<int, String>(err), "boom"));
|
||||
|
||||
let some: Option = Option_NewSome(5);
|
||||
let none: Option = Option_NewNone();
|
||||
Test_AssertTrue(Option_IsSome(some));
|
||||
Test_AssertEqInt(Option_UnwrapOr(none, 9), 9);
|
||||
let filled: Option = Option_Or(none, Option_NewSome(3));
|
||||
Test_AssertEqInt(Option_UnwrapOr(filled, 0), 3);
|
||||
let some: Option<int> = Option_NewSome<int>(5);
|
||||
let none: Option<int> = Option_NewNone<int>();
|
||||
Test_AssertTrue(Option_IsSome<int>(some));
|
||||
Test_AssertEqInt(Option_UnwrapOr<int>(none, 9), 9);
|
||||
let filled: Option<int> = Option_Or<int>(none, Option_NewSome<int>(3));
|
||||
Test_AssertEqInt(Option_UnwrapOr<int>(filled, 0), 3);
|
||||
|
||||
PrintLine("stdlib_collections: ok");
|
||||
Test_Pass("stdlib_collections");
|
||||
|
||||
Reference in New Issue
Block a user