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
+207 -5
View File
@@ -5736,13 +5736,177 @@ CASES = {
main proc() i16 { return $(make()) catch |err| err.pointer^ }
""", b"", b"compile-time result cannot escape evaluation",
),
"comptime_untagged_union_remains_an_escape_trap": (
"comptime_untagged_union_preserves_scalar_and_ieee_bits": (
"""
Storage :: union { number i16, bits u16 }
make proc() Storage { return Storage { number = 42 } }
main proc() i16 { return $(make()).number }
Storage :: union { real f64, bits u64 }
make proc(bits u64) Storage { return Storage { bits = bits } }
main proc() bool {
one :: $(make(4607182418800017408))
zero :: $(make(9223372036854775808))
nan :: $(make(9221120237041090626))
return (
one.real == 1.0 and zero.bits == 9223372036854775808
and nan.bits == 9221120237041090626
)
}
""", b"true\n", None,
),
"comptime_untagged_union_owns_padding_tail_bytes_and_copies": (
"""
Pair :: struct { small u8, large u64 }
Storage :: union { small u8, rows [2]mut Pair, words [4]mut u64, bytes [32]u8 }
make proc(prefix u64) Storage {
value := Storage { words = [prefix, 40, maxval!(u64), 2] }
value.small = 9
return value
}
Token proc($value Storage) type { return struct {} }
main proc() [8]u64 {
if (Token(make(maxval!(u64))) == Token(make(9))) return [0, 0, 0, 0, 0, 0, 0, 0]
value := $(make(maxval!(u64)))
saved :: value
value.small = 0
value.rows[0].large = 99
other :: $(make(maxval!(u64)))
return [u64(saved.small), u64(saved.bytes[1]), u64(saved.bytes[7]),
saved.rows[0].large, u64(saved.bytes[17]), u64(saved.bytes[23]),
other.rows[1].large, value.rows[0].large]
}
""", b"[9, 255, 255, 40, 255, 255, 2, 99]\n", None,
),
"comptime_untagged_union_cache_identity_and_generated_defaults": (
"""
Produced proc() type { return union { small u16, words [2]mut u16, bits u32 } }
Storage :: alias Produced()
make proc(tail u16) Storage {
value := Storage { words = [0, tail] }
value.small = 40
return value
}
Token proc($value Storage) type { return struct {} }
Fixed proc($value Storage) type { return struct { values [usize(value.words[1])]i16 } }
score proc($value Storage) u16 { return value.small + value.words[1] }
Generated proc() type {
return struct_type!(.auto, ["value"], [Storage], [make(2)])
}
main proc() u16 {
if (Token(make(2)) != Token(Storage { bits = 131112 })) return 0
if (Token(make(2)) != Token(Storage { words = [40, 2] })) return 0
if (Token(make(2)) == Token(make(3))) return 0
first Fixed(make(2)) :: { values = [20, 20] }
same Fixed(Storage { words = [40, 2] }) :: first
generated Generated() :: {}
if (generated.value.words[1] != 2 or same.values[1] != 20) return 0
return score(make(2))
}
""", b"42\n", None,
),
"comptime_untagged_union_preserves_zero_size_and_nominal_members": (
"""
Number :: distinct u16
Kind :: enum(u16) { answer = 42 }
Storage :: union { number Number, kind Kind }
Empty :: union { bytes [0]u8, record struct {} }
make proc() Storage { return Storage { kind = .answer } }
empty proc() Empty { return Empty { bytes = [] } }
exact proc(value Storage) Number { return value.number }
main proc() u16 {
zero :: $(empty())
if (sizeof!(Empty) != 0 or zero.bytes.len != 0) return 0
return u16(exact($(make())))
}
""", b"42\n", None,
),
"comptime_untagged_union_nested_optional_tagged_and_fallible_channels": (
"""
Storage :: union { small u16, words [2]u16 }
Choice :: union(enum) { data Storage, empty void }
Failure :: struct { value Storage }
Packet :: struct {
maybe [2]?Storage
choice Choice
success Storage ! Failure
failure Storage ! Failure
}
make proc() Packet {
value Storage :: { words = [40, 2] }
return {
maybe = [value, null], choice = .data{value},
success = value, failure = Failure { value = value },
}
}
score proc(value Storage) u16 { return value.words[0] + value.words[1] }
main proc() [4]u16 {
packet :: $(make())
if (packet.maybe[1] != null) return [0, 0, 0, 0]
good :: packet.success catch Storage { small = 0 }
bad :: packet.failure catch |err| err.value
return [score(packet.maybe[0]?), score(packet.choice.data), score(good), score(bad)]
}
""", b"[42, 42, 42, 42]\n", None,
),
"comptime_untagged_union_rejects_source_pointer": (
"""
Storage :: union { bits u64, pointer @i16 }
make proc() Storage {
value i16 :: 42
return Storage { pointer = &value }
}
main proc() u64 { return $(make()).bits }
""", b"", b"compile-time result cannot escape evaluation",
),
"comptime_untagged_union_rejects_inactive_pointer_layout": (
"""
Storage :: union { bits u64, pointer ?@i16 }
make proc() Storage { return Storage { bits = 0 } }
main proc() u64 { return $(make()).bits }
""", b"", b"compile-time result cannot escape evaluation",
),
"comptime_untagged_union_rejects_inactive_callback_layout": (
"""
Storage :: union { bits u64, callback @proc(value i16) i16 }
make proc() Storage { return Storage { bits = 42 } }
main proc() u64 { return $(make()).bits }
""", b"", b"compile-time result cannot escape evaluation",
),
"comptime_untagged_union_rejects_inactive_string_layout": (
"""
Storage :: union { bits [2]u64, text string }
make proc() Storage { return Storage { bits = [0, 0] } }
main proc() u64 { return $(make()).bits[0] }
""", b"", b"compile-time result cannot escape evaluation",
),
"comptime_untagged_union_rejects_nested_inactive_slice_layout": (
"""
Storage :: union { bits [2]u64, rows [1]struct { bytes []u8 } }
make proc() Storage { return Storage { bits = [0, 0] } }
main proc() u64 { return $(make()).bits[0] }
""", b"", b"compile-time result cannot escape evaluation",
),
"comptime_untagged_union_rejects_uninitialized_tail_bytes": (
"""
Storage :: union { small u8, words [2]mut u64 }
make proc() Storage {
value Storage := undefined
value.small = 42
return value
}
main proc() u8 { return $(make()).small }
""", b"", b"read before initialization",
),
"comptime_untagged_union_rejects_uninitialized_padding_bytes": (
"""
Pair :: struct { small u8, large u64 }
Storage :: union { pair Pair, words [2]u64 }
make proc() Storage {
value Storage := undefined
value.pair.small = 1
value.pair.large = 42
return value
}
main proc() u64 { return $(make()).pair.large }
""", b"", b"read before initialization",
),
"comptime_float_bits_survive_aggregate_materialization": (
"""
Floats :: struct { zero f64, nan f64 }
@@ -9127,7 +9291,45 @@ def check_specialization_packages(temporary, executable):
}
""").strip() + "\n")
check(app, executable, (b"[40, 2, 40]\n", None))
return 24
sizing.write_text(textwrap.dedent("""
Storage :: union { small u16, words [2]mut u16 }
make proc() Storage {
value := Storage { words = [0, 2] }
value.small = 40
return value
}
Token proc($value Storage) type { return struct {} }
Generated proc() type {
return struct_type!(.auto, ["value"], [Storage], [make()])
}
""").strip() + "\n")
source.write_text(textwrap.dedent("""
lib :: import "../library"
main proc() u16 {
direct :: $(lib.make())
value lib.Generated() :: {}
if (lib.Token(lib.make()) != lib.Token(lib.Storage { words = [40, 2] })) return 0
return direct.small + value.value.words[1]
}
""").strip() + "\n")
check(app, executable, (b"42\n", None))
sizing.write_text(textwrap.dedent("""
Storage :: union { small u8, words [2]u64 }
make proc() Storage {
value u8 :: 255
overflow :: value + 1
return Storage { words = [u64(overflow), 2] }
}
""").strip() + "\n")
source.write_text(textwrap.dedent("""
lib :: import "../library"
main proc() u8 { return $(lib.make()).small }
""").strip() + "\n")
check(app, executable, (b"", b"integer arithmetic overflow"))
interpreted = run(str(HONEY), "run", str(app))
assert interpreted.stderr.startswith(f"{sizing.resolve()}:4:".encode()), outcome(interpreted)
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
return 26
def check_dependency_provenance(temporary, executable):