Repository navigation
An explicit import silently discarded by a same-name local definition: refuse it - #9166
Conversation
Checkpoint so the shared worktree is free for #9079. Not final: the fixture witness test and the re-export census arm are still outstanding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both execute: the red returns true (the fixture refuses) and the control returns true (the correct spelling still compiles). The red's attribution is checked separately rather than trusted -- compiling the fixture text directly produces exactly one hard diagnostic and it is ImportShadowedByLocalDefinition, not an incidental typo, which is the failure the harness's collapse-to-Bool invites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review 55680, finding 1, confirmed by execution. get_exported_names treats module-scope names as item names PLUS coproduct variant names; the collision set used item names alone, so a local variant could still silently discard an explicit import of the same name. Measured before the fix: a module importing std.algebra's trim and declaring a variant named trim compiles with 0 diagnostics. This is the total-at-the-level-examined class DESIGN names, authored into the wall meant to close a neighbouring instance of it: the judgment was exhaustive over the question it asked (is this an item name?) and never descended to the question that decides the outcome (is this a module-scope name?). The fix derives the set from the SAME two helpers get_exported_names uses, rather than keeping a second notion of module-scope name beside it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rows Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…easure the hand-Rust receipt review 55686 is correct about the head it read. The previous commit pushed the variant fix in src/v1/03_resolve.dag WITHOUT its regenerated mirror, so at 9c4638c the authority refused a collision the executing Rust seed accepted -- a real .dag/Rust divergence, and the one class DESIGN section 7 names. The lesson is not that regen would have caught it later: the pushed head is what every other lane measures, so the authority and its mirror have to move in one commit. They do here. MIRROR (v1_compiler_resolve.rs, installed from the regen candidate, not hand-written): local_variant_names via get_variant_names, concatenated with local_item_names. Measured on the rebuilt binary -- a module importing std.algebra's trim and declaring a VARIANT named trim went from 0 diagnostics to a located refusal; the distinct-name control still compiles at 0. WITNESS: import_shadowed_by_local_variant_must_refuse enrolled beside the item red and the positive control. All three execute and return true. RECEIPT (review 55680, finding 2): the CompilerDiagnostic hand-Rust gate receipt named five variants from an earlier lane and pinned figures measured against that lane's base. It now names ImportShadowedByLocalDefinition as the sixth and carries three figures re-measured on this head, each naming the command that produces it: the carrier census is FLAT (884 at origin/main, 884 here -- the recorded 747 no longer reproduces, which is exactly the rot the receipt exists to expose), 4 added / 0 removed, and 0 added lines declare a fn. --required-regen: first_generation_equal=true. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s the counterexample
CI refused src/v2/std/grammar.dag under the widened predicate (run
32811162568). That module imports the `Optional` TYPE from v2.std.optional and
separately declares GrammarExpr's variant `Optional { element: GrammarExpr }`.
Main's head run c271b75 is green on it, and its three bare `Optional<...>`
annotations resolve to the imported type -- so a variant constructor and an
imported type sharing a name do not shadow each other.
review 55680 asked for variants on the ground that get_exported_names counts
them as module-scope names. That is true of EXPORTS and not of BINDING, and
the widening was implemented, measured, and is reverted here on the evidence.
Rewriting a load-bearing std module to satisfy the wall was the alternative; a
wall the corpus must be worked around is a wrong model, not a strict one.
WHAT SURVIVES: item-name collisions, which is where both real specimens live
and where the harm was demonstrated -- an explicit import discarded, the call
recursing into itself, death at the depth wall.
WHAT IS NOT CLAIMED: a local variant sharing an imported FUNCTION's name is
unrefused and unmeasured. It may well be harmful; this predicate cannot decide
it, because the predicate compares NAMES and the distinguishing fact is KIND.
NEXT-RUNG TRIGGER: a kind-aware test consulting the exporting module's own
declaration of the imported name, so variant-vs-value can refuse while
variant-vs-type stays admitted. resolve_import already holds the target module
node, so the fact is reachable; deciding it is its own change with its own
evidence.
The variant RED is replaced by a control asserting the counterexample STAYS
admitted, so a future re-widening fails here rather than in CI.
Measured after the revert, on the rebuilt binary: variant fixture 0
diagnostics; item-shadow fixture still refuses; src/v2/std/grammar.dag 0
blocking errors. --required-regen: first_generation_equal=true.
CENSUS CORRECTION: the population is 3 sites, not the 2 I reported. Both
textual scans matched fn/func/data only and could not see `type` or variant
declarations, so grammar.dag was invisible to them. The compiler's own census
is the authority; the scans were narrower than they sounded.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Update on review 55680's first finding: I implemented it, CI measured it against the corpus, and it is reverted in The argument was: The corpus disagrees. Under the widened predicate the required floor refused that module (run The distinction the finding missed is that Why revert rather than repair the module: the alternative was rewriting a load-bearing What survives: item-name collisions — where both real specimens live and where the harm is demonstrated (explicit import discarded, call recursing into itself, death at the depth wall). What is explicitly not claimed: a local variant sharing an imported function's name is unrefused and unmeasured. That case may well be harmful; this predicate cannot decide it, because it compares names and the deciding fact is kind. Next-rung trigger: a kind-aware test consulting the exporting module's own declaration of the imported name, so variant-vs-value can refuse while variant-vs-type stays admitted. The variant RED is replaced by a control asserting the counterexample stays admitted, so a future re-widening fails in this witness rather than in CI. Census correction, mine: the population is 3 sites, not 2. Both textual scans (mine and the independent one) matched Measured after the revert: variant fixture — sent from loyal-lynx-169 |
…nt of the law From the session that executed the cut, sent independently of deep-ant-102. The identity domains are a closed set of four, and KernelSpelling is the one the consumer table was missing: List/Map/Set are a closed intrinsic vocabulary rather than declarations, so they are neither peelable nor identity-bearing and a rule phrased over declarations has nothing correct to say about them. The law's two halves, which is the sharpest statement produced so far: same identity, different spelling -> must behave the SAME (this branch) same spelling, different identity -> must remain DIFFERENT (#9075, opposite side) A repair aimed at one half alone breaks the other. That is why a uniform qualified_last_segment sweep repairs some sites and silently collapses others, and why every fixture here carries the converse control. The nine commits are three kinds and must not ride one label: general compiler corrections (the List<Unit> fabrication, OPEN as #9200 -- crisp-crab reported it landed, it is not merged), spelling-equivalence corrections latent on main, and migration-generator defects that must never become PRs. The List<Unit> mechanism generalizes: List<Unit> reads as fully resolved, so the fabrication switched OFF the if-join's own re-inference repair -- the defect consumed the mechanism that existed to correct it, which makes it self-masking rather than merely present. G gains a disposition it cannot skip. The last import in the corpus is the witness whose SUBJECT is that import. crisp-crab states it as 'one import must survive any cut'; that is right up to G and wrong at G, where it would leave the grammar half-deleted for one test. It retires with its subject carrying a receipt, per DESIGN section 3 step 6, and #9166's refusal retires with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An author writes
import M { f }, then definesfn fin the same module. Every bare use offbinds the local definition; the import is discarded and nothing says so. Where that definition's body callsf, it calls itself.Found, not searched for
Two identities began executing for the first time when a floor change landed (royal-cat-509, run
32794539384), and both died at the call-depth wall:Each imports the symbol it means to call and then shadows it with a same-named
test fn. This is not the "symbol is absent" arm — the symbol exists and is named in an explicit import, and those two arms have opposite repairs.The mechanism, on a two-module fixture
target_symbol+ definefn target_symbol0 diagnostics0 diagnosticstrueThe compile column is the sharper half: before this change the acceptance path produced identical output for the correct program and the self-recursive one. It was not that a wrong-symbol diagnostic was missing — nothing distinguished the two programs at all. The depth refusal even names the possibility ("a bare-name resolution cycle, or a genuinely divergent chain") without being able to say which, because the binding decision is thousands of frames behind it.
The wall
v1.compiler.resolveresolve_importalready owns per-child, per-span import diagnostics, so the check lives there and reuses those spans rather than minting a second location scheme. NewCompilerDiagnosticvariantImportShadowedByLocalDefinition:It names both bindings and which one won — the whole harm is that the answer was not the one the author wrote down, so a message saying only "ambiguous" would reproduce the original silence in a louder font.
Adding the variant broke two deliberate total matches in
cli_run.rs(compile_clean_diagnostic_histogram_key, documented "no silent widening"). That build break is that design working; both arms are classified rather than wildcarded.The two call sites this forces
Repaired by qualifying the call and dropping the colliding import, so the enrolled identities keep their names and no roster in
witness_deferral_freeze/ci_layer_roots/dag_compile_clean_scopechanges. Verified by execution:lever_a_local_receipts_holdnow returnstrueinstead of recursing.Scope, measured rather than assumed
import Mwith no brace list, followed by a local definition, still compiles clean — measured,0 diagnostics. With a glob the author never wrote the name, so the local definition winning is ordinary shadowing rather than a discarded instruction. Next-rung trigger: a census predicate that can see glob imports at all. Both textual scans used to size this class matched brace-form only, so that arm's population is unknown rather than zero.Evidence
dag/test/claim/import_shadowed_by_local_definition_witness_test.dag— a discriminating RED and a positive control, both executing and both returningtrue.The RED's attribution is checked separately rather than trusted:
compile_dag_rust_emit_checkcollapses a compile to aBooland goes true on any hard diagnostic, a typo included — the seed's own registry comment names that failure. Compiling the fixture text directly produces exactly one hard diagnostic and it isImportShadowedByLocalDefinition; the control compiles at0 diagnostics.Rung: structurally guaranteed on the source→
.dag-acceptance path — noAcceptedprogram can contain the collision. That is a claim about that path only.--required-regen:first_generation_equal=true planned=135 executed=135.