Repository navigation
Model TraitBoundWitness: derive Clone-bound evidence from RC-match lowering facts - #8466
Conversation
…wering facts (node://adhoc-d30225a0-531) Root-caused the materialization_carriers E0599 cluster: emit_fn_def (src/v1/05_emit_rust.dag) calls v1_generic_params_needing_clone_bound (src/v1/trait_derive_emit.dag) without the function body in scope, so the Clone-bound decision is structurally blind to the RC-deref-clone lowering emit_typed_match already performs via analyze_rc_match when a match scrutinee resolves to a shared/Rc-wrapped generic coproduct. When the pointee type carries a generic parameter, the emitted .clone() call needs that parameter Clone-bounded; nothing derives or emits the bound today, producing rustc E0599. Adds v1.compiler.trait_bound_witness (additive, unwired): a pure decision core reusing analyze_rc_match's existing needs_deref verdict (no new heuristic), plus a Node-consuming extraction wrapper scoped to a function body that is directly a top-level match (the shape of the real specimens in dag/std/cache_interface.dag). A proposed consumer diff for emit_fn_def is documented in-module for the owner of the hot emitter files (materialization_carriers lane) to land alongside the actual fix; this PR does not touch src/v1/05_emit_rust.dag or src/v1/trait_derive_emit.dag. Cross-lane grounding: this exact site family is already censused by prior P-fn/B0 work (dag/tools/e0599_emitter_decision_census.dag, cause CloneSharedRequirement, lowering DerefCloneWholeValue) which explicitly defers the sharing-carrier choice to a pending B1 decision. TraitBoundWitness answers a narrower, carrier-agnostic question (which bound an already-occurring clone needs) and is shaped to be absorbed by whichever carrier B1 selects rather than compete with it. dag/test/claim/trait_bound_witness_test.dag adds four real executing unit tests over the pure decision core plus one discriminating specimen (compile_dag_rust_emit_check) that asserts today's actual unfixed emitted shape on a minimal Choice<N>/describe<N> fixture. Per DESIGN.md S4b, this specimen is expected to invert (not delete) once the consumer diff lands, becoming the positive-fix regression control. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…atterns The required-floor fold is a single whole-corpus pass, so this file's [n] => ... list-literal match-arm patterns (unsupported by the .dag grammar -- "expected FatArrow, found LBracket") refused the entire floor on PR #8466, not just this witness. Rewritten using count/first()/skip(n: 1) with Optional (Present/Absent) matching, which the grammar does support and which this module already used nowhere near match-arm position. Also collapses the trait-bound-witness design to mint no named carrier at all: the two v1.compiler.trait_bound_witness functions return plain List<String> (the same shape v1_generic_params_needing_clone_bound already returns and unions in emit_fn_def), so there is nothing to reconcile against a future B1 RequiredTraitWitness-family decision. The Node-consuming Rc-match-scrutinee extraction is inlined directly into emit_fn_def (05_emit_rust.dag) rather than wrapped in this module, avoiding an emit_rust <-> trait_bound_witness import cycle (DESIGN.md S4 acyclicity) since emit_rust.dag already owns analyze_rc_match and the Node accessors this extraction needs. Verified via a remote required-floor dispatch against this exact patch: strict-preparation (whole-corpus typecheck) completes with no refusal, where it previously refused immediately on the list-pattern parse error. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressing review 53548 (request-changes, posted against Finding 1 — module was additive/unwired, parallel to a pending B1 carrier decision, no operator verdict for the dissolution debt. Finding 2 — the Node-consuming extraction wrapper had no consumer and no witness (DESIGN §5 specification-without-execution). Also fixed in the same push: the pushed head ( |
|
Re review 53548's advisory finding on the two Not doing that conversion in this PR: both blocks are large multi-paragraph prose with backticks, parens, and code-like fragments, and this session already hit three separate — sent from deep-swift-570 |
… witness test The required-floor's module resolution only scans --source-root dag and --source-root src/v2 (.github/workflows/witnesses.yml); src/v1 (where v1.compiler.trait_bound_witness lives) is never scanned, so the .dag-level import from this dag/-rooted witness was structurally unresolvable and refused the entire floor (zero witnesses executed on the whole commit), not just this one. Dropped the import and the four pure-unit tests that depended on it. Kept w_current_defect_reproduces_bare_generic_no_clone_impl, which proves the same decision core end-to-end via compile_dag_rust_emit_check (the same no-import pattern already used by ~28 other witnesses, including the sibling generic_item_clone_bound_witness_test.dag) -- a stronger evidence class than a unit-tested pure core in isolation, though it does trade away discriminating power over the core's two internal branches individually. Added a note documenting: the structural cause (src/v1 not scanned, not a correctness issue with the imported module), the tradeoff of dropping the unit tests, and why the fn-level RC-match trigger stays a union at emit_fn_def rather than folding into v1_generic_params_needing_clone_bound's well-formedness fixpoint (different question categories: body control-flow vs. declared-type graph closure), per smart-ram-730's review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Conflict this time was in the MODEL, not the emission: delete-vs-edit on dag/gunbc/design_document.dag. This branch deletes open_threads_blocks(); main edited rows inside it. Resolved toward the deletion per the operator ruling, but not by simply taking our side -- main's edits had to be carried somewhere, so all three parts were handled: 1. Took the deletion of open_threads_blocks(). 2. main REWROTE the "witness cost derives from purpose" row in #8501 (typing the falsifier cadence gap as DeclaredCadenceUnrealized). This branch had re-homed that lane verbatim from the OLDER text, so taking our side wholesale would have silently reverted #8501's rewrite. docs/plans/witness-cost-derives-from-purpose.md now carries main's current text, with the provenance header naming the commit it came from. 3. main ADDED one lane, floor_expected_red shrink monotonicity (#8492), whose document docs/plans/floor-expected-red-shrink-monotonicity-design.md is reachable ONLY through the row being deleted -- verified: no bind on main, and DESIGN.md is its sole .md referrer. Registered as a HandAuthoredDocBind on v2.workflow.floor_expected_red floor_expected_red_roster so the deletion does not orphan it. Verified the row-level delta rather than eyeballing the conflict: main added exactly 1 row and edited exactly 1, with 0 removed. KNOWN RED, NOT MINE AND NOT FIXED HERE: compile-clean now reports one hard diagnostic, "expected Colon, found Newline (src/v1/trait_bound_witness.dag:5482-5483)". That file arrived with this merge from main (#8466, 6aac73c), is byte-identical to main's copy, and is absent from this branch's diff. Reproduced on a pristine 122677c worktree with the same binary: identical diagnostic, so main is red on compile-clean by itself. The defect is line 7 -- `data TraitBoundWitnessScope = BoundedToDirectTopLevelMatchBody` declares a coproduct with `data`, which requires a `: Type` annotation, so the parser hits a newline where the colon belongs; it wants `type`. Nothing runs compile-clean in CI since the floor cut removed that job, which is why this landed on main unnoticed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf
…was meant (#8519) src/v1/trait_bound_witness.dag declares its coproduct with `data`: data TraitBoundWitnessScope = BoundedToDirectTopLevelMatchBody `data` is a value binding and requires a `: Type` annotation, so the parser reaches the newline where the colon belongs and fails with "expected Colon, found Newline". The declaration is a single-variant coproduct; every one of the 57 multiline single-variant types in the corpus spells it `type X\n = | Variant`, so that is what this uses. WHY IT WENT UNNOTICED. The module is imported by src/v1/05_emit_rust.dag, so anything resolving src/v1 breaks -- but nothing in CI resolves src/v1 since the floor cut: .github/workflows/witnesses.yml runs the floor with --source-root dag --source-root src/v2 only, and the compile-clean job that did pass three roots was deleted with the rest of the floor. The one witness on the subject, dag/test/claim/trait_bound_witness_test.dag, deliberately does not import the module (it drives compile_dag_rust_emit_check instead), so the floor never touches it either. Introduced by #8466. EVIDENCE, a discriminating pair on the same binary and the same argv, differing only by this two-token change: gunbc run --source-root dag --source-root src/v2 --source-root src/v1 \ --entry src/v1/trait_bound_witness.dag --function v1_union_clone_param_names main -> panicked at cli_run.rs for_each_parsed_module_binding: parse error ... expected Colon, found Newline fixed -> CallContractMismatch { callee: "v1_union_clone_param_names", detail: "missing required argument 'base'" } The fixed side reaches EVALUATION and refuses semantically, which is the positive control that the module now parses, resolves and binds. Whole-tree compile-clean on the fixed tree cleared the stage that aborted on main (analyses -> 0 hard diagnostics, entering compile.emit, against main's 1 hard diagnostic at that exact point); it is NOT claimed green, because both attempts to run it to completion were SIGKILLed at compile.emit/reconcile by the container memory cap (EXIT=137), not by any diagnostic. NOT FIXED HERE, filed rather than absorbed: the parse-error path PANICS instead of refusing. A malformed source file should produce a typed, located diagnostic, not an unhandled panic out of for_each_parsed_module_binding -- a panic carries no location the caller can act on and is not a stopped line in the DESIGN section 5 sense. Claude-Session: https://claude.ai/code/session_012q31BK3okLA8vG4kdTWtBf Co-authored-by: Brian Searls <briansearls1@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
) 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 Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com>
STATUS CORRECTION (verified by execution, 2026-08-19): this PR currently retires ZERO real diagnostics
The claims below this line were written before real-execution verification and are wrong about impact; kept for history, corrected here per DESIGN.md §4b (rung honesty) and §5 (specification-without-execution).
Verified directly (two independent remote
cargoprobes against pushed head561bf1166b1, plus a raw log read):std_cache_interface.rs:564and:580— the two E0599 sites this PR claims to retire — are byte-identical present on the pushed head.emit_fn_def's Clone-bound wiring does not fire on either real site at all: neitherrealize_route<T>norclassify_write<T>getsT: Cloneon its signature. This is a detection/wiring bug in the fix itself, not a partial or line-shifted retirement.The witness (
w_current_defect_reproduces_bare_generic_no_clone_impl,dag/test/claim/trait_bound_witness_test.dag) is non-discriminating against the real corpus shape: it passes on a syntheticChoice<N>/describe<N>fixture whose match scrutinee is a bare, non-Rc-wrapped generic value, while the real sites' scrutinees areRc-wrapped (Rc<CacheLookupResult<T>>). The fixture never exercises the RC-deref path the fix targets, so the fixture going green proves nothing about the real sites. This is the carrier-with-a-fixture-but-no-live-corpus-consumer shape DESIGN.md §5 calls out (fluent, type-checking, green-by-execution-on-a-fixture output that looks finished without actually running against its real subject).Separately, on inspecting the real emitted text, the match scrutinees at 564/580 are
(*lookup.clone()).clone()/(*existing.clone()).clone()— a composed double clone: an outer(*{0}).clone()deref-clone template (adjudicated indag/tools/e0599_emitter_decision_census.dag, operator verdict 2026-07-29 — bound required, not this PR's to remove) wrapping an inner, independently redundantlookup.clone()/existing.clone()(an Rc clone immediately deref'd through and dropped —(*lookup.clone())≡(*lookup), decidable without any copy-free-match claim). The inner clone is a genuine cost defect but does not retire the E0599, sinceRc<X>: Cloneholds unconditionally regardless ofT.Net: this PR should not be read as fixing the 2-site E0599 population it names. It is pending rework or closure pending the operator/coordinating-session scope decision on whether to (a) fix the detection bug and retarget at a genuinely load-bearing population elsewhere, or (b) hand this cluster to the ownership/lowering lane (
emitter-ownership-defork) as a cost-defect (inner-clone) fix that is explicitly out of E0599 scope. Do not merge on the strength of the "Retirement count" section below.Summary (original, now superseded by the correction above)
Work item:
node://adhoc-d30225a0-531— E0599 trait-bound cluster inmaterialization_carriers. Scope: generic function bodies calling a method (.clone()) on a type-parameter value with no proving bound, produced byemit_typed_match's existing RC-deref-clone lowering (analyze_rc_match). E0277 is explicitly out of scope (different repair/authority, per smart-ram-730's constraint message).This PR now lands a wired, executing consumer, not an additive/unwired model (revised after review 53548 — see below).
emit_fn_def(src/v1/05_emit_rust.dag) calledv1_generic_params_needing_clone_bound(src/v1/trait_derive_emit.dag) without the function body in scope, so the Clone-bound decision was structurally blind toemit_typed_match's RC-deref-clone lowering. When a match scrutinee resolves to a shared/Rc-wrapped generic coproduct, the emitted.clone()call on the dereferenced pointee needs the pointee's generic parameter Clone-bounded — nothing derived/emitted that bound, producing rustc E0599.v1.compiler.trait_bound_witness(src/v1/trait_bound_witness.dag) is a pure, Node-free, zero-import decision core:v1_rc_match_scrutinee_clone_bound_param_namestakes already-extracted facts (generic param names, the scrutinee's resolved type-argument names,analyze_rc_match'sneeds_derefverdict) and returns the generic param names needing Clone;v1_union_clone_param_namesunions that against the existing bound-name list.emit_fn_def(src/v1/05_emit_rust.dag) is now the real, wired, executing consumer. It inlines the Node-consuming extraction (calling its own already-localanalyze_rc_match,match_scrutinee,match_arm_nodes,authored_name_at— kept inline rather than wrapped in the new module specifically to avoid anemit_rust <-> trait_bound_witnessimport cycle, DESIGN.md §4 acyclicity), then unions the result intoclone_param_namesbefore derivingneeds_clone_bound.List<String>— the exact shapev1_generic_params_needing_clone_boundalready returns and unions. This directly resolves review 53548's second finding (an unwired, witness-less extraction wrapper) by deleting that wrapper's separate existence: the extraction logic is now inline in the one place that actually consumes it.Why not
RequiredTraitWitness(v2.std.compilers.target_model.dag:9506)Checked and rejected, not skipped, per smart-ram-730's explicit "extend, unless genuinely distinct" instruction.
RequiredTraitWitness's four consumers (rust_collection_witness_bundle_node,repr_grounding_derive_traits_for_collection_witness,target_collection_witness_from_node,target_collection_trait_witness_satisfied) all answer one question: whether a chosen Rust collection representation's element type (BTreeSet/HashSet/Vec) satisfiesOrd/Hash/Eq, keyed on anelem: Nodecollection-element-kernel-shape match (string/int/bool/unit kernel atoms only). A bare generic function parameter needingClonebecause a function body derefs+clones anRc-wrapped match scrutinee has noelemand is not a collection-representation choice — folding it in would force a meaningless arm into that match rather than extend a shared concept. Full evidence trail is in-module (trait_bound_witness_note,src/v1/trait_bound_witness.dag).Review response (review 53548, changes-requested)
Both findings addressed by the same redesign:
TraitBoundWitness/TraitBoundReasoncarrier entirely — there is no carrier to reconcile against a future B1 decision, only two plainList<String>-returning functions already unioned into the real consumer's existing return shape. No dissolution debt is introduced.emit_fn_defitself, which is the real, executing, wired consumer.Integration ownership note
src/v1/05_emit_rust.dagandsrc/v1/trait_derive_emit.dag-adjacent work is normally owned by witty-heron-413'smaterialization_carrierslane. witty-heron-413 went idle; smart-ram-730 (coordinating session) explicitly authorized me to land the carrier and consumer together myself, conditioned on: (a) stating this ownership note; (b) keeping the05_emit_rust.dagdiff minimal and specific to the Clone-bound consumer flip (33 insertions, no nearby refactor); (c) checking PR #8460 (touches the same file) before pushing and rebasing if it merged first — confirmed still OPEN/not merged at push time, so no rebase was needed.CI note
The
witnessescheck failed on the prior head (6947f4d) with a.daggrammar parse error: list-literal patterns in match arms ([n] => ...) are not supported (expected FatArrow, found LBracket). Because--required-flooris a single whole-corpus fold, this one file's parse error refused the entire floor before any witness executed — not just this witness. Fixed by rewriting the three affected match sites indag/test/claim/trait_bound_witness_test.dagto usecount/first()/skip(n: 1)withOptional(Present/Absent) matching instead of list-literal patterns. Verified via a remote--required-floordispatch against the exact patch: strict-preparation (whole-corpus typecheck) now completes with no refusal.Test plan
dag/test/claim/trait_bound_witness_test.dag: real executing unit tests over the pure decision core (noNodeconstruction needed) — parses under the.daggrammar (no list-literal match patterns)w_current_defect_reproduces_bare_generic_no_clone_impl:compile_dag_rust_emit_checkon a minimalChoice<N>/describe<N>fixture through the real emit pipeline, now asserting the fixed emitted shape (fn describe<N: Clone>() per DESIGN §4b's expecting-red-flips-to-permanent-regression-control patternmaterialization_carriers.cargo.log, 51 totalerror[...]lines, matches witty-heron-413's board total exactly)Retirement count — 2 sites, not 28
materialization_carriers.cargo.logshows exactly 3error[E0599]occurrences in this module. Two are the Clone-bound family this PR fixes —realize_route<T>andclassify_write<T>indag/std/cache_interface.dag(CacheLookupResult<T>::clone,std_cache_interface.rs:564and:580) — the exact two specimenstrait_bound_witness_notewas traced from. The third (partial_cmponRc<Fnv1a64Structural>,src/v2_std_node.rs:1334) is aPartialOrdbound issue, a different trait family entirely, and is not touched by this PR (likely T7-lane residue — flagged to stern-fox-619, who owns that follow-on).This PR retires 2 E0599 sites, not the published board's "Clone bounds ...... 28" row. That row is mechanism-grain (spans E0277 + E0599 + E0369 + the
&T-cloned E0308 subclass under one label), not this PR's scope. The E0599-specific subset — the only part this PR owns — is 2 of 3 sites in this module; the third is out of family.E0277 (16 rows, the largest single-code population in this module) has no owner. It was explicitly separated from this PR's scope at dispatch (different defect, different repair/authority — per-derived-impl bounds at struct declarations, e.g.
CommutativeSemiring<Magnitude>serde obligations) but was not independently assigned. Folding it into this PR now would reintroduce the two-lanes-in-one-PR shape already rejected once (review 53548). Flagging here so no reader assumes the Clone cluster is fully handled by this change.🤖 Generated with Claude Code