feat: materialize address-free compile-time untagged unions

Snapshot full union storage rather than choosing an overlapping field. Preserve structural union source syntax independently of nominal identity so direct literal arguments materialize and share normalized specialization keys. Reject uninitialized bytes and address-bearing member layouts.
This commit is contained in:
2026-10-30 15:54:32 +01:00
parent c8890b4355
commit cc39c96fc0
9 changed files with 420 additions and 25 deletions
+23 -1
View File
@@ -60,6 +60,8 @@ RecordField :: struct {
RecordType :: struct {
declaration ast.NodeId
# union syntax remains available even when structural checking erases nominal identity.
source ast.NodeId = ast.NodeId(maxval!(u32))
fields []RecordField
is_union bool = false
tag_type Type = Type{}
@@ -1291,6 +1293,14 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.union_constant: {
value :: ast.union_constant(tree, id)
state.cells[usize(id)].fixed = true
state.cells[usize(id)].type = try check_annotation(state, value.type, false)
try collect_expression(state, value.value)
try depend(state, value.type, id)
try depend(state, value.value, id)
}
.try_expr: {
operand :: ast.type_operand(tree, id)
try collect_expression(state, operand)
@@ -2156,6 +2166,18 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
value :: ast.typed_constant(tree, id)
try compatible(state, value.value, state.cells[usize(id)].type)
}
.union_constant: {
value :: ast.union_constant(tree, id)
type :: state.cells[usize(id)].type
if (type.kind == .invalid) return
if (type.kind != .record or ast.expr_kind(tree, value.value) != .literal_string) {
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
return
}
descriptor :: state.records.items[usize(type.record_type)]
if (!descriptor.is_union or descriptor.tag_type.kind != .none)
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
}
.try_expr, .catch_expr: try validate_fallible_expression(state, id)
.unary: {
expression :: ast.unary_expr(tree, id)
@@ -4195,7 +4217,7 @@ check_union proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.Allo
empty :: try mem.alloc(RecordField, state.records.allocator, 0)
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
arraylist.append(&state.records, RecordType{
declaration = declaration, fields = empty, is_union = true,
declaration = declaration, source = id, fields = empty, is_union = true,
}) catch |err| {
mem.free(state.records.allocator, empty)
return err