Repository navigation
Row 1b: derive Clone bound for call-forwarding fn/impl signatures - #8539
Merged
Merged
Conversation
added 2 commits
August 19, 2026 08:33
Extends TraitBoundWitnessScope with BoundedToDirectSingleCallLambdaBody: a wrapper function whose body is directly (or via a single-call lambda) a call into an already Clone-bounded callee now derives its own Clone bound from the callee's real requirement, instead of the emitter reasoning about it ad hoc per call site. - src/v1/trait_bound_witness.dag: new pure decision core (v1_call_forwarding_clone_bound_wrapper_param_names), scoped and documented as distinct from the RC-match-scrutinee mechanism (#8466); next-rung trigger (a recursive body walker) named as explicit future work, not built here. - src/v1/04_emit_info.dag, 04_infer.dag: add fn_decl_items: Map<String, Node> to EmitGraphInfo/EmitInfoBuildState so the new mechanism can resolve a callee by name and re-run emit_fn_def's own derivation on the callee's real declaration Node, rather than a parallel re-derivation (DESIGN.md S2/S5). - src/v1/05_emit_rust.dag: minimal wiring at the emit_fn_def call site only — extracts the existing self-derivation into v1_fn_body_derived_clone_param_names and unions it with the new v1_call_forwarding_clone_bound_param_names; no new decision logic in the hot emitter itself. - dag/test/claim/trait_bound_witness_test.dag: two new witnesses driving the real emit pipeline end to end (compile_dag_rust_emit_check), reproducing the real src/v2/std/staging.dag cached_stage<A, B> shape (now emits `fn cached_stage<A: Clone, B: Clone>(`) and a declared negative control for the match-wrapped-body boundary this mechanism does not cover (src/v2/compiler/materialization_carriers.dag's cached_stage_governed<A, B>, confirmed still bare — a named, non-silent scope boundary, not a live compile error in the census entrypoint). Verified against docs/probes/materialization_carriers_rebaseline_2026-08-19.md's repro instrument: real gunbc compile -> cssl_assemble -> cargo build --release --lib --message-format=json against the assembled crate now reports zero E0277/E0308/E0599/E0369/unreachable_patterns/E0560/E0282 errors (down from the 46 Clone-bound-related baseline errors); the 5 remaining errors (E0422 x2, E0425 x3) are pre-existing and unrelated (ProviderRetention/NonEmptyStr not found). dashboard node adhoc-574e999b-39c
This was referenced Aug 19, 2026
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
Breaks the --required-regen population deadlock for the new module dag/extdeps/languages/rust/derive_contracts.dag: its first stage0 mirror cannot be produced by regen itself, because validate_compared_populations refuses on emitted_not_committed BEFORE any candidate is written. Bytes are emitter output (gunbc compile --target rust --entry ...), never hand-authored, following the route receipted by #8539. Registration is the file plus its lib.rs line only -- neither stage0_emit_plan_generated.dag nor stage0_crate_layout_generated.dag enumerates ordinary emitter-produced modules (verified, not assumed). Generation-1 mirrors installed: emit_rust, trait_derive_emit, derive_contracts. Generation-2 is NOT installed and this tree is not the regen fixed point -- installing it yields 161 rustc errors, tracked separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls
added a commit
that referenced
this pull request
Aug 21, 2026
…8574) * Row 1a: supplemental generic Clone bounds for derives over FreeMonoid fields Restores the row-1a mechanism from quarry after the §3 question that caused its drop was settled by execution: #8539 landed and left these sites standing, so its Clone-bound synthesis demonstrably does not generalise to this case. The live instance is FreeMonoidUniqueState<T> in src/v2/std/algebra.dag, whose derive over `seen: Rc<Vec<T>>` requires a supplemental T: Clone the emitter did not synthesize (10 E0277 + 2 E0369 on the materialization_carriers board). The witness oracle is silent-raven-853's, resurrected from their three commits (c76b769 introducing it, a52f0e1 and 776a6d5 correcting it) rather than rewritten. Author-of-oracle and author-of-implementation stay separate. Re-attributes two notes that labelled a coordinating session's ruling as an "operator verdict" while naming a session id. The substance is unchanged; the label claimed an authority external to the diff that a session cannot hold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * E0310: synthesize 'static on emitted impl Fn parameters An arrow type realizes in Rust as Rc<dyn Fn(..) -> ..>, and a dyn trait object defaults to the 'static lifetime, so a function that closes over its arrow-typed parameters and returns one cannot compile unless those parameters are 'static. rustc reports E0310 at the Rc::new(move |..| ..) site. The obligation is created by the realization choice, not by the authored source: v2/std/staging.dag declares plain arrows and carries no Rust concept at all. It is fixed at the emitter for that reason. Declaring it at the source would mean inventing Rust lifetime syntax in a medium-agnostic language ('static appears in no .dag source module) and would leave the next arrow-into-Rc site to be rediscovered the same way. Unconditional rather than gated on reaching a stored position: the substrate has no reference or lifetime concept, so every closure it can construct is already 'static and the bound rejects no expressible program. Same shape as the + Clone beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Row 1a: drop two in-body source annotations the grain rule refuses The restored mechanism carried five // lines inside declaration bodies. DESIGN §4c admits only standalone leading blocks attached to module-scope declarations, so v2 self-compile refused with two hard diagnostics and the regen never produced a seed: source annotation sits inside a declaration body. Only module-item grain is modeled; move it above the declaration it describes. Restores the quarry's own fix for this (8edf566), which the enumerated commit list used to restore the mechanism had omitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Row 1a witness: assert struct impl BODIES, not only their headers The struct and enum witnesses were asymmetric. The enum asserted both impl headers and impl bodies; the struct asserted headers only, so an emitter producing correct headers over an empty or wrong body satisfied every assertion in it -- a PartialEq whose body is literally `true` passed. The enum's body assertions would have caught that only by being the more thorough of the pair, which is luck rather than coverage wherever the struct and enum emission paths can diverge. The body spellings are grounded in a mechanism this change did not author: the runtime prelude's pre-existing NonEmptyVec hand-written impls establish the `&self.<field>` access form and the `fn eq(&self, other: &Self) -> bool` signature, and `debug_struct(..).field(..).finish()` is the std Formatter builder documented for named-field structs. Reading the spellings off this emitter instead would have re-run the same collision one level down. That precedent also independently corroborates the two impl headers already asserted here, which was not why it was consulted: it emits `impl<T: Clone + std::fmt::Debug> std::fmt::Debug for NonEmptyVec<T>`, character-for-character the shape of the third include, predating this change. Those two assertions are therefore precedent-backed rather than author-chosen -- load-bearing, because the rest of this witness was authored by the session that authored the emitter under test. Two limits recorded rather than left implicit: the include assertions are spelling-sensitive, so a semantically identical where-clause form fails include 3 and a red here is not by itself a semantic defect; and the `#[derive(Debug` exclude matches file-wide, safe only while this fixture declares a single type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Regen v1 emitter mirrors and seed the derive_contracts first mirror Breaks the --required-regen population deadlock for the new module dag/extdeps/languages/rust/derive_contracts.dag: its first stage0 mirror cannot be produced by regen itself, because validate_compared_populations refuses on emitted_not_committed BEFORE any candidate is written. Bytes are emitter output (gunbc compile --target rust --entry ...), never hand-authored, following the route receipted by #8539. Registration is the file plus its lib.rs line only -- neither stage0_emit_plan_generated.dag nor stage0_crate_layout_generated.dag enumerates ordinary emitter-produced modules (verified, not assumed). Generation-1 mirrors installed: emit_rust, trait_derive_emit, derive_contracts. Generation-2 is NOT installed and this tree is not the regen fixed point -- installing it yields 161 rustc errors, tracked separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Gate 'static synthesis on the enclosing fn returning an arrow The unconditional form landed in 5d9fc3e on the premise that the bound 'cannot reject any expressible program'. That premise was FALSIFIED BY EXECUTION: installing the generation-2 regen candidate produced 161 rustc errors (E0373 / E0597) in v1_compiler_emit.rs and v1_compiler_emit_rust.rs. The premise conflated WHAT is captured with HOW. The captured values are owned, but the emitter emits the closure WITHOUT move, so the capture is a borrow, and a non-move closure borrowing an owned local is not 'static however owned that local is. The gate is precise, not a proxy: the Rc::new(move |..| ..) site that creates the obligation exists exactly when the function returns an arrow, and the lowering already emits those closures with move. So no call site changes and no additional clone is emitted. The rejected alternative -- emit move at every closure site to satisfy a bound synthesized at every parameter -- is two unconditional widenings composing, and a move closure must clone anything still live afterward, raising clone count on the axis this lane is reducing. Measured: the seed closure has 99 impl Fn parameters and ZERO functions returning Rc<dyn Fn>, so the unconditional form bought nothing there. After the gate, emitted '+ 'static' bounds in the regenerated seed closure: ZERO (the two remaining matches are a hand-maintained file's own bound and this emitter's own string literal). Regen drift falls from 8 files to 2. Not yet at the fixed point: std_occurrence_binding.rs carries a SEPARATE regression -- AmbiguousBindingCandidates<N> loses a required N: Clone through an Rc-wrapped field -- which is Row 1a header-bareness, a different class, tracked separately and not repaired here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Row 1a: split the fused impl_bodies flag; derive header bareness from fields impl_bodies-nonempty was ONE FLAG ENCODING TWO FACTS: it stood in for "does some field's type require this parameter to carry a bound", AND it was the switch keeping the item header in agreement with its supplemental impl headers. Removing it from the header site alone revealed the second fact rather than breaking an invariant. Both facts now derive from one authority. Header bareness reads clone_bounded_type_params (the well-formedness fixpoint, "does NAMING G<A..> require P: Clone"), which is exactly the field-derived property; the GroupCompletion supplemental impls read the same record through one Bool. Rust REQUIRES an impl's generics to satisfy the type's own bounds, so struct S<M: Clone> with impl<M> Neg for S<M> cannot compile. The two headers are ONE decision rendered twice, which makes single authority the construction answer here, not a style preference. Note the two renderings are NOT the same string: the bound belongs in the impl generics and must NOT appear in the type reference (GroupCompletion<M: Clone> is not Rust). Only a compiler fact crosses into extdeps.languages.rust.emit -- a Bool about the carrier's declaration. The spelling (Clone, trait paths, where-clauses) stays in extdeps, so the §3 duty is intact. Deliberately NOT routed through spelling.where_bounds, which carries the OP's own requirement: merging them would recreate the fused flag this change splits. Precedent, same file: reference_use_lines_representation_invariant_note resolved this class by deleting both halves of a fused flag and letting each fact derive from its own authority. ERROR TRAJECTORY -- the 21 were not a regression of the 1; they appeared ONLY because the header fix was correct, and are resolved by feeding the impl headers the same record: 161 unconditional 'static 1 after the 'static gate 21 after the field-derived header rule (header fixed, impls still bare) 0 after both headers read one authority VERIFIED: first_generation_equal=true AND fixed_point_equal=true. FILE OUTSIDE THIS LANE'S DECLARED PAIR: dag/extdeps/languages/rust/emit.dag carries the impl header literal and has no sole-write owner; announced here so anyone who considers it theirs sees it. Leaving the literal and matching it compiler-side would have been a second representation of one fact. KNOWN ASYMMETRY, named not fixed: the item header is rendered compiler-side while the impl header is rendered in extdeps, so the tree answers "where does a Rust bound get spelled" two ways. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Name the has_fn_fields residue as a declared boundary with its trigger Splitting the fused impl_bodies flag left has_fn_fields as the only thing between the well-formedness record and the emitted header, which is exactly where a second proxy would hide. Checked rather than asserted: it IS partly a proxy. It is a real discriminator for the DERIVE half -- a derive emits per-impl bounds, so Monoid<T> { identity: T } with derive(Clone) gets impl<T: Clone> Clone generated and needs no header bound. That half is correct and is now stated as the reason, replacing a vague 'different carrier shape'. It has NO authority over the WELL-FORMEDNESS half, which is independent of derives entirely, yet it gates that computation too. Population measured as a closed enumeration, not a spot check: 17 callable- field generic structs (16 derive Clone); the complete non-callable, non-phantom field set is &'a str, identity/zero/one/top/bottom: T, and Rc<PartialFunction<K, V>>. Exactly one names a declared type, and PartialFunction<K, V> is itself bare, so it carries no obligation. The exposing population is EMPTY -- 'no instance today', NOT 'cannot occur'. Deferred on displaced cost, NOT on unavailable evidence: a controlled fixture plus a regen fixed point over the live 17 are both available today. With the population empty the benefit is zero while the risk against 17 live carriers is not, so it is worth less than what it displaces. Recording 'no evidence' would have been false and would have retired on the wrong condition. Mirrored defect class, worth stating together: 'static was a bound APPLIED to a population that could not need it; this is a bound SUPPRESSED for a population that does not yet exist. 'It cannot reject anything' and 'nothing needs it' are the same unmeasured claim at opposite signs. Annotation-only: regen fixed point unchanged (§4c -- annotations cannot alter target-program bytes), verified rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Update the witness consumers of the changed signature; add a two-position control CI caught a real miss: rust_supplemental_impls_group_completion gained a parameter and src/v2/test/claim/emit/trait_derive_seed_emit_binding_test.dag still called it with zero arguments -- four call sites, four call-shape refusals. I verified the emitter and the regen fixed point but not the WITNESS consumers of the signature I changed. Local regen cannot catch this; only the floor runs those tests. Also adds the control that was missing for the failure mode most likely to recur. Rust requires an impl's generics to satisfy the type's own bounds, so the carrier bound is ONE FACT rendered TWICE, and the two renderings are not the same string: impl<M: Clone> Neg for GroupCompletion<M> compiles impl<M: Clone> Neg for GroupCompletion<M: Clone> is not Rust An implementation that renders one bounded value and substitutes it at both positions passes a naive presence check and emits code rustc refuses, so the negative assertion is the load-bearing half. The false arm additionally proves the parameter DISCRIMINATES: without it, hardwiring the bound on would satisfy every positive assertion. VERIFIED BY EXECUTION, both directions, not by compiling: correct emitter -> returned true perturbed to bound the TYPE position too -> returned false restored -> returned true Also corrects a now-false sentence in trait_derive_emit_item_clone_bound_rule_ note, which still claimed arithmetic carriers with supplemental impl blocks keep bare item params. They no longer do. Corrected in place rather than left standing beside the change that falsified it. Note the two prose edits behave differently, which is §4c working as specified: the leading // annotation added earlier changed ZERO emitted bytes, while this data ...: String row is program data and drifted its mirror. Prose in a String is not free. Regen: first_generation_equal=true AND fixed_point_equal=true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Brian Searls <briansearls1@gmail.com> 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.
Summary
Extends the Clone-bound derivation mechanism landed in #8466 (
TraitBoundWitnessScope::BoundedToDirectTopLevelMatchBody, the RC-match-scrutinee case) with a second, structurally distinct case:BoundedToDirectSingleCallLambdaBody. A wrapper function whose body is directly — or via a single-call lambda — a call into an already Clone-bounded callee now derives its ownClonebound from the callee's real requirement, rather than the emitter reasoning about it ad hoc per call site.This targets Row 1b of the census in
docs/probes/materialization_carriers_rebaseline_2026-08-19.md: 14 of the 51 baseline compile errors traced to generic fn/inherent-impl signatures missing aClonebound their forwarding calls into other generic functions actually require.What changed
src/v1/trait_bound_witness.dag— new pure decision core,v1_call_forwarding_clone_bound_wrapper_param_names. Scoped and documented as distinct from the RC-match mechanism; the next-rung trigger (a recursive body walker mirroringemit_rust_expr_if/emit_rust_expr_let's accessor shapes) is named as explicit future work, not built here (DESIGN.md §6 — no hypothetical generality).src/v1/04_emit_info.dag,04_infer.dag— addfn_decl_items: Map<String, Node>toEmitGraphInfo/EmitInfoBuildState, mirroring the pre-existingtype_decl_items. This lets the new mechanism resolve a callee by name and re-runemit_fn_def's own derivation on the callee's real declarationNode, instead of a second parallel re-derivation (DESIGN.md §2/§5 — single authority, construction over validation).src/v1/05_emit_rust.dag— minimal wiring at theemit_fn_defcall site only: the existing self-derivation logic is extracted verbatim intov1_fn_body_derived_clone_param_names, and the newv1_call_forwarding_clone_bound_param_namesis unioned with it. No new decision logic added to the hot emitter itself — all reasoning lives in the puretrait_bound_witness.dagmodule.dag/test/claim/trait_bound_witness_test.dag— two new witnesses, both driving the real emit pipeline end-to-end viacompile_dag_rust_emit_check(a real executing consumer, not an inert unit test — DESIGN.md §5):call_forwarding_derives_clone_bound_from_callee: reproduces the realsrc/v2/std/staging.dagcached_stage<A, B>shape; asserts the fixed emitted signaturefn cached_stage<A: Clone, B: Clone>(.call_forwarding_scope_excludes_match_wrapped_body: a declared-boundary negative control (DESIGN.md §4b rung honesty). Reproducessrc/v2/compiler/materialization_carriers.dag'scached_stage_governed<A, B>shape — a lambda body that wraps the call in amatchrather than being directly the call — and confirms it stays bare (fn cached_stage_governed<A, B>(), outside this mechanism's declared scope.Verification
Ran the census-doc's own repro instrument (
docs/probes/materialization_carriers_rebaseline_2026-08-19.md§1): realgunbc compile --target rust→cssl_assemble→cargo build --release --lib --message-format=jsonagainst the assembled crate.72676cf0be): 51 errors —E0277 16 · E0308 15 · E0599 9 · E0425 3 · E0422 2 · E0369 2 · unreachable_patterns 2 · E0560 1 · E0282 1.E0277/E0308/E0599/E0369/unreachable_patterns/E0560/E0282errors. 5 errors remain (E0422 x2,E0425 x3), both pre-existing and unrelated to Clone bounds (ProviderRetention/NonEmptyStrnot found) — out of this PR's scope.v2_std_staging.rsnow emitsfn cached_stage<A: Clone, B: Clone>(...), confirmed against the realsrc/v2/std/staging.dagmodule.v2_compiler_materialization_carriers.rs'scached_stage_governed<A, B>remains bare — the declared scope boundary above. Confirmed it has zero callers in this crate's dependency closure, which is why the residual gap does not surface as a live rustc error in the verified entrypoint.dag/test/claim/trait_bound_witness_test.dag(the pre-existing RC-match witness plus the two new ones)PASSviaclaim_batch --source-root dag --source-root src/v2 --entry dag/test/claim/trait_bound_witness_test.dag --functions ....Test plan
claim_batchrun overtrait_bound_witness_test.dag: all 3 witnesses PASSdocs/probes/materialization_carriers_rebaseline_2026-08-19.md: 46 Clone-bound-related errors resolved, 5 pre-existing unrelated errors remain, byte-verified against real emitted Rustorigin/maindashboard node adhoc-574e999b-39c