Skip to content

Give CompactType's refinement slot the None sentinel its shape slots carry - #115

Open
dpmills wants to merge 2 commits into
dmills/canonical-dischargefrom
dmills/not-a-value
Open

Give CompactType's refinement slot the None sentinel its shape slots carry#115
dpmills wants to merge 2 commits into
dmills/canonical-dischargefrom
dmills/not-a-value

Conversation

@dpmills

@dpmills dpmills commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

CompactType::merge could not tell "no refinement contribution here" from "a value that guarantees nothing": both were the empty RefinementSet, and an empty set is absorbing under the positive intersect, so a bare variable's contribution erased refinements a sibling bound established. Three places worked around that — merge's imposes_nothing guard, the bound fold keeping the variable out until the end, and the opposite-polarity fallback's refinement union. refinements becomes Option<RefinementSet>, the sentinel rec, var, and fun already carry, with None as the merge identity; a hole and a bare variable are the only contributions that are not values and keep it, and CompactType::value builds the rest.

dissolve_read_feeds' reconstructed channel now imposes nothing rather than an empty refinement set — it re-expresses content already at the position rather than arriving as a second bound — so a refined feed read keeps its refinements. Nothing else changes behavior: the suite passes forward, under DEEP_TYPECHECK=1, and under CAMBRA_REFINEMENT_ORDER=reverse.

Asserting it

CompactType::refinements documents that None is the merge identity and belongs to exactly the two contributions that are not values — a hole and a bare variable — while Some(empty) is a value guaranteeing nothing. Nothing in the type system says so: every value-shaped arm has to be built from CompactType::value, across a dozen construction sites, and a position that carried content with the slot absent would absorb a sibling bound's refinements instead of intersecting with none of its own, so Int joined with {Int | p} would keep p.

refinement_slot_present is that post-condition, checked on compact_type_with's output and on every recursive-variable bound it recorded. Variable contributions are not content: a bare variable's whole content is its identity, so vars is the one populated field the slot may accompany as None.

Three tests keep the checker from being vacuous — it accepts the hole and the bare variable, and rejects content with the slot absent both at the root and below it. The whole suite passes with the assert live, so the invariant holds on every program the corpus compiles.

@dpmills
dpmills force-pushed the dmills/not-a-value branch from b188b46 to 693f95d Compare August 20, 2026 20:51
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 693f95d to e7ce9db Compare August 20, 2026 23:24
@dpmills
dpmills force-pushed the dmills/not-a-value branch from e7ce9db to 97b813f Compare August 21, 2026 04:29
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 97b813f to e54152e Compare August 21, 2026 04:39
@dpmills
dpmills force-pushed the dmills/not-a-value branch from e54152e to 64bd663 Compare August 21, 2026 04:55
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 64bd663 to ffceb03 Compare August 21, 2026 05:47
@dpmills
dpmills force-pushed the dmills/not-a-value branch from ffceb03 to 2dceea3 Compare August 21, 2026 18:41
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 2dceea3 to 456fae2 Compare August 21, 2026 19:42
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 456fae2 to 0131efc Compare August 21, 2026 20:45
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 0131efc to 01f4c60 Compare August 21, 2026 22:23
@dpmills dpmills changed the title Give CompactType's claim slot the None sentinel its shape slots carry Give CompactType's refinement slot the None sentinel its shape slots carry Aug 21, 2026
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 01f4c60 to fe7fee9 Compare August 22, 2026 00:08
@dpmills
dpmills force-pushed the dmills/not-a-value branch from fe7fee9 to 5282a5a Compare August 22, 2026 00:22
@dpmills
dpmills force-pushed the dmills/not-a-value branch 2 times, most recently from 53e8425 to 7638303 Compare August 22, 2026 00:46
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 7638303 to 063e13a Compare August 22, 2026 01:06
…ots carry

`CompactType::merge` could not tell "no refinement contribution here" from "a value that guarantees nothing": both were the empty `RefinementSet`, and an empty set is absorbing under the positive intersect, so a bare variable's contribution erased refinements a sibling bound established. Three places worked around that — `merge`'s `imposes_nothing` guard, the bound fold keeping the variable out until the end, and the opposite-polarity fallback's refinement union. `refinements` becomes `Option<RefinementSet>`, the sentinel `rec`, `var`, and `fun` already carry, with `None` as the merge identity; a hole and a bare variable are the only contributions that are not values and keep it, and `CompactType::value` builds the rest.

`dissolve_read_feeds`' reconstructed channel now imposes nothing rather than an empty refinement set — it re-expresses content already at the position rather than arriving as a second bound — so a refined feed read keeps its refinements. Nothing else changes behavior: the suite passes forward, under `DEEP_TYPECHECK=1`, and under `CAMBRA_REFINEMENT_ORDER=reverse`.
`CompactType::refinements` documents that `None` is the merge identity and belongs to exactly the two contributions that are not values — a hole and a bare variable — while `Some(empty)` is a value guaranteeing nothing. Nothing in the type system says so: every value-shaped arm has to be built from `CompactType::value`, across a dozen construction sites, and a position that carried content with the slot absent would absorb a sibling bound's refinements instead of intersecting with none of its own, so `Int` joined with `{Int | p}` would keep `p`.

`refinement_slot_present` is that post-condition, checked on `compact_type_with`'s output and on every recursive-variable bound it recorded. Variable contributions are not content: a bare variable's whole content is its identity, so `vars` is the one populated field the slot may accompany as `None`.

Three tests keep the checker from being vacuous — it accepts the hole and the bare variable, and rejects content with the slot absent both at the root and below it. The whole suite passes with the assert live, so the invariant holds on every program the corpus compiles.
@dpmills
dpmills force-pushed the dmills/not-a-value branch from 063e13a to fcfc727 Compare August 22, 2026 03:43
@dpmills
dpmills marked this pull request as ready for review August 22, 2026 03:49
@dpmills
dpmills requested a review from a team as a code owner August 22, 2026 03:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant