Unions: a union return gets a discriminant, so the match E0723 forces can reach code #217
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/217-unions-a-union-return-gets-a-discriminant-so-t"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Unions: a union return gets a discriminant, so the match E0723 forces can reach code
Along the way:
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.rsand 15 more.Happy with this. I ran the suite against the branch and it is green,
src/rust/crates/ck_lower/src/lower/match_lower.rsincluded.@ -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> {extra_test_blocksreads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?Split. The decision in
extra_test_blocksis a pure function now, with its own tests insrc/rust/crates/ck_lower/src/lower/match_lower.rs.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.