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
+30
View File
@@ -1,3 +1,4 @@
import "@std/debug"
import "@std/mem"
import "@std/arraylist"
import "@std/strconv"
@@ -2097,6 +2098,35 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
if (trap_type(state, id)) return ir.no_value
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
}
.union_constant: {
if (trap_type(state, id)) return ir.no_value
bytes :: ast.union_constant(tree, id).value
text :: ast.literal_text(tree, bytes)
count :: ir.storage_size(state.module, .record, shape)
byte_shape :: try ir.add_array(state.module, .u8, ir.no_array, count, false)
scratch :: state.arguments.items.len
defer state.arguments.items = state.arguments.items.ptr[..scratch]
cursor := usize(1)
while (cursor < text.len - 1) {
byte :: ast.string_next(text, &cursor) catch |_| {
trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span)
return ir.no_value
}
element :: try constant(state, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = u64(byte) } }, span)
try arraylist.append(&state.arguments, element)
}
debug.assert(state.arguments.items.len - scratch == count)
block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)]
start :: block.arguments.items.len
for (state.arguments.items[scratch..]) |element| try arraylist.append(&block.arguments, element)
payload :: try emit(state, ir.Instruction{
op = .array_literal, type = .array, array_type = byte_shape,
arguments_start = start, arguments_count = count, span = span,
})
return try emit(state, ir.Instruction{
op = .union_literal, type = .record, array_type = shape, left = payload, span = span,
})
}
.invalid: {
invalid :: ast.invalid(tree, id)
trap(state, invalid.diagnostic, invalid.span)