Repository navigation
Propagate the PartialEq bound one hop up the call graph: the caller that forwards its own generic param into a comparing callee emitted a program that does not typecheck - #8802
Merged
Conversation
…warding its own generic param into a comparing callee emitted a program that does not typecheck #8699 landed the seed's equality bound correctly but only for the function whose OWN BODY compares. That is not a whole rule: once `fn callee<K>` carries `K: PartialEq`, every caller instantiating that K with its own generic param is obliged by rustc to prove a bound it never states, so the seed emits E0277 against a function whose body compares nothing. Measured on the real specimen, gunbc#8605's std.trait_derive_shape: repr_grounding_derive_shape_has_trait<K> compares `k == capability_key` and earned `<K: Clone + PartialEq>`; repr_grounding_derive_completeness_predicate<K> forwards its own K into that parameter and emitted `<K: Clone>`. That is what blocks #8605's regen. WHAT CHANGED - The per-(callee param, call argument) join that the Clone forwarding already performed is factored out as v1_call_forwarding_forwarded_param_names and is trait-agnostic: the only thing that distinguished Clone from equality was WHICH of the callee's generic params already earned a bound, and that now arrives as callee_bound_param_names. Forking the join per trait would have been a second representation of one fact about a call site (DESIGN.md §3). - v1_call_forwarding_equality_bound_param_names re-derives the callee's own equality bound with the same derivation emit_fn_def runs on itself, and forwards it. Its walk is RECURSIVE where the Clone half's is direct, because the specimens differ: the forwarded call here sits inside a lambda inside a pipelined all(), which the direct-shape lookup structurally cannot reach. Pointing the Clone half at the same walk would widen which functions receive a Clone bound corpus-wide — that is the next-rung trigger BoundedToDirectSingleCallLambdaBody already names, measured on its own regen, not smuggled in beside an equality fix. - One hop, declared: a bound earned only two hops up is discovered as each intermediate fn is itself re-emitted, exactly as the Clone half's note describes. - Cost shape (DESIGN.md §6): a fn with no type params can never receive a forwarded bound, so the (caller node × callee body) product is refused at the door rather than computed and discarded. §3 RENAMES, because the shared core is no longer about Clone v1_call_forwarding_clone_bound_wrapper_param_names -> v1_call_forwarding_bound_wrapper_param_names v1_union_clone_param_names -> v1_union_bound_param_names and the prose citations of `v1_call_forwarding_clone_bound_wrapper_param_name` — a symbol that never existed under that spelling — are corrected to the real one. A REAL DEFECT SURFACED BY THE FIRST GREEN, not designed around v1_union_bound_param_names filtered `extra` against `base` only, so a name repeated WITHIN extra survived twice. Latent while the only consumer was Clone, whose renderer keys a map by param name and absorbs the repeat; the equality bound concatenates onto that param's trait list, and the first emission produced `K: Clone + PartialEq + PartialEq` where one call site forwards the same wrapper param through two callee parameters (a bare K and a CapRow<K>). Fixed at the head — a union is duplicate-free in both directions — not at each consumer. EVIDENCE, by execution - Pre-fix emission of the reduction: `pub fn cap_table_complete<K: Clone>`. Post-fix: `pub fn cap_table_complete<K: Clone + PartialEq>`. - The real #8605 specimen, its trait_derive_shape.dag emitted through this seed: `pub fn repr_grounding_derive_completeness_predicate<K: Clone + PartialEq>`. - Two new floor witness rows in dag/test/claim/fn_equality_bound_witness_test.dag, both green (gunbc run --claim-run), alongside the two #8699 rows which stay green. The negative row is the discriminator: same wrapper shape, same generic param, same pipeline, but a callee comparing only concrete types — a derivation keyed on "my callee is generic" bounds K there and breaks every caller instantiating K with a non-PartialEq type. Measured bare: `pub fn cap_table_shape_is<K: Clone>`. - Regen fixed point: claim_executor --required-regen first_generation_equal=true, planned=129 executed=129, after installing the re-derived mirrors. cargo fmt --all --check clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 21, 2026
… a STEP
33 -> 10 -> 0 hard diagnostics. The recurring cost of every integration that
touches src/v1/*.dag is that main's hunks arrive with bare names (main still
has imports) and the cut leaves them unqualified. This has now happened four
times and I have hand-fixed it four times, so it is a step rather than a
memory:
scratchpad/qualify-from-diagnostics.py
It is driven by the COMPILER'S OWN DIAGNOSTICS, never by a corpus sweep: it
reads "unresolved type 'X' (file:...)" out of a regen log, so it only touches
names the compiler reported, at files the compiler named. Three properties
that matter, each one a defect I hit today:
* its declarer index reads multi-line coproducts and payload-carrying
variants, the blind spot that manufactured a 4067-row false finding and a
189-row false audit
* it keeps a declaration head and a variant name bare and qualifies only
the rest of the line -- the guard that previously skipped whole variant
lines and left their FIELD TYPES unqualified
* a name with two or more declarers is REFUSED and reported, never guessed.
No best-effort arm; ambiguity is a hand decision
It closed 8 of this integration's 33. The remaining class it CANNOT see is
the v1/v2 Node collision, which arrives as "no field 'name' on type 'Node'"
rather than as an unresolved name -- bare Node binds to v2.std.node.Node,
which has no such field. Ten sites, qualified to v1.std.core.Node by hand;
teaching the script that class needs the declarer to be chosen by the FILE'S
HOME rather than by uniqueness, which is a different rule and is not folded
in on a guess.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Unblocks #8605's regen. Orphaned lane (swift-moth-294 no longer exists), picked up here.
The gap
#8699 landed the seed's equality bound correctly, but only for the function whose own body compares. That is not the whole rule. Once
fn callee<K>carriesK: PartialEq, every caller instantiating thatKwith its own generic param is obliged by rustc to prove a bound it never states — so the seed emits a program that does not typecheck, and the E0277 lands on a function whose body compares nothing.Measured on the real specimen, #8605's
std.trait_derive_shape:repr_grounding_derive_shape_has_trait<K>(comparesk == capability_key)<K: Clone + PartialEq>✅repr_grounding_derive_completeness_predicate<K>(forwards its ownKinto that parameter)<K: Clone>❌ E0277What changed
v1_call_forwarding_forwarded_param_namesand is trait-agnostic: the only thing separating Clone from equality was which of the callee's generic params already earned a bound, and that now arrives ascallee_bound_param_names. Forking it per trait would be a second representation of one fact about a call site (§3).v1_call_forwarding_equality_bound_param_namesre-derives the callee's own equality bound with the same derivationemit_fn_defruns on itself (§2), then forwards it.all(), which the direct-shape lookup structurally cannot reach. Pointing the Clone half at this walk would widen which functions receive a Clone bound corpus-wide — that is the next-rung triggerBoundedToDirectSingleCallLambdaBodyalready names, measured on its own regen, not smuggled in beside an equality fix.§3 renames
The shared core is no longer about Clone:
Prose citations of
v1_call_forwarding_clone_bound_wrapper_param_name— a symbol that never existed under that spelling — are corrected to the real one.A real defect the first green surfaced
v1_union_bound_param_namesfilteredextraagainstbaseonly, so a name repeated withinextrasurvived twice. Latent while the sole consumer was Clone, whose renderer keys a map by param name and absorbs the repeat; the equality bound concatenates onto that param's trait list, and the first emission producedK: Clone + PartialEq + PartialEq— one call site forwarding the same wrapper param through two callee parameters (a bareKand aCapRow<K>). Fixed at the head: a union is duplicate-free in both directions, so no consumer has to know whether the result can repeat.Test plan — green by execution, with a discriminating red
pub fn cap_table_complete<K: Clone>. After:pub fn cap_table_complete<K: Clone + PartialEq>. That measured pre-fix spelling is what the new witness refuses, rather than merely asserting the fixed one is present.trait_derive_shape.dagemitted through this seed:pub fn repr_grounding_derive_completeness_predicate<K: Clone + PartialEq>.dag/test/claim/fn_equality_bound_witness_test.dag, both green viagunbc run --claim-run, alongside Emit a PartialEq bound on the generic parameter a fn body compares: generalize the type-param renderer from Clone-only to a per-parameter trait map #8699's two rows which stay green. The negative row is the discriminator — same wrapper shape, same generic param, same pipeline, but a callee comparing only concrete types; a derivation keyed on "my callee is generic" boundsKthere and breaks every caller instantiatingKwith a non-PartialEq type. Measured bare:pub fn cap_table_shape_is<K: Clone>.claim_executor --required-regen:first_generation_equal=true planned=129 executed=129after installing the re-derived mirrors (three generations: emit_rust, then trait_bound_witness, then fixed).cargo fmt --all --checkclean;cargo build --releaseclean.🤖 Generated with Claude Code