Skip to content

Native resolve: a module sees only its own declarations and imports (no ancestor leak) + the imports that relied on it - #13315

Merged
gunbai-bot[bot] merged 9 commits into
mainfrom
session/calm-ant-675-no-ancestor-leak
Oct 5, 2026
Merged

gunbai-bot[bot] merged 9 commits into
mainfrom
session/calm-ant-675-no-ancestor-leak

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Step 2 of R1 (docs/plans/native-resolve-frontier-root-causes.md). Implements the operator ruling of 2026-10-04: no shadowing, and a module sees only its own declarations and its own imports. Step 1 was the identity dedup in #13205, which has merged.

The wall

  • Index: v2.std.symbol_index SymbolIndex gains module_roots: Map<QualifiedName, ModuleRootDeclared>. It is written by v2.compiler.symbol_index_fill symbol_index_fill_module_declarations (via symbol_index_mark_module_root) for every module it fills.
  • Walk: symbol_index_lexical_collect stops ascending at the referencing module's root. Before this change it continued into ancestor module positions, so extdeps.bmc.access saw every import and declaration of extdeps.bmc.
  • Callers: unchanged. The boundary is a fact of the index, so resolve_expected_rename_chain (which passes a declaration path) gets it too.

Controls: v2.test.claim.binding.module_scope_no_ancestor_leak

Both modules are filled through symbol_index_fill_module_root, the parent's import is bound through symbol_index_bind_at, and every claim runs the real walk and the real selection.

claim before (dedup only) after
an_ancestor_modules_import_is_not_in_scope_in_a_contained_module FAIL PASS
an_ancestor_modules_declaration_is_not_in_scope_in_a_contained_module FAIL PASS
a_modules_own_declaration_is_in_scope_from_inside_it PASS PASS
a_modules_own_import_is_in_scope_in_that_module PASS PASS
  • Existing claim files: I ran every claim file that touches the symbol index lexical walk or its fill (seed gunbc run --claim-run). Their outcomes are the same with and without this change. Two files carry 4 failures that are already red without the wall:
    • normalize.operation_requires_edge (2)
    • provenance.loaded_carrier_receipts (2)

The dependents land with the wall

This follows calm-boar-904's condition: the dependents are fixed in the same motion, not handed off.

  • Census at the wall-only head: v2-native-census run 37198026753, compared by (module, name, reason) against Native resolve R1: lexical candidates keyed by declaration identity #13205's head run 37186602915. Same base, so there is no main drift.
    • 41 new refusal keys in 9 modules: 8 real modules, plus this PR's own control file, which follows its siblings in carrying no imports.
    • 91 refusal keys gone: one per module, each an extdeps_external_authority_anchor ambiguity between a child module's anchor and its parent's.
    • N7 closure (145 modules of v2.test.claim.parse.expression_bodied_fn_decl_parse): refusing modules 75 → 75.
  • Why the migration isn't limited to those 8 modules: the census population is a lower bound, because an already-refusing module can hide further leak use. So the migration is derived over all 1,042 contained modules.
    • Rule: a name the module uses, neither declares nor imports, and that an ancestor module imported or declared gets the import the leak supplied, from the same source module. Resolution is therefore unchanged by construction.
    • Result: 38 modules, 62 names. This covers all 8 census modules.
    • Cycles: no new import edge creates a cycle (checked by reachability over the import graph).
  • Census at this head: run 37238053116. The before/after delta will be posted as a comment.

🤖 Generated with Claude Code

gunbc-ci-auto-heal and others added 9 commits October 4, 2026 03:45
symbol_index_lexical_collect drops a candidate whose declaring path is already in the
population, so one declaration reached by its own import plus an ancestor's import (or the
ancestor that declares it) is one candidate. Distinct declarations under one name still
refuse as ambiguous. Controls in v2.test.claim.binding.lexical_candidate_declaration_identity.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…no ancestor leak)

SymbolIndex records each module's root (module_roots, written by symbol_index_fill);
symbol_index_lexical_collect stops ascending there. Operator ruling 2026-10-04.
Controls: v2.test.claim.binding.module_scope_no_ancestor_leak.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…38 modules, 62 names)

Derived, not hand-picked: for each module with an ancestor module, every name it uses that it
neither declares nor imports and that an ancestor imported or declared gets the import the leak
supplied (same source module, so resolution is unchanged). No import cycle introduced.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t block; doubled comma)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t-free files

Declaring an import turns a file's bare channel off (UnimportedBareProvider); each name here
is imported from the provider the floor named.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…field

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… stated condition

The arm read 'if [nesting-granted scope] is not [kept], these references become residual and this
arm dissolves'. The 2026-10-04 ruling answers that: a module sees only its own declarations and
imports. The nesting control now asserts the stripped nested reference is the one residual row.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… the three symbol_index presence checks

Review 76019: symbol_index_is_module_root_at copied the hand-rolled Optional-to-Bool shape of its two
siblings. All three now read map_contains_key.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 76019's finding in ba25306. I added one map-membership predicate, v2.std.collection map_contains_key, next to map_lookup. All three symbol_index presence checks now read it: symbol_index_is_module_root_at (the new one), symbol_index_binds_namespace_body_at and symbol_index_declares_resource_at, so the existing copies are gone too. The leak controls still pass (4/4).

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit cdd124d Oct 5, 2026
4 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/calm-ant-675-no-ancestor-leak branch October 5, 2026 05:15
@briansrls
briansrls restored the session/calm-ant-675-no-ancestor-leak branch October 5, 2026 05:15
gunbai-bot Bot pushed a commit that referenced this pull request Oct 5, 2026
After #13315 gave analyze.dag explicit imports, emit-build refused three element binders
(no field 'label'/'target' on type 'T') at the List<Edge> walks this PR added via a Cons
match and list_map. They now use the fold(.., fn(acc, e)) form every other edge walk in the
file uses, prepending and reversing once. fold_analysis 23/27 (4 rostered), compile gate 4/4.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants