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:
+20
-19
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user