Repository navigation
Filter a module's own coproduct-variant names out of the emitter's import candidates - #9461
Conversation
…port candidates
reference_derived_use_lines already filters candidates against the names the
module declares, and its own note names the hazard: the bare-name registry is
last-write-wins across the closure, so a homonym in another module steals the
row and a self-colliding `pub use` is synthesized. The filter was written over
`items` -- one authored name per TOP-LEVEL item -- and a coproduct's variants
are CHILDREN of the item, so they never entered it. The guard was at the wrong
grain, not missing.
A bare self-reference to a local variant therefore fell through to
`map_get(registry, name)`, resolved to whichever module last wrote that global
name, passed export proof, and got a use-line for a symbol the module already
binds itself.
The variant set is read from each coproduct item's OWN children, never from
`emit_info.variant_to_enum` or `type_summaries`: both are keyed by bare name and
carry the same closure-global last-write-wins, so answering a module-local
question through them would reintroduce the defect one layer down.
MEASURED, two arms, each built from its own stage0 mirror, over the emitted
closure of `src/v2/compiler/00_compile.dag` (175 files):
membership delta: exactly one file, `v2_std_node.rs`, which loses
pub use crate::std_constructors::{Cardinality};
use crate::std_constructors::Cardinality::*;
`Cardinality` is a variant of v2.std.node's own `Connective`; the homonym is
`std.constructors`' `type Cardinality = Required | Optional`. Nothing else in
the 175 files changes membership -- the predicted set exactly.
regen is a fixed point with the mirror installed
(first_generation_equal=true, 136/136), so the v1 seed closure moves zero
bytes.
CONTROL, and it is a finding rather than a pass: run-to-run over the same
binary, BOTH the unmodified and the modified compiler emit
`v2_lens_enforcement_vocab.rs` and `v2_std_cross_tree_resolution.rs` with
import blocks in varying ORDER (identical membership). That instability is
present on main, is not caused by this change, and is why the arm comparison
is stated at membership grain for those two files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # src/v1/05_emit_rust.dag # src/v1/stage0/src/v1_compiler_emit_rust.rs
|
CI red here is main's red, not this PR's. Same failure signature, both phases, all six errors identical: main run The witnesses lane has been failing on main for the last five commits ( |
|
Two updates now that this is out of draft. The reordering class is independently confirmed. The control finding in the body — that both the unmodified and the modified compiler emit The CI red is main's, and both halves are owned. Per the comparison above: the duplicate declaration in |
|
Executed-specimen gap from node adhoc-0a3a31ae-4ee: I ran this PR head ( pub use crate::v2_std_collection::{empty_map};
pub use crate::v2_std_optional::{Optional};So the direct local-coproduct-child filter fixes the PR’s measured |
Same resolution as the previous merge: the single conflict was a generated mirror, zero .dag conflicts, so stage0 was reduced to exactly main's and the merged .dag emitted the union. Six mirrors move again -- emit_core_support, emit_go and emit_python alongside the three that git flags -- because adding CompilerDiagnostic variants changes emitted match arms in every emitter matching on it. Picks up #9461, which filters a module's own coproduct-variant names out of the emitter's import candidates. That is this branch's candidate set, so the export-proof population is re-measured on this tree rather than carried across; the prediction published before measuring is that the 87 is unchanged (all dotted cross-module, where #9461 removes bare local self-references) and that registry-absent shrinks.
Recomputed at 5a62da7 (RUNNER_HEAD confirmed on the runner), replacing the be89e23-based bytes: a mirror computed at a stale base is stale whatever CI later reports, so the base window was closed rather than waited out. Two-generation procedure, pristine tree at that head: gen-0 first_generation_equal=false, drift in exactly these four files install candidate, REBUILD claim_executor gen-1 first_generation_equal=true The rebuild between passes is the point: a single pass verifies an emission against a binary that predates it. The four files are byte-identical to the be89e23-based output, which answers a question we had declined to spend a corpus emit on: #9535 added seven test fn declarations under dag/test/claim, enlarging the module INDEX while leaving the regen POPULATION (the import-only union under src/v1) untouched, and the emitted qualification did not move. #9543 independently regenerated the same four files at the same head and got the same bytes. Two independent negatives on index-sensitivity at this grain. Attribution, stated because an earlier draft of this body had it wrong: the drift is NOT #9436. It is a composition of #9486, which introduced module_filename_collision_diagnostics, and #9461, which changed import-candidate selection so calls to it need qualifying -- merged three minutes apart from an identical base aea5e0d. Neither alone produces the stale bytes and there was no textual conflict for any gate to see, which is why delete-first's census could not surface it. Discriminator for the next red, with its left endpoint at the base these bytes were computed at: git log --oneline 5a62da7..origin/main -- 'src/v1/**/*.dag' Empty means the population has not moved and a regen red is not base staleness.
…qualification is closure-dependent, not hand-carried wrong (#9551) `required-regen` has refused every main push for six hours: required-regen: first_generation_equal=false planned=138 executed=138 declared_divergent=1 [main.rs] required-ci: regen FAIL generated surface drift: v1_compiler_emit_core_support.rs, v1_compiler_emit_go.rs, v1_compiler_emit_python.rs, v1_compiler_emit_rust.rs THIS IS NOT A MISSED REGENERATION, and the framing matters because the obvious reading teaches the wrong lesson. #9486 hand-carried these mirrors alongside its `.dag` edits and its own PR run was GREEN on regen -- run 33109524057 at head c84f377, `first_generation_equal=true`. The bytes it committed were exactly what the emitter produced ON ITS BASE. Nothing about them changed between that run and the merge. WHAT THE DRIFT ACTUALLY IS. The emitted call spelling is CLOSURE-DEPENDENT. Every one of the 28 changed lines here is one class: a bare call name where the emitter now renders a fully-qualified `crate::<module>::<fn>` path. - let filename_collisions = module_filename_collision_diagnostics(typed.clone()); + let filename_collisions = + crate::v1_compiler_emit_core_support::module_filename_collision_diagnostics( + typed.clone(), + ); The bare form compiles -- the file's own `pub use crate::v1_compiler_emit_core_support::{...}` re-export brings the name into scope -- so the committed mirror was correct Rust with correct semantics. It simply is not what the emitter renders once the surrounding closure changed. Two changes, each green on its own base, red in combination: a semantic merge conflict, which no per-PR gate can see BY CONSTRUCTION, since each PR's regen runs against a closure the other has not joined yet. ONE MEASURED DETAIL THAT NARROWS THE MECHANISM. The `pub use` header in `v1_compiler_emit_go.rs` is UNCHANGED between committed and candidate -- the import list still carries `module_filename_collision_diagnostics` -- and the call renders qualified anyway. So the emitter's qualification decision is not simply "is this name in the import list", and any future explanation of this class has to account for that. #9461 (58d10ac, emitter import candidates) is the natural other half of the pair and three of the emit_rust sites are its code, but this PR does not assert that as established. CHANGED-LINE CENSUS, complete, nothing elided: v1_compiler_emit_core_support.rs 4 authored_name_at, make_error_node -> crate::v1_std_core:: v1_compiler_emit_go.rs 5 module_filename_collision_diagnostics v1_compiler_emit_python.rs 5 same v1_compiler_emit_rust.rs 14 same, plus is_type_def_item / is_coproduct_type / authored_name_at NO AUTHORITY EDIT. The `.dag` is correct and untouched. Only the emitted mirror bytes moved, and they were installed VERBATIM from `target/stage0-regen-candidate/src` -- no hand-editing of emitted output, which is the move that created this situation and must not be the move that resolves it. EVIDENCE BY EXECUTION, with a non-stale instrument. `claim_executor` was built locally from main tip b6003a4 (verified equal to `origin/main`, not an ancestor), finishing 01:08 UTC -- so it postdates every commit it measures. Three separate lanes reported findings from stale binaries on this defect tonight; this one cannot be. BEFORE: first_generation_equal=false, FAIL naming those four files AFTER: first_generation_equal=true planned=138 executed=138 Also verified: `cargo fmt --all --check` clean (no collision with the fmt gate, which consumes the next formatter pass of this same artifact), and `cargo build --release -p v1-compiler` clean under `RUSTFLAGS=-D warnings`. Regen deleted no orphan content and reached nothing under `dag/extdeps`: `changed_paths` named exactly these four files and every other emitted file in the 138 already matched. DURABILITY LIMIT, STATED SO IT IS NOT LATER READ AS AN INEFFECTIVE FIX. This mirror is correct for the closure at b6003a4. If a PR landing ahead of this one changes that closure again, the mirror re-drifts and the gate refuses again through no fault of this change. That is a property of the class -- emitted bytes are a function of the whole closure, and the merge queue does not serialize on it -- not a defect in this repair. Claude-Session: https://claude.ai/code/session_01KQiX51cUPte6qm5zQfrbfr Co-authored-by: gunbc-ci-auto-heal <gunbc-ci-auto-heal@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The defect
v1.05_emit_rustreference_derived_use_linesfilters import candidates against the names the module declares. Its own note names the hazard exactly: the bare-name registry is last-write-wins across the closure, so a homonym elsewhere steals the row and a self-collidingpub useis synthesized (the E0255 class).The filter was written over
items— one authored name per TOP-LEVEL item. A coproduct's variants are CHILDREN of the item, so they never entered it. The guard was at the wrong grain, not missing.A bare self-reference to a module's own variant therefore fell through to
map_get(registry, name), resolved to whichever module last wrote that global name, passed export proof, and got a use-line for a symbol the module already binds itself (viause self::Enum::*;, or natively for a grounded alias).The fix
local_coproduct_variant_namesreads the variant set from each coproduct item's own children, and the result joins the already-filter.Deliberately not via
emit_info.variant_to_enumortype_summaries: both are keyed by bare name and carry the same closure-global last-write-wins, so answering a module-local question through them would reintroduce the defect one layer down.Measurement — prediction stated first, then measured
Predicted delta: only REMOVALS of
pub uselines whose imported name is a variant of a coproduct declared in the emitting module. Nothing else.Two arms, each built from its own stage0 mirror (distinct sha256), over the emitted closure of
src/v2/compiler/00_compile.dag— 175 files:v2_std_node.rslosesCardinalityis a variant of v2.std.node's ownConnective; the homonym that stole the registry row isstd.constructors'type Cardinality = Required | Optional. The measured set is the predicted set.claim_executor --required-regenreportsfirst_generation_equal=true planned=136 executed=136.READ THIS BEFORE TRYING TO REPRODUCE ON A SMALL ENTRY — you will get zero, and the fix is not inert
The class is closure-size-dependent. Measured:
--entry dag/std/algebra.dag(7 files) and--entry dag/std/node.dag(15 files) both show a zero delta on both arms.That is the mechanism, not a weakness. The escape requires the homonym to be present in the same closure to win the last-write-wins registry row; an entry-scoped closure usually excludes it, and then the pre-existing
info.module_name != this_module_namecheck already skips the name. So a per-entry emission can be clean while a wide one fabricates — and any population of this class measured at entry grain is complete over the wrong subject while looking complete.Control failed, and that is a separate finding
Run-to-run over the same binary, BOTH the unmodified and the modified compiler emit
v2_lens_enforcement_vocab.rsandv2_std_cross_tree_resolution.rswith import blocks in varying order (identical membership) — three runs of one binary differ. This instability is present on main at 38a127b, is not caused by this change, and is why the arm comparison above is stated at membership grain for those two files. It is an unordered-iteration defect (an unordered collection where an ordered one belongs); it is routed to its own owner and is not chased here. Note the scope: it makes empirical byte-comparison controls unreadable on those two files; it says nothing about structural byte-identity arguments.Re-measured after merging main
The branch was merged onto
3bbd53c05a(which includes #9439's four-armed disposition refactor of this same function; the conflict in05_emit_rust.dagwas resolved by keeping main's function and re-applying this change on top, and the stage0 mirror was regenerated, never hand-resolved). The two-arm delta was re-run against that base:v2_std_node.rsloses the same two lines — unchanged result.v2_std_cross_tree_resolution.rsshows an order-only change with identical membership. That is one of the two files named in the control finding above, so it is instrument noise, not a delta.claim_executor --required-regen→first_generation_equal=true planned=138 executed=138, and--required-regen-fixed-point→fixed_point_equal=true referenced_at=3bbd53c05a.First measurement was taken against
38a127bd60; the re-measurement above is against3bbd53c05a.