fix: reject direct assignment to a tagged-union variant member
A tagged union (enum-with-payload) is laid out { tag, payload }, but a
direct member write `s.rect = payload` lowered to a payload-only store
(union_gep into field 1) with no tag store — the discriminant went stale,
so a later match/== took the wrong arm with no diagnostic (issue 0136).
The read path already distinguishes tagged unions (enum_payload/enum_tag);
the write path treated them like plain unions.
A variant is set via construction (`s = .variant(payload)`, which writes
both tag and payload). A direct member write can't safely set the tag (the
active variant isn't known at the write site), so it is now rejected with a
diagnostic pointing to construction. A new diagTaggedUnionVariantWrite guard
— reusing the shared fieldLvalueResolve matcher, applied at both store sites
(lowerAssignment, lowerMultiAssign) — fires only for a whole-variant write
on a tagged union. Plain `union` writes and nested sub-field writes
(`s.rect.w = ...`) are unaffected.
Resolves issue 0136. Tests: examples/0185 (rejected), 0186 (nested write +
construction still work). specs.md / readme.md updated.
This commit is contained in:
8
specs.md
8
specs.md
@@ -394,6 +394,14 @@ s = Shape.rect(42); // explicit prefix
|
||||
r := s.circle; // load payload as f32 (undefined behavior if wrong variant active)
|
||||
```
|
||||
|
||||
#### Setting a Variant
|
||||
A variant is set by construction — `s = .rect(payload)` — which writes both the
|
||||
tag and the payload together. Direct member assignment to a variant
|
||||
(`s.rect = payload`) is **rejected at compile time**: it would store the payload
|
||||
but not the tag, leaving the two desynced so a later `match` takes the wrong arm.
|
||||
Mutating a sub-field of the *active* variant's payload in place is allowed
|
||||
(`s.rect.w = 9.0`).
|
||||
|
||||
#### Pattern Matching
|
||||
```sx
|
||||
if s == {
|
||||
|
||||
Reference in New Issue
Block a user