Canonicalize each arrow's Pi binder as the solver flattens types - #76
Closed
dpmills wants to merge 4 commits into
Closed
Canonicalize each arrow's Pi binder as the solver flattens types#76dpmills wants to merge 4 commits into
dpmills wants to merge 4 commits into
Conversation
Contributor
Author
This was referenced Aug 11, 2026
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 11, 2026 22:34
13b4fae to
14faed2
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 12, 2026 00:09
14faed2 to
0a9ee2e
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 12, 2026 00:38
0a9ee2e to
cc899f9
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 12, 2026 20:08
cc899f9 to
e64b690
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 12, 2026 20:43
e64b690 to
53e505c
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 12, 2026 22:30
53e505c to
7689793
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 12, 2026 22:45
7689793 to
fbfda85
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 13, 2026 05:39
fbfda85 to
991642a
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 13, 2026 06:01
991642a to
254df81
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
2 times, most recently
from
August 13, 2026 23:42
800ecc1 to
bd29729
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 14, 2026 19:49
bd29729 to
b167942
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 14, 2026 21:34
b167942 to
97be0a9
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 14, 2026 21:41
97be0a9 to
1a42ebf
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 14, 2026 23:11
1a42ebf to
4162d96
Compare
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 17, 2026 17:57
4162d96 to
b497221
Compare
Two α-identity defects — `SpecKey` splitting on α-variant dependent types, and α-variant bound merges keeping an arrival-dependent binder plus a dangling predicate twin — get the repair the codebase already applied to refinements: **one reserved name per position**. `ReservedName::Pi(depth)` (spelled `__pi0`, `__pi1`, …) joins `Elem`, and the solver canonicalizes every arrow's binder to it as types flatten: - `compact_go` renames the binder to `Name::pi(depth)` and rides the rename on the accumulated substitution, so every predicate reference inside the codomain is rewritten as it lands — the same force that discharges dependent applications. α-variant bounds now compact to *identical* shapes: they merge, their refinement copies dedup instead of accumulating a dangling twin, and every identity built on the flattened form (equality walls, caches) is α-insensitive. The rename also subsumes the previous `shadow(b)` (insertion over any outer mapping of the source binder). - `spec_key::key_go` does the same, so a keyed predicate references its binder α-insensitively — two uses whose instantiation types differ only in source binder names key together. (The binder name itself was already excluded from the key; this closes the leak through the *references*.) - `Type::alpha_normalized` exposes the same form as a pure function; `lambda_elim`'s recorded-vs-rebuilt asserts compose it with `without_pi_names` (whose `Some`-vs-`None` job is unchanged), since the coalesced original now carries canonical binders while the point-free construction rebuilds from the term's own names. Both defects are pinned as `spec_key_shares_alpha_variant_dependent_types` and `alpha_variant_bound_merge_is_canonical`, the latter also asserting the merged type carries one binder and exactly one deduped refinement layer. `constrain`'s semantics are untouched — this stratum deliberately leaves mid-inference source-named types to the existing α-reconciliation. Retiring that machinery too needs the minting-level stratum (`emit_lambda` producing canonical binders).
The rule — *rename a function type's binder at codomain-depth `d` to the reserved `Name::pi(d)`, rewrite references to it through that rename, recurse into the codomain at `d + 1` while the domain keeps `d`* — was written out three times: in `compact_go` (the flattened bound graph), in `spec_key::key_go` (the specialization key), and in `Type::alpha_normalized` (the pure function the recorded-vs-recomputed walls compare through). The first two already carried a comment that they must be changed together, and by `compact_go`'s own documentation a divergence between them is **silent**: it yields a shared clone whose interior was resolved against a different use's argument. `alpha_normalized` was a third copy that no comment mentioned. Three copies of one rule, agreement load-bearing and unenforced. All three now call `Subst::canonical_pi_binder`, which owns the rule and states why the walks applying it have to agree. `key_go` discards the returned canonical name — the binder name is deliberately not part of a specialization key (`SpecKey::fun`); what it needs is that the *references* were rewritten — and that asymmetry is now visible at the call site instead of being implicit in a hand-inlined copy. Behaviour is unchanged; this is the rule stated once rather than asserted thrice. The lockstep note on `compact_go` is narrowed to what genuinely remains duplicated between it and `key_go` (polarity flips, edge-substitution composition, the cycle guard).
`freshen_pi_binders`'s structural walk names the variant it recurses through, so the rename downstack reaches it. No behavior change.
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 18, 2026 04:11
b497221 to
9be5020
Compare
…un-derivable `canonical_pi_binder`'s doc justified the rule by its effect — one shared name per position makes α-variant types flatten alike — which reads as convenience and invites the question of why a comparison could not do the same job. It cannot: a Pi reference has no position until flattening, because a dependent refinement rides a bound edge and the variable holding it need not sit under the binder its predicate references. `type-inference.md` §4.5 gains that as a section, with the group-by shape its own lowering tests pin as the exhibit, and the four alternatives recorded against what each breaks on — minting at emission, counting inside out, an α-invariant `PartialEq`/`Hash`, and renaming at merge. The rule was already shared; the *depth* was not. Each of the three walks took the canonical binder from `canonical_pi_binder` and then computed `depth + 1` itself, so a walk could get the name right and the depth wrong. `canonical_pi_binder` now returns a `PiCodomain` carrying the binder, the codomain's substitution, and the codomain's depth together, and no `+ 1` remains outside it. What the walks owe each other is not what the doc previously claimed. `compact_go` and `alpha_normalized` must assign the same index, since `lambda_elim` compares a solver-produced type against a rebuilt one by normalizing both. `key_go` owes only injectivity: a key carries no binder name, so a consistent relabelling is invisible, while conflating two binders makes two uses share a specialization resolved against the other's argument. Injectivity is what the new tests assert, and it is the property the shared rule cannot establish. Entering a codomain at the arrow's own depth names every binder `__pi0` — internally consistent and idempotent, so a type compared against its own canonical form still matches. The first version of these tests did exactly that and passed under the injected fault; `canonical_binders_keep_distinct_binders_distinct` and `spec_key_keeps_distinct_binders_distinct` fail under it.
dpmills
force-pushed
the
dmills/canonical-pi-binders
branch
from
August 18, 2026 05:22
777736f to
2194bc3
Compare
Contributor
Author
|
Superseded by the scoped-inference-variables branch ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A dependent function type's binder name leaks into two identities that are meant to be blind to it.
spec_keyexcludes the binder from the key, but a dependent refinement predicate references it, so(𝑥: 𝐷) ⤇ {Int | __elem == 𝑥}and its𝑦-twin key apart and split a specialization two uses should share. Merging α-variant bounds keeps the first arrival's binder while unioning both copies of one predicate, coalescing to an arrival-order-dependent type whose second predicate references a binder that is no longer there. Both flattening walks now rename each arrow's binder, and every codomain reference to it, to the reserved__pi{depth}name, so α-equivalence coincides with structural equality on the flattened form.Subst::canonical_pi_binderowns the rule; its doc comment names the three walks that apply it and what a silent divergence between them costs.Type::alpha_normalizedis that scheme as a pure function, whichlambda_elim's debug assertions compose withwithout_pi_namesto compare canonical solver output against a type rebuilt from the term's own binders.A regression test in
compact.rsand one inspec_key.rsstate the repaired findings.Why the rewrite sits at the flattening layer rather than at emission or in a comparison, and what the four alternatives break on, is recorded in type-inference.md;
canonical_pi_bindercites it.