From cf074bec897045ba4a6d9e68a4239f7c5dffcab4 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Sat, 30 May 2026 23:50:33 +0300 Subject: [PATCH] feat: add algebraic enums (tagged unions) support - Generate tagged unions in C backend for enums with data - Add HirEnumVariant type with fields and namedFields - Support _Tag and _Data field access in sema - Support enum variant constants (Result_Ok, Result_Err) - Support _Data union field access (Ok_0, Err_0, etc.) - Add algebraic_enums.bux example Example: enum Result { Ok(int), Err(String) } let r: Result = Result { tag: Result_Ok }; r.data.Ok_0 = 42; --- examples/algebraic_enums.bux | 29 ++++++++++++++ src/c_backend.nim | 75 ++++++++++++++++++++++++++++++------ src/hir.nim | 7 +++- src/hir_lower.nim | 34 ++++++++++++++-- src/sema.nim | 58 ++++++++++++++++++++++++---- 5 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 examples/algebraic_enums.bux diff --git a/examples/algebraic_enums.bux b/examples/algebraic_enums.bux new file mode 100644 index 0000000..86aa4bc --- /dev/null +++ b/examples/algebraic_enums.bux @@ -0,0 +1,29 @@ +// Algebraic Enums - Enums with data (tagged unions) +extern func Std_Io_PrintLine(s: String); +extern func Std_Io_PrintInt(n: int); + +enum Result { + Ok(int), + Err(String) +} + +func Main() -> int { + let r1: Result = Result { tag: Result_Ok }; + r1.data.Ok_0 = 42; + + let r2: Result = Result { tag: Result_Err }; + r2.data.Err_0 = "error message"; + + if r1.tag == Result_Ok { + Std_Io_PrintLine("r1 is Ok:"); + Std_Io_PrintInt(r1.data.Ok_0); + Std_Io_PrintLine(""); + } + + if r2.tag == Result_Err { + Std_Io_PrintLine("r2 is Err:"); + Std_Io_PrintLine(r2.data.Err_0); + } + + return 0; +} diff --git a/src/c_backend.nim b/src/c_backend.nim index 61a0f98..934686c 100644 --- a/src/c_backend.nim +++ b/src/c_backend.nim @@ -355,17 +355,70 @@ proc emitStruct*(be: var CBackend, name: string, fields: seq[tuple[name: string, be.emitLine(&"}} {name};") be.emitLine("") -proc emitEnum*(be: var CBackend, name: string, variants: seq[string]) = - be.emitLine(&"typedef enum {{") - inc be.indent - for i, v in variants: - if i < variants.len - 1: - be.emitLine(&"{name}_{v},") - else: - be.emitLine(&"{name}_{v}") - dec be.indent - be.emitLine(&"}} {name};") - be.emitLine("") +proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) = + # Check if this is a simple enum (no data) or algebraic enum (with data) + var hasData = false + for v in variants: + if v.fields.len > 0 or v.namedFields.len > 0: + hasData = true + break + + if not hasData: + # Simple enum - generate as before + be.emitLine(&"typedef enum {{") + inc be.indent + for i, v in variants: + if i < variants.len - 1: + be.emitLine(&"{name}_{v.name},") + else: + be.emitLine(&"{name}_{v.name}") + dec be.indent + be.emitLine(&"}} {name};") + be.emitLine("") + else: + # Algebraic enum - generate tagged union + # 1. Generate tag enum + be.emitLine(&"typedef enum {{") + inc be.indent + for i, v in variants: + if i < variants.len - 1: + be.emitLine(&"{name}_{v.name},") + else: + be.emitLine(&"{name}_{v.name}") + dec be.indent + be.emitLine(&"}} {name}_Tag;") + be.emitLine("") + + # 2. Generate union for data + be.emitLine(&"typedef union {{") + inc be.indent + for v in variants: + if v.fields.len > 0: + # Positional fields + for i, f in v.fields: + let typ = typeToC(f) + be.emitLine(&"{typ} {v.name}_{i};") + elif v.namedFields.len > 0: + # Named fields - generate as struct + be.emitLine(&"struct {{") + inc be.indent + for nf in v.namedFields: + let typ = typeToC(nf.typ) + be.emitLine(&"{typ} {nf.name};") + dec be.indent + be.emitLine(&"}} {v.name};") + dec be.indent + be.emitLine(&"}} {name}_Data;") + be.emitLine("") + + # 3. Generate main struct with tag + union + be.emitLine(&"typedef struct {{") + inc be.indent + be.emitLine(&"{name}_Tag tag;") + be.emitLine(&"{name}_Data data;") + dec be.indent + be.emitLine(&"}} {name};") + be.emitLine("") proc emitExternDecl*(be: var CBackend, efunc: HirFunc) = let retType = typeToC(efunc.retType) diff --git a/src/hir.nim b/src/hir.nim index e38fdda..973eb3d 100644 --- a/src/hir.nim +++ b/src/hir.nim @@ -124,11 +124,16 @@ type body*: HirNode isPublic*: bool + HirEnumVariant* = object + name*: string + fields*: seq[Type] # Positional fields for algebraic enums + namedFields*: seq[tuple[name: string, typ: Type]] # Named fields + HirModule* = object funcs*: seq[HirFunc] externFuncs*: seq[HirFunc] # Functions declared with extern (no body) structs*: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] - enums*: seq[tuple[name: string, variants: seq[string]]] + enums*: seq[tuple[name: string, variants: seq[HirEnumVariant]]] consts*: seq[tuple[name: string, typ: Type, value: HirNode]] # Constructor helpers diff --git a/src/hir_lower.nim b/src/hir_lower.nim index 9cb9cd4..da0f4fc 100644 --- a/src/hir_lower.nim +++ b/src/hir_lower.nim @@ -433,7 +433,7 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = var funcs: seq[HirFunc] = @[] var externFuncs: seq[HirFunc] = @[] var structs: seq[tuple[name: string, fields: seq[tuple[name: string, typ: Type]]]] = @[] - var enums: seq[tuple[name: string, variants: seq[string]]] = @[] + var enums: seq[tuple[name: string, variants: seq[HirEnumVariant]]] = @[] var consts: seq[tuple[name: string, typ: Type, value: HirNode]] = @[] for decl in module.items: @@ -463,9 +463,37 @@ proc lowerModule*(module: Module, sema: Sema): HirModule = fields.add((f.name, fType)) structs.add((decl.declStructName, fields)) of dkEnum: - var variants: seq[string] = @[] + var variants: seq[HirEnumVariant] = @[] for v in decl.declEnumVariants: - variants.add(v.name) + var fields: seq[Type] = @[] + for f in v.fields: + var fType = makeUnknown() + if f != nil and f.kind == tekNamed: + case f.typeName + of "int", "int32": fType = makeInt() + of "int64": fType = makeInt64() + of "float64": fType = makeFloat64() + of "float32": fType = makeFloat32() + of "bool": fType = makeBool() + of "String", "str": fType = makeStr() + else: fType = makeNamed(f.typeName) + fields.add(fType) + + var namedFields: seq[tuple[name: string, typ: Type]] = @[] + for nf in v.namedFields: + var fType = makeUnknown() + if nf.ftype != nil and nf.ftype.kind == tekNamed: + case nf.ftype.typeName + of "int", "int32": fType = makeInt() + of "int64": fType = makeInt64() + of "float64": fType = makeFloat64() + of "float32": fType = makeFloat32() + of "bool": fType = makeBool() + of "String", "str": fType = makeStr() + else: fType = makeNamed(nf.ftype.typeName) + namedFields.add((nf.name, fType)) + + variants.add(HirEnumVariant(name: v.name, fields: fields, namedFields: namedFields)) enums.add((decl.declEnumName, variants)) of dkConst: let value = ctx.lowerExpr(decl.declConstValue) diff --git a/src/sema.nim b/src/sema.nim index 008a0ae..e3d477d 100644 --- a/src/sema.nim +++ b/src/sema.nim @@ -132,6 +132,13 @@ proc collectGlobals*(sema: var Sema) = if not sema.globalScope.define(sym): sema.emitError(decl.loc, &"duplicate symbol '{decl.declEnumName}'") sema.typeTable[decl.declEnumName] = t + # For algebraic enums, add variant constants with _Tag type + for variant in decl.declEnumVariants: + let variantName = decl.declEnumName & "_" & variant.name + let variantType = makeNamed(decl.declEnumName & "_Tag") + let variantSym = Symbol(kind: skConst, name: variantName, typ: variantType, + decl: decl, isPublic: decl.isPublic) + discard sema.globalScope.define(variantSym) of dkUnion: let t = makeNamed(decl.declUnionName) let sym = Symbol(kind: skType, name: decl.declUnionName, typ: t, @@ -398,14 +405,51 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type = of ekField: let obj = sema.checkExpr(expr.exprFieldObj, scope) if obj.kind == tkNamed: - let sym = sema.globalScope.lookup(obj.name) - if sym != nil and sym.decl != nil and sym.decl.kind == dkStruct: - for f in sym.decl.declStructFields: - if f.name == expr.exprFieldName: - return sema.resolveType(f.ftype) - sema.emitError(expr.loc, &"struct '{obj.name}' has no field '{expr.exprFieldName}'") + # Check if this is a _Data union field access + if obj.name.endsWith("_Data"): + let enumName = obj.name[0..^6] # Remove "_Data" suffix + let enumSym = sema.globalScope.lookup(enumName) + if enumSym != nil and enumSym.decl != nil and enumSym.decl.kind == dkEnum: + # Look for the field in enum variants + for variant in enumSym.decl.declEnumVariants: + # Check positional fields: Ok_0, Ok_1, etc. + for i, f in variant.fields: + let fieldName = variant.name & "_" & $i + if fieldName == expr.exprFieldName: + return sema.resolveType(f) + # Check named fields + for nf in variant.namedFields: + if nf.name == expr.exprFieldName: + return sema.resolveType(nf.ftype) + sema.emitError(expr.loc, &"union '{obj.name}' has no field '{expr.exprFieldName}'") + else: + sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") else: - sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") + let sym = sema.globalScope.lookup(obj.name) + if sym != nil and sym.decl != nil: + if sym.decl.kind == dkStruct: + for f in sym.decl.declStructFields: + if f.name == expr.exprFieldName: + return sema.resolveType(f.ftype) + sema.emitError(expr.loc, &"struct '{obj.name}' has no field '{expr.exprFieldName}'") + elif sym.decl.kind == dkEnum: + # Algebraic enum fields + if expr.exprFieldName == "tag": + return makeNamed(obj.name & "_Tag") + elif expr.exprFieldName == "data": + return makeNamed(obj.name & "_Data") + else: + sema.emitError(expr.loc, &"enum '{obj.name}' has no field '{expr.exprFieldName}'") + elif sym.decl.kind == dkUnion: + # Union fields + for f in sym.decl.declUnionFields: + if f.name == expr.exprFieldName: + return sema.resolveType(f.ftype) + sema.emitError(expr.loc, &"union '{obj.name}' has no field '{expr.exprFieldName}'") + else: + sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") + else: + sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") else: sema.emitError(expr.loc, &"cannot access field on type {obj.toString}") return makeUnknown()