feat: nested multi-field enum patterns (bootstrap + selfhost)

Multi-field variants use Enum_Variant_Payload nested types (avoids tag name clash).
Sema resolves data.Variant.Variant_i; enum field types fully resolve tuples.
Selfhost parses full type exprs in enum payloads; emit structs/tuples before enums.
Example: examples/nested_patterns.bux (Pair::Two, Box::Val((a,c)), Shape::Dot).
This commit is contained in:
2026-07-18 01:33:36 +03:00
parent f9185c96b2
commit 66f11d1869
12 changed files with 383 additions and 209 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ SRC := bootstrap/main.nim
OUT := buxc
BUILD_DIR := build
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 ctfe 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 struct_tuple_pat match_block
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 ctfe 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 struct_tuple_pat match_block nested_patterns
.PHONY: all build dev debug test clean clean-all test-examples selfhost test-golden test-errors selfhost-loop lsp
+20 -19
View File
@@ -600,23 +600,12 @@ proc emitEnum*(be: var CBackend, name: string, variants: seq[HirEnumVariant]) =
let typ = typeToC(be, v.fields[0])
be.emitLine(&"{typ} {v.name}_0;")
elif v.fields.len > 1:
# Multi positional fields — nested struct so fields don't overlay
be.emitLine(&"struct {{")
inc be.indent
for i, f in v.fields:
let typ = typeToC(be, f)
be.emitLine(&"{typ} {v.name}_{i};")
dec be.indent
be.emitLine(&"}} {v.name};")
# Multi positional — named nested typedef Enum_Variant_Payload
let nestedName = name & "_" & v.name & "_Payload"
be.emitLine(&"{nestedName} {v.name};")
elif v.namedFields.len > 0:
# Named fields - generate as struct
be.emitLine(&"struct {{")
inc be.indent
for nf in v.namedFields:
let typ = typeToC(be, nf.typ)
be.emitLine(&"{typ} {nf.name};")
dec be.indent
be.emitLine(&"}} {v.name};")
let nestedName = name & "_" & v.name & "_Payload"
be.emitLine(&"{nestedName} {v.name};")
dec be.indent
be.emitLine(&"}} {name}_Data;")
be.emitLine("")
@@ -716,15 +705,27 @@ proc emitModule*(be: var CBackend, module: HirModule): string =
if module.structs.len > 0:
be.emitLine("")
# Enum definitions (must come before structs that reference them)
# Nested multi-field enum payloads (Enum_Variant_Payload) must be fully
# defined before the algebraic enum union that embeds them by value.
var payloadStructNames: seq[string] = @[]
for e in module.enums:
for v in e.variants:
if v.fields.len > 1 or v.namedFields.len > 0:
payloadStructNames.add(e.name & "_" & v.name & "_Payload")
for s in module.structs:
if s.name in payloadStructNames:
be.emitStruct(s.name, s.fields)
# Enum definitions (after payload structs; before user structs that may use them)
for e in module.enums:
be.emitEnum(e.name, e.variants)
if module.enums.len > 0:
be.emitLine("")
# Struct definitions
# Remaining struct definitions (skip payloads already emitted)
for s in module.structs:
be.emitStruct(s.name, s.fields)
if s.name notin payloadStructNames:
be.emitStruct(s.name, s.fields)
# Slice fat-pointer typedefs
if sliceTypes.len > 0:
+25 -25
View File
@@ -180,7 +180,9 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
let multiField = fieldTypes.len > 1
var payloadBase = dataLoad
if multiField:
let variantStructTy = makeNamed(variantName)
# Nested struct type Enum_Variant_Payload (avoids clash with tag Enum_Variant)
let nestedName = enumName & "_" & variantName & "_Payload"
let variantStructTy = makeNamed(nestedName)
let variantPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: variantName,
typ: makePointer(variantStructTy), loc: loc)
payloadBase = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc)
@@ -208,10 +210,12 @@ proc matchPatternBindings(ctx: var LowerCtx, subject: HirNode, pattern: Pattern,
if entry.name == nf.name:
fieldTy = entry.typ
break
# Named payload fields live under data.VariantName.name
# Named payload fields live under data.Variant.name on Enum_Variant_Payload
let nestedName = enumName & "_" & variantName & "_Payload"
let variantStructTy = makeNamed(nestedName)
let variantPtr = HirNode(kind: hFieldPtr, fieldPtrBase: dataLoad, fieldName: variantName,
typ: makePointer(makeNamed(variantName)), loc: loc)
let variantLoad = HirNode(kind: hLoad, loadPtr: variantPtr, typ: makeNamed(variantName), loc: loc)
typ: makePointer(variantStructTy), loc: loc)
let variantLoad = HirNode(kind: hLoad, loadPtr: variantPtr, typ: variantStructTy, loc: loc)
let fieldPtr = HirNode(kind: hFieldPtr, fieldPtrBase: variantLoad, fieldName: nf.name,
typ: makePointer(fieldTy), loc: loc)
let fieldLoad = HirNode(kind: hLoad, loadPtr: fieldPtr, typ: fieldTy, loc: loc)
@@ -2217,33 +2221,29 @@ proc lowerModule*(module: Module, sema: Sema): HirModule =
for v in decl.declEnumVariants:
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)
# Full resolve — supports tuples, pointers, named types, etc.
fields.add(if f != nil: ctx.resolveTypeExpr(f) else: makeUnknown())
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)
let fType = if nf.ftype != nil: ctx.resolveTypeExpr(nf.ftype) else: makeUnknown()
namedFields.add((nf.name, fType))
variants.add(HirEnumVariant(name: v.name, fields: fields, namedFields: namedFields))
# Multi-field / named-field variants get a named nested struct type
# Enum_Variant_Payload (suffix avoids clash with tag constant Enum_Variant).
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 = decl.declEnumName & "_" & v.name & "_Payload"
structs.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 = decl.declEnumName & "_" & v.name & "_Payload"
structs.add((nestedName, nestedFields))
enums.add((decl.declEnumName, variants))
of dkConst:
let value = ctx.lowerExpr(decl.declConstValue)
+107 -111
View File
@@ -500,20 +500,12 @@ proc emitEnumDef(be: var LirCBackend, name: string, variants: seq[HirEnumVariant
# Single positional field — flat (compat: data.Variant_0)
be.emitLine(&"{typeToCStr(v.fields[0])} {v.name}_0;")
elif v.fields.len > 1:
# Multi positional — nested struct so fields don't share union storage
be.emitLine(&"struct {{")
be.indent += 1
for i, f in v.fields:
be.emitLine(&"{typeToCStr(f)} {v.name}_{i};")
be.indent -= 1
be.emitLine(&"}} {v.name};")
# Multi positional — named nested struct Enum_Variant_Payload
let nestedName = name & "_" & v.name & "_Payload"
be.emitLine(&"{nestedName} {v.name};")
elif v.namedFields.len > 0:
be.emitLine(&"struct {{")
be.indent += 1
for nf in v.namedFields:
be.emitLine(&"{typeToCStr(nf.typ)} {nf.name};")
be.indent -= 1
be.emitLine(&"}} {v.name};")
let nestedName = name & "_" & v.name & "_Payload"
be.emitLine(&"{nestedName} {v.name};")
be.indent -= 1
be.emitLine(&"}} {name}_Data;")
be.emitLine("")
@@ -648,104 +640,8 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
for e in module.enums:
localTypeNames.incl(e.name)
# Collect slice types used in struct fields and enum payloads.
var sliceTypes: seq[tuple[name: string, elem: string]] = @[]
var sliceNames: HashSet[string]
proc registerSlice(t: Type) =
if t == nil or t.kind != tkSlice: return
let name = typeToCStr(t)
if sliceNames.contains(name): return
sliceNames.incl(name)
let elem = if t.inner.len > 0: typeToCStr(t.inner[0]) else: "void"
sliceTypes.add((name, elem))
for s in module.structs:
for f in s.fields:
registerSlice(f.typ)
for e in module.enums:
for v in e.variants:
for ft in v.fields:
registerSlice(ft)
for nf in v.namedFields:
registerSlice(nf.typ)
# Build dependency graph among structs, enums, and slice types.
# Edge A -> B means "A depends on B, so B must be emitted before A".
var deps: Table[string, seq[string]]
for s in module.structs:
deps[s.name] = @[]
for e in module.enums:
deps[e.name] = @[]
for st in sliceTypes:
deps[st.name] = @[]
proc addDeps(node: string, t: Type) =
for dep in collectValueDeps(t):
if dep == node: continue
if localTypeNames.contains(dep) or sliceNames.contains(dep):
if dep notin deps[node]:
deps[node].add(dep)
for s in module.structs:
for f in s.fields:
addDeps(s.name, f.typ)
for e in module.enums:
for v in e.variants:
for ft in v.fields:
addDeps(e.name, ft)
for nf in v.namedFields:
addDeps(e.name, nf.typ)
# Topological sort (Kahn's algorithm).
var inDegree: Table[string, int]
var dependents: Table[string, seq[string]]
for node in deps.keys:
inDegree[node] = 0
for node, nodeDeps in deps:
for d in nodeDeps:
if not inDegree.hasKey(d): inDegree[d] = 0
inDegree[node] += 1
dependents.mgetOrPut(d, @[]).add(node)
var queue: seq[string] = @[]
for node, deg in inDegree:
if deg == 0:
queue.add(node)
var sorted: seq[string] = @[]
while queue.len > 0:
let node = queue.pop()
sorted.add(node)
for depNode in dependents.getOrDefault(node):
inDegree[depNode] -= 1
if inDegree[depNode] == 0:
queue.add(depNode)
if sorted.len < deps.len:
# Cycle detected; fall back to a safe deterministic order.
sorted = @[]
for s in module.structs: sorted.add(s.name)
for e in module.enums: sorted.add(e.name)
for st in sliceTypes: sorted.add(st.name)
# Map type names back to their definitions.
var structMap: Table[string, seq[tuple[name: string, typ: Type]]]
for s in module.structs: structMap[s.name] = s.fields
var enumMap: Table[string, seq[HirEnumVariant]]
for e in module.enums: enumMap[e.name] = e.variants
var sliceMap: Table[string, string]
for st in sliceTypes: sliceMap[st.name] = st.elem
# Emit type definitions in dependency order.
for name in sorted:
if structMap.hasKey(name):
be.emitStructDef(name, structMap[name])
elif enumMap.hasKey(name):
be.emitEnumDef(name, enumMap[name])
elif sliceMap.hasKey(name):
be.emitSliceTypeDef(name, sliceMap[name])
# Collect and emit tuple typedefs used in the module (and nested tuples first).
# Emit tuple typedefs early — enums/structs may embed them by value
# (e.g. Box::Val((int,int)) → Tuple_int_int Val_0 in the union).
var tupleTypes: seq[Type] = @[]
var tupleNames: HashSet[string]
proc registerTuple(t: Type) =
@@ -840,6 +736,106 @@ proc emitModule*(be: var LirCBackend, builder: LirBuilder, module: HirModule): s
for tt in tupleTypes:
be.emitTupleDef(tt)
# Collect slice types used in struct fields and enum payloads.
var sliceTypes: seq[tuple[name: string, elem: string]] = @[]
var sliceNames: HashSet[string]
proc registerSlice(t: Type) =
if t == nil or t.kind != tkSlice: return
let name = typeToCStr(t)
if sliceNames.contains(name): return
sliceNames.incl(name)
let elem = if t.inner.len > 0: typeToCStr(t.inner[0]) else: "void"
sliceTypes.add((name, elem))
for s in module.structs:
for f in s.fields:
registerSlice(f.typ)
for e in module.enums:
for v in e.variants:
for ft in v.fields:
registerSlice(ft)
for nf in v.namedFields:
registerSlice(nf.typ)
# Build dependency graph among structs, enums, and slice types.
# Edge A -> B means "A depends on B, so B must be emitted before A".
var deps: Table[string, seq[string]]
for s in module.structs:
deps[s.name] = @[]
for e in module.enums:
deps[e.name] = @[]
for st in sliceTypes:
deps[st.name] = @[]
proc addDeps(node: string, t: Type) =
for dep in collectValueDeps(t):
if dep == node: continue
if localTypeNames.contains(dep) or sliceNames.contains(dep):
if dep notin deps[node]:
deps[node].add(dep)
for s in module.structs:
for f in s.fields:
addDeps(s.name, f.typ)
for e in module.enums:
for v in e.variants:
for ft in v.fields:
addDeps(e.name, ft)
for nf in v.namedFields:
addDeps(e.name, nf.typ)
# Multi-field / named-field nested struct must be defined before the enum
if v.fields.len > 1 or v.namedFields.len > 0:
addDeps(e.name, makeNamed(e.name & "_" & v.name & "_Payload"))
# Topological sort (Kahn's algorithm).
var inDegree: Table[string, int]
var dependents: Table[string, seq[string]]
for node in deps.keys:
inDegree[node] = 0
for node, nodeDeps in deps:
for d in nodeDeps:
if not inDegree.hasKey(d): inDegree[d] = 0
inDegree[node] += 1
dependents.mgetOrPut(d, @[]).add(node)
var queue: seq[string] = @[]
for node, deg in inDegree:
if deg == 0:
queue.add(node)
var sorted: seq[string] = @[]
while queue.len > 0:
let node = queue.pop()
sorted.add(node)
for depNode in dependents.getOrDefault(node):
inDegree[depNode] -= 1
if inDegree[depNode] == 0:
queue.add(depNode)
if sorted.len < deps.len:
# Cycle detected; fall back to a safe deterministic order.
sorted = @[]
for s in module.structs: sorted.add(s.name)
for e in module.enums: sorted.add(e.name)
for st in sliceTypes: sorted.add(st.name)
# Map type names back to their definitions.
var structMap: Table[string, seq[tuple[name: string, typ: Type]]]
for s in module.structs: structMap[s.name] = s.fields
var enumMap: Table[string, seq[HirEnumVariant]]
for e in module.enums: enumMap[e.name] = e.variants
var sliceMap: Table[string, string]
for st in sliceTypes: sliceMap[st.name] = st.elem
# Emit type definitions in dependency order.
for name in sorted:
if structMap.hasKey(name):
be.emitStructDef(name, structMap[name])
elif enumMap.hasKey(name):
be.emitEnumDef(name, enumMap[name])
elif sliceMap.hasKey(name):
be.emitSliceTypeDef(name, sliceMap[name])
# Fat function-pointer typedefs (BuxFn_*) — before forward decls that use them
var fatTypes: seq[Type] = @[]
var fatNames: HashSet[string]
+30 -3
View File
@@ -1330,12 +1330,18 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
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.
# Multi-field / named-field variant: data.Variant → Enum_Variant_Payload
# (suffix avoids clashing with tag constant Enum_Variant)
if variant.fields.len > 1 and variant.name == expr.exprFieldName:
return makeNamed(enumName & "_" & variant.name & "_Payload")
if variant.namedFields.len > 0 and variant.name == expr.exprFieldName:
return makeNamed(enumName & "_" & variant.name & "_Payload")
# Single positional fields: Ok_0, Ok_1, etc. (flat on the union)
for i, f in variant.fields:
let fieldName = variant.name & "_" & $i
if fieldName == expr.exprFieldName:
return sema.resolveType(f)
# Check named fields
# Named fields nested under data.Variant.name
for nf in variant.namedFields:
if nf.name == expr.exprFieldName:
return sema.resolveType(nf.ftype)
@@ -1378,7 +1384,28 @@ proc checkExpr(sema: var Sema, expr: Expr, scope: Scope): Type =
else:
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
else:
sema.emitError(expr.loc, &"cannot access field on type {obj.toString}")
# Synthetic nested multi-field type Enum_Variant_Payload — generated for
# multi-field / named-field algebraic variants (not a user-declared type).
var foundNested = false
for (_, gsym) in sema.globalScope.table.pairs:
if gsym.decl == nil or gsym.decl.kind != dkEnum: continue
let ename = gsym.decl.declEnumName
for variant in gsym.decl.declEnumVariants:
let nestedName = ename & "_" & variant.name & "_Payload"
if nestedName != objType.name: continue
foundNested = true
for i, f in variant.fields:
let fieldName = variant.name & "_" & $i
if fieldName == expr.exprFieldName:
return sema.resolveType(f)
for nf in variant.namedFields:
if nf.name == expr.exprFieldName:
return sema.resolveType(nf.ftype)
sema.emitError(expr.loc, &"nested variant type '{objType.name}' has no field '{expr.exprFieldName}'")
return makeUnknown()
if not foundNested:
sema.emitError(expr.loc, &"undeclared type '{objType.name}'")
return makeUnknown()
elif objType.kind == tkDynRef:
# Trait object: methods come from the interface
let ifaceName = objType.name
+24
View File
@@ -345,10 +345,21 @@ enum Result {
Err(String)
}
enum Pair {
Two(int, int), // multi-field → nested payload
One(int), // single-field → flat data.One_0
None
}
func Main() -> int {
let r: Result = Result { tag: Result_Ok };
r.data.Ok_0 = 42;
// Multi-field construction: data.Variant.Variant_i
var p: Pair = Pair { tag: Pair_Two };
p.data.Two.Two_0 = 3;
p.data.Two.Two_1 = 4;
if r.tag == Result_Ok {
PrintInt(r.data.Ok_0);
}
@@ -356,6 +367,10 @@ func Main() -> int {
}
```
Layout notes:
- Single positional field: flat union member `data.Variant_0`
- Multi-field: nested payload `data.Variant.Variant_0` / `data.Variant.Variant_1` (C type `Enum_Variant_Payload`)
---
## Pattern Matching
@@ -383,6 +398,7 @@ Supported patterns:
- Identifier catch-all: `name` (binds whole subject)
- Range: `1..9`, `1..=9`
- Enum tags + **payload bindings**: `Option::Some(value)`, `Pair::Two(a, b)`
- **Nested**: `Box::Val((a, b))`, `Shape::Dot(Point { x, y })`
- **Tuple patterns**: `(a, b)` → binds `subject._0`, `subject._1`
- **Struct patterns**: `Point { x: px, y: py }` or shorthand `Point { x, y }`
- Guard patterns: parsed; full lowering still evolving
@@ -396,6 +412,14 @@ match p {
Point { x, y } => x * 10 + y,
_ => -1
}
match bx {
Box::Val((a, c)) => a + c,
Box::Empty => 0
}
match sh {
Shape::Dot(Point { x, y }) => x * 10 + y,
Shape::Empty => -1
}
// Multi-statement arm bodies (block expression; last expr is the value)
match n {
+16 -4
View File
@@ -285,9 +285,21 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
---
## Сесия 17 (deeper nested patterns + multi-field enum layout)
1. **Multi-field payload type:** nested struct `Enum_Variant_Payload` (suffix avoids clash with tag `Enum_Variant`)
2. Bootstrap: full `resolveTypeExpr` for enum field types (tuples/pointers); sema synthetic field lookup on payload types; `data.Two.Two_0` construction
3. LIR C backend: emit tuple typedefs **before** enums that embed them; topo deps for `*_Payload`
4. Selfhost: enum variants parse full types (`parserParseType`) — fixes `Val((int,int))`; store `fieldTypeName*`; structs before enums (e.g. `Dot(Point)`)
5. Patterns: `Pair::Two(a, b)`, `Box::Val((a, c))`, `Shape::Dot(Point { x, y })` + block arms
6. Example: `examples/nested_patterns.bux`
7. Verified: bootstrap + **buxc2** + selfhost-loop IDENTICAL ✓
---
## Следващи стъпки
1. Deeper nested patterns (`Some((a, b))` with multi-field enum layout ergonomics)
2. LSP: wire hover types from real sema
3. Generic type inference for `Iter_Map` without explicit `<T,U>`
4. Match arm guards (`p if cond => …`)
1. LSP: wire hover types from real sema
2. Generic type inference for `Iter_Map` without explicit `<T,U>`
3. Match arm guards (`p if cond => …`)
4. Pattern binding name shadowing (C locals are function-scoped)
+79
View File
@@ -0,0 +1,79 @@
import Std::Io::{PrintLine, PrintInt};
import Std::Test::{Test_AssertEqInt, Test_Pass};
enum Pair {
Two(int, int),
One(int),
None
}
enum Box {
Val((int, int)),
Empty
}
struct Point {
x: int,
y: int,
}
enum Shape {
Dot(Point),
Empty
}
func SumPair(p: Pair) -> int {
match p {
Pair::Two(a, b) => a + b,
Pair::One(x) => x,
Pair::None => 0
}
}
func SumBox(bx: Box) -> int {
match bx {
Box::Val((a, c)) => a + c,
Box::Empty => 0
}
}
func SumShape(sh: Shape) -> int {
match sh {
Shape::Dot(Point { x, y }) => x * 10 + y,
Shape::Empty => -1
}
}
func Main() -> int {
var p: Pair = Pair { tag: Pair_Two };
p.data.Two.Two_0 = 3;
p.data.Two.Two_1 = 4;
Test_AssertEqInt(SumPair(p), 7);
var p1: Pair = Pair { tag: Pair_One };
p1.data.One_0 = 42;
Test_AssertEqInt(SumPair(p1), 42);
var bx: Box = Box { tag: Box_Val };
bx.data.Val_0 = (10, 20);
Test_AssertEqInt(SumBox(bx), 30);
let pt: Point = Point { x: 2, y: 5 };
var sh: Shape = Shape { tag: Shape_Dot };
sh.data.Dot_0 = pt;
Test_AssertEqInt(SumShape(sh), 25);
let z: int = match bx {
Box::Val((a, c)) => {
let s: int = a + c;
s * 2
},
Box::Empty => 0
};
Test_AssertEqInt(z, 60);
PrintInt(SumPair(p));
PrintLine("");
Test_Pass("nested_patterns");
return 0;
}
+45 -39
View File
@@ -1309,6 +1309,42 @@ func CBackend_Generate(mod: *HirModule) -> String {
}
StringBuilder_Append(&cbe.sb, "\n");
// Tuple typedefs before enums/structs that embed them by value
StringBuilder_Append(&cbe.sb, "/* Tuple types */\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int {\n int _0;\n int _1;\n} Tuple_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n");
// Struct definitions before enums (enums may embed structs by value, e.g. Shape::Dot(Point))
// Pass 1: emit structs with no value-typed struct fields (leaf structs)
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
if CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Pass 2: emit structs that contain value-typed struct fields
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
if !CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Enum definitions
var ei: int = 0;
while ei < mod.enumCount {
@@ -1364,22 +1400,28 @@ func CBackend_Generate(mod: *HirModule) -> String {
while vi < en.variantCount {
let ev: *HirEnumVariant = &en.variants[vi];
if ev.fieldCount > 0 {
// Prefer stored C type name (handles Tuple_*, named types)
var ft0: String = CBackend_TypeToC(ev.fieldType0);
if !String_Eq(ev.fieldTypeName0, "") { ft0 = ev.fieldTypeName0; }
var ft1: String = CBackend_TypeToC(ev.fieldType1);
if !String_Eq(ev.fieldTypeName1, "") { ft1 = ev.fieldTypeName1; }
if ev.fieldCount == 1 {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType0));
StringBuilder_Append(&cbe.sb, ft0);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.name);
StringBuilder_Append(&cbe.sb, "_0;\n");
} else {
// Nested anonymous struct (layout matches bootstrap data.Variant.Variant_i)
StringBuilder_Append(&cbe.sb, " struct {\n");
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType0));
StringBuilder_Append(&cbe.sb, ft0);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.fieldName0);
StringBuilder_Append(&cbe.sb, ";\n");
if ev.fieldCount > 1 {
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, CBackend_TypeToC(ev.fieldType1));
StringBuilder_Append(&cbe.sb, ft1);
StringBuilder_Append(&cbe.sb, " ");
StringBuilder_Append(&cbe.sb, ev.fieldName1);
StringBuilder_Append(&cbe.sb, ";\n");
@@ -1423,45 +1465,9 @@ func CBackend_Generate(mod: *HirModule) -> String {
StringBuilder_Append(&cbe.sb, "\n");
}
// Struct definitions (skip generics)
// Pass 1: emit structs with no value-typed struct fields (leaf structs)
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
if CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Pass 2: emit structs that contain value-typed struct fields
si = 0;
while si < mod.structCount {
if String_Eq(mod.structs[si].name, "") || CBE_StructHasGeneric(&mod.structs[si]) {
si = si + 1;
continue;
}
if !CBE_StructHasValueStructField(&mod.structs[si]) {
si = si + 1;
continue;
}
CBE_EmitStructDef(cbe, &mod.structs[si]);
si = si + 1;
}
// Fat function-pointer typedefs (BuxFn_*) — before forward decls
CBE_EmitFatFuncTypedefs(cbe, mod);
// Common tuple struct types
StringBuilder_Append(&cbe.sb, "/* Tuple types */\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int {\n int _0;\n int _1;\n} Tuple_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_int_int_int {\n int _0;\n int _1;\n int _2;\n} Tuple_int_int_int;\n");
StringBuilder_Append(&cbe.sb, "typedef struct Tuple_Empty {\n char _pad;\n} Tuple_Empty;\n\n");
// Env structs for capturing closures (no static instance — heap per value)
var ei2: int = 0;
while ei2 < mod.funcCount {
+2
View File
@@ -126,8 +126,10 @@ struct HirEnumVariant {
fieldCount: int;
fieldType0: int;
fieldName0: String;
fieldTypeName0: String; // C type name (e.g. "int", "Tuple_int_int")
fieldType1: int;
fieldName1: String;
fieldTypeName1: String;
}
// ---------------------------------------------------------------------------
+7 -2
View File
@@ -782,9 +782,12 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
dataLoad.child1 = dataPtr;
dataLoad.typeName = String_Concat(enumName, "_Data");
// Multi-field: nested struct data.Variant; single-field: flat data.Variant_0
// Multi-field: nested struct data.Variant (anonymous or Enum_Variant_Payload);
// single-field: flat data.Variant_0
var payloadBase: *HirNode = dataLoad;
if fieldCount > 1 {
let nestedName: String = String_Concat(String_Concat(enumName, "_"),
String_Concat(variantName, "_Payload"));
let vPtr: *HirNode = bux_alloc(sizeof(HirNode)) as *HirNode;
vPtr.kind = hFieldPtr;
vPtr.line = line;
@@ -796,7 +799,7 @@ func Lcx_PatternBindings(ctx: *LowerCtx, subject: *HirNode, pat: *Pattern,
vLoad.line = line;
vLoad.column = col;
vLoad.child1 = vPtr;
vLoad.typeName = variantName;
vLoad.typeName = nestedName;
payloadBase = vLoad;
}
@@ -3977,10 +3980,12 @@ func HirLower_LowerModule(mod: *Module, sema: *Sema) -> *HirModule {
// Positional field names: Variant_0, Variant_1 (matches data.Variant_i / nested struct)
hm.enums[ei].variants[vi].fieldName0 = String_Concat(v.name, "_0");
hm.enums[ei].variants[vi].fieldType0 = Lcx_ResolveTypeKindFromName(v.fieldTypeName0);
hm.enums[ei].variants[vi].fieldTypeName0 = v.fieldTypeName0;
}
if v.fieldCount > 1 {
hm.enums[ei].variants[vi].fieldName1 = String_Concat(v.name, "_1");
hm.enums[ei].variants[vi].fieldType1 = Lcx_ResolveTypeKindFromName(v.fieldTypeName1);
hm.enums[ei].variants[vi].fieldTypeName1 = v.fieldTypeName1;
}
}
vi = vi + 1;
+27 -5
View File
@@ -148,6 +148,28 @@ func parserExpectIdentOrKeyword(p: *Parser, msg: String) -> LexToken {
// Type parsing
// ---------------------------------------------------------------------------
// C-friendly type name from a TypeExpr (named, tuple, pointer, …).
func parserTypeExprCName(te: *TypeExpr) -> String {
if te == null as *TypeExpr { return "int"; }
if te.kind == tekTuple {
if !String_Eq(te.typeName, "") { return te.typeName; }
return "Tuple_Empty";
}
if te.kind == tekPointer || te.kind == tekRef || te.kind == tekMutRef {
if te.pointerPointee != null as *TypeExpr {
return String_Concat(parserTypeExprCName(te.pointerPointee), "*");
}
return "void*";
}
if !String_Eq(te.typeName, "") {
if String_Eq(te.typeName, "String") || String_Eq(te.typeName, "str") {
return "const char*";
}
return te.typeName;
}
return "int";
}
func parserParseType(p: *Parser) -> *TypeExpr {
let line: uint32 = parserCurToken(p).line;
let col: uint32 = parserCurToken(p).column;
@@ -1953,14 +1975,14 @@ func parserParseEnumDecl(p: *Parser, isPublic: bool) -> *Decl {
v.fieldTypeName0 = "";
v.fieldTypeName1 = "";
// Optional (Type, Type) data
// Optional payload: (Type, Type) or (Tuple, ...) — full type expressions
if parserMatch(p, tkLParen) {
let t0: LexToken = parserExpect(p, tkIdent, "expected data type");
v.fieldTypeName0 = t0.text;
let te0: *TypeExpr = parserParseType(p);
v.fieldTypeName0 = parserTypeExprCName(te0);
v.fieldCount = 1;
if parserMatch(p, tkComma) {
let t1: LexToken = parserExpect(p, tkIdent, "expected data type");
v.fieldTypeName1 = t1.text;
let te1: *TypeExpr = parserParseType(p);
v.fieldTypeName1 = parserTypeExprCName(te1);
v.fieldCount = 2;
}
discard parserExpect(p, tkRParen, "expected ')'");