diff --git a/Makefile b/Makefile index 7adb4b0..eed535a 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/bootstrap/ast.nim b/bootstrap/ast.nim index 06cc6a5..c9a16c0 100644 --- a/bootstrap/ast.nim +++ b/bootstrap/ast.nim @@ -462,6 +462,7 @@ type of dkEnum: declEnumName*: string declEnumBaseType*: TypeExpr + declEnumTypeParams*: seq[TypeParam] declEnumVariants*: seq[EnumVariant] of dkUnion: declUnionName*: string diff --git a/bootstrap/hir_lower.nim b/bootstrap/hir_lower.nim index 3a24f78..d684262 100644 --- a/bootstrap/hir_lower.nim +++ b/bootstrap/hir_lower.nim @@ -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,47 +1279,69 @@ 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: - decl = baseSym.decl - var subst = initTable[string, Type]() - for i, tp in decl.declStructTypeParams: - if i < typeArgs.len: - subst[tp.name] = typeArgs[i] - for f in decl.declStructFields: - if f.name == expr.exprFieldName: - if f.ftype != nil: - case f.ftype.kind - of tekNamed: - if f.ftype.typeArgs.len > 0: + 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: + if i < typeArgs.len: + subst[tp.name] = typeArgs[i] + for f in decl.declStructFields: + if f.name == expr.exprFieldName: + if f.ftype != nil: + case f.ftype.kind + of tekNamed: + if f.ftype.typeArgs.len > 0: + return substituteType(ctx, f.ftype, subst) + case f.ftype.typeName + of "int", "int32", "int64": return makeInt() + of "float64": return makeFloat64() + of "float32": return makeFloat32() + of "bool": return makeBool() + else: + if subst.hasKey(f.ftype.typeName): + return subst[f.ftype.typeName] + return makeNamed(f.ftype.typeName) + of tekOwn, tekPointer: return substituteType(ctx, f.ftype, subst) - case f.ftype.typeName - of "int", "int32", "int64": return makeInt() - of "float64": return makeFloat64() - of "float32": return makeFloat32() - of "bool": return makeBool() - else: - if subst.hasKey(f.ftype.typeName): - return subst[f.ftype.typeName] - return makeNamed(f.ftype.typeName) - of tekOwn, tekPointer: - 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: 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: diff --git a/bootstrap/parser.nim b/bootstrap/parser.nim index 65e5384..e02df68 100644 --- a/bootstrap/parser.nim +++ b/bootstrap/parser.nim @@ -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 = diff --git a/docs/IMPROVEMENTS.md b/docs/IMPROVEMENTS.md new file mode 100644 index 0000000..87c8120 --- /dev/null +++ b/docs/IMPROVEMENTS.md @@ -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`) | `src/parser.bux` | +| B.4 | `Type_Eq` структурно сравнение | `src/types.bux` | +| B.6 | `Iter` 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` и `Option` генерични | `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` и `Option` в stdlib + +## Пример който работи + +```bux +enum Pair { + First(T), Second(U), +} + +func Main() -> int { + let p: Pair = Pair_MakeFirst(42); + if p.tag == Pair_First { + PrintInt(p.data.First_0 as int64); // → 42 + } + let s: Pair = Pair_MakeSecond(99); + if s.tag == Pair_Second { + PrintInt(s.data.Second_0 as int64); // → 99 + } + return 0; +} +``` diff --git a/examples/generic_enum.bux b/examples/generic_enum.bux new file mode 100644 index 0000000..752b084 --- /dev/null +++ b/examples/generic_enum.bux @@ -0,0 +1,40 @@ +// generic_enum.bux — Full test: generic enums, value reads, match, multiple instances + +import Std::Io::{PrintLine, PrintInt}; + +enum Pair { + First(T), + Second(U), +} + +func Pair_MakeFirst(value: T) -> Pair { + let p: Pair = Pair { tag: Pair_First }; + p.data.First_0 = value; + return p; +} + +func Pair_MakeSecond(value: U) -> Pair { + let p: Pair = Pair { tag: Pair_Second }; + p.data.Second_0 = value; + return p; +} + +func Main() -> int { + // Test 1: tag check + value read + let p: Pair = Pair_MakeFirst(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 = Pair_MakeSecond(99); + if s.tag == Pair_Second { + Print("Second value: "); + PrintInt(s.data.Second_0 as int64); + PrintLine(""); + } + + return 0; +} diff --git a/examples/map_remove.bux b/examples/map_remove.bux index 7f87d1a..8222298 100644 --- a/examples/map_remove.bux +++ b/examples/map_remove.bux @@ -38,21 +38,21 @@ func Main() -> int { Set_Free(&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 = 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")); // --- 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 = 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); PrintLine("map_remove: ok"); Test_Pass("map_remove + result helpers"); diff --git a/lib/Iter.bux b/lib/Iter.bux index 6537b22..f8c463e 100644 --- a/lib/Iter.bux +++ b/lib/Iter.bux @@ -2,6 +2,10 @@ module Std::Iter { import Std::Array::*; + // SAFETY: Iter stores a raw *T pointer to the source array's element buffer. + // The iterator MUST NOT outlive the source Array. 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 { data: *T, len: uint, diff --git a/lib/Option.bux b/lib/Option.bux index 04e7b1f..0f532fb 100644 --- a/lib/Option.bux +++ b/lib/Option.bux @@ -3,46 +3,44 @@ module Std::Option { extern func bux_exit(code: int); - enum Option { - Some(int), + enum Option { + Some(T), None, } - func Option_NewSome(value: int) -> Option { - let o: Option = Option { tag: Option_Some }; + func Option_NewSome(value: T) -> Option { + let o: Option = Option { tag: Option_Some }; o.data.Some_0 = value; return o; } - func Option_NewNone() -> Option { + func Option_NewNone() -> Option { return Option { tag: Option_None }; } - func Option_IsSome(o: Option) -> bool { + func Option_IsSome(o: Option) -> bool { return o.tag == Option_Some; } - func Option_IsNone(o: Option) -> bool { + func Option_IsNone(o: Option) -> bool { return o.tag == Option_None; } - func Option_Unwrap(o: Option) -> int { + func Option_Unwrap(o: Option) -> 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(o: Option, 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(o: Option, 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(o: Option, other: Option) -> Option { if o.tag == Option_Some { return o; } diff --git a/lib/Result.bux b/lib/Result.bux index 1a5ba2c..5ae3866 100644 --- a/lib/Result.bux +++ b/lib/Result.bux @@ -3,48 +3,46 @@ module Std::Result { extern func bux_exit(code: int); - enum Result { - Ok(int), - Err(String), + enum Result { + Ok(T), + Err(E), } - func Result_NewOk(value: int) -> Result { - let r: Result = Result { tag: Result_Ok }; + func Result_NewOk(value: T) -> Result { + let r: Result = 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(msg: E) -> Result { + let r: Result = Result { tag: Result_Err }; r.data.Err_0 = msg; return r; } - func Result_IsOk(r: Result) -> bool { + func Result_IsOk(r: Result) -> bool { return r.tag == Result_Ok; } - func Result_IsErr(r: Result) -> bool { + func Result_IsErr(r: Result) -> bool { return r.tag == Result_Err; } - func Result_Unwrap(r: Result) -> int { + func Result_Unwrap(r: Result) -> 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(r: Result, 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(r: Result, 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(r: Result) -> 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(r: Result, other: Result) -> Result { if r.tag == Result_Ok { return r; } diff --git a/src/c_backend.bux b/src/c_backend.bux index 46016fa..fdd027a 100644 --- a/src/c_backend.bux +++ b/src/c_backend.bux @@ -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 diff --git a/src/hir_lower.bux b/src/hir_lower.bux index 785f58a..8ae4ec6 100644 --- a/src/hir_lower.bux +++ b/src/hir_lower.bux @@ -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 diff --git a/src/parser.bux b/src/parser.bux index 93e8350..84a0ad4 100644 --- a/src/parser.bux +++ b/src/parser.bux @@ -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 + 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 + parserParseTypeParams(p, d); + discard parserExpect(p, tkLBrace, "expected '{'"); var methods: *Decl = null as *Decl; var lastMethod: *Decl = null as *Decl; diff --git a/src/types.bux b/src/types.bux index 802c103..1108c95 100644 --- a/src/types.bux +++ b/src/types.bux @@ -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; } diff --git a/tests/stdlib_golden/collections/src/Main.bux b/tests/stdlib_golden/collections/src/Main.bux index 50bb8f8..486b10d 100644 --- a/tests/stdlib_golden/collections/src/Main.bux +++ b/tests/stdlib_golden/collections/src/Main.bux @@ -45,21 +45,21 @@ func Main() -> int { Test_AssertFalse(Set_IsEmpty(&s)); Set_Free(&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 = 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 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 = 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); PrintLine("stdlib_collections: ok"); Test_Pass("stdlib_collections");