Unions: a union return gets a discriminant, so the match E0723 forces can reach code #217

Manually merged
mdoorn merged 1 commit from feat/217-unions-a-union-return-gets-a-discriminant-so-t into master 2026-09-03 10:24:22 +02:00
Owner

Unions: a union return gets a discriminant, so the match E0723 forces can reach code

Along the way:

  • unions: a union return gets a discriminant, so the match E0723 forces can reach code

Touches 21 files: src/rust/crates/ck_codegen/src/user_fn.rs, src/rust/crates/ck_codegen/src/value.rs, src/rust/crates/ck_driver/tests/capability.rs, src/rust/crates/ck_lower/src/layout/exprs.rs, src/rust/crates/ck_lower/src/layout/mod.rs, src/rust/crates/ck_lower/src/layout/tuple.rs and 15 more.

Unions: a union return gets a discriminant, so the match E0723 forces can reach code Along the way: - unions: a union return gets a discriminant, so the match E0723 forces can reach code Touches 21 files: `src/rust/crates/ck_codegen/src/user_fn.rs`, `src/rust/crates/ck_codegen/src/value.rs`, `src/rust/crates/ck_driver/tests/capability.rs`, `src/rust/crates/ck_lower/src/layout/exprs.rs`, `src/rust/crates/ck_lower/src/layout/mod.rs`, `src/rust/crates/ck_lower/src/layout/tuple.rs` and 15 more.
mdoorn approved these changes 2026-09-03 09:17:15 +02:00
mdoorn left a comment

Happy with this. I ran the suite against the branch and it is green, src/rust/crates/ck_lower/src/lower/match_lower.rs included.

Happy with this. I ran the suite against the branch and it is green, `src/rust/crates/ck_lower/src/lower/match_lower.rs` included.
@ -297,0 +300,4 @@
/// The extra test blocks a **union-member** arm needs: one per member tag
/// past the first, since the frame's tag word is compared to each in turn
/// (an asserted `data` union covers every one of its variants).
fn extra_test_blocks(&mut self, test: &ArmTest, span: Span) -> Vec<BlockId> {
Owner

extra_test_blocks reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?

`extra_test_blocks` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?
Author
Owner

Split. The decision in extra_test_blocks is a pure function now, with its own tests in src/rust/crates/ck_lower/src/lower/match_lower.rs.

Split. The decision in `extra_test_blocks` is a pure function now, with its own tests in `src/rust/crates/ck_lower/src/lower/match_lower.rs`.
dferreira approved these changes 2026-09-03 10:19:22 +02:00
dferreira left a comment

Second read on src/rust/crates/ck_lower/src/lower/match_lower.rs. Nothing to add.

Second read on `src/rust/crates/ck_lower/src/lower/match_lower.rs`. Nothing to add.
`-> T or Refusal` parsed and typechecked, E0723 then made the caller `match`
it, and that match refused with `UnsupportedMatch`. The language pushed you
into a construct it could not lower: correct code with no path to an
executable. It was the single reason `fn main` in the sibling repo's `Main3.ck`
priced UNBOUNDED.

The reason was not the pattern. It was the VALUE. A `data` union is one
declaration, so its repr is settled by the declaration — all-bare is the P2.3
tag word, mixed is the driver-planned tagged record — and either way a value of
it is one word. An `or`-union is not a declaration: `Tally or Refusal` names a
record and a `data` union's variants in one type, and a `Tally` frame pointer
and a `Refusal` tag word are both one word and indistinguishable at runtime.
There was nothing for an arm to test. Worse, the ABI had been picking the FIRST
member with a layout row, so a `Refusal` was already flattening as a `Tally`
base — the shape did not fail loudly, it failed quietly, and only
`UnsupportedMatch` upstream kept anyone from observing it.

So the type gets a frame of its own, and it is the same frame LC5 already gives
a mixed union (`arenas.md` — tag word, then max of cases), applied
structurally rather than per declaration, exactly as `Ty::Tuple` is. The tag is
the member type's own `TypeId`, never its position in the member list: a nested
`if / else if` chain types its inner branch at a NARROWER union than the outer
one, and an intrinsic tag makes that widening a plain frame copy instead of a
runtime re-tagging.

- `ck_lower::layout::union` — the frame law. `Ty::Union` classifies as one word
  when every member classifies the same way (all-bare variants, or the ONE
  shared row a mixed union's variants point at — `Arc::ptr_eq`, since two
  records can plan identical layouts), and as the tagged frame when they do
  not. `union_tests.rs` pins the three-way discriminator plus the word-granular
  refusal.
- `ck_lower::lower::union_lower` — construction. A member is lifted by its own
  tag into a fresh frame, its words copied in if it is itself a frame; a
  narrower union widens by copying its words verbatim. A value that cannot be
  placed is the new `LowerError::UnionValue`, never a guessed tag.
- `match_test` / `match_lower` — destruction. A `TypeAssert` arm reads its
  asserted type back off the binder's checked type (the type stage seeded it
  there), covers every union member that type's leaves name, and tests the
  frame's tag word once per member — one block each, a miss falling to the
  next. A frame member binds the payload's ADDRESS with its layout, so
  `counted.files` is an ordinary record projection; every other member loads
  its words. An arm value is lifted into the match's union frame where the
  match is typed at one, which is where `if cond { T } else { E }` — the whole
  surface of a `-> T or E` body — builds the union.
- `layout::TypeShapes` / `Snapshots` — the boundary had two answers for one
  question. Deciding a TYPE's shape reads only the two settled snapshots;
  deciding a VALUE's additionally reads the expression arena. Codegen builds a
  signature from types with no body in hand and had been answering the type
  question a second way, which is how a union's parameter width came to
  disagree with the same union's argument width (a Cranelift verifier error the
  moment a union crossed a parameter). One trait pair, three implementors, one
  law.
- `raise_prop::dummy_return` now shapes a union-returning propagation frame
  like the record case it already handled.

Verified by reading the emitted machine code (`ckc disassemble`, no container):
the callee stores tag + payload and returns three words through the return
area; the caller loads the tag, tests it against each member, and either adds
the two record words back or hands the payload word to `code_of`. The
impossible fallthrough is still `ud2`.

- capability: 428 -> 432 cells. `union_return_match_binds_a_record_member`
  (runs: 42) and `union_return_match_takes_the_refusal_arm` (runs: 67) take the
  two sides of the discriminant, the second through a nested chain.
- BROKEN_CELL_COUNT 50 -> 52, both newly pinned and both the REMAINDER of this
  slice rather than debt it tolerated. `union_arm_of_a_whole_data_union_refuses`
  — an arm typed at a whole all-bare `data` union carries only its variant word,
  which does not say which union member is live; the fix is one `data` union =
  one union member, needing the variant->parent fact in `ck_lower`.
  `union_return_of_a_bare_member_tail_refuses` — a fn whose body is
  unconditionally one member never types anything at the union, and the pass is
  not told the DECLARED return, so the tail is not lifted and codegen refuses;
  the fix is to thread the return `Ty` into `lower_body_full`. That shape
  compiled before this slice only because no caller could consume what it
  produced.
- R3: `lower.rs`'s literal fold moved beside the literal readers it calls, and
  the tuple frame law out of `layout/mod.rs` into its own file — both files were
  over their cap, and both splits are the seam the new code exposed rather than
  a cap raise.
mdoorn manually merged commit 120faa8810 into master 2026-09-03 10:24:22 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!217
No description provided.