Repository navigation
fix(sync): an import a name match took follows its module once it resolves another way - #2752
Merged
Merged
Conversation
…olves another way
A named import that a name match linked to a declaration elsewhere while
its module did not export the name kept that link after an edit gave the
module the name. A barrel's `export { EmptyState } from './EmptyState'`
declares nothing, so no definition delta, failed-ref retry or barrel walk
saw it (the "retargets" PR #2744 left). A name match into the module
itself, a class exported only as its default and imported by name, kept
its link after the default was dropped, where a fresh index finds nothing.
reopenNameMatchedImports (module-importers.ts): for each module the sync
modified, its importers' binding edges that resolved some other way than
their import (getImportBindingsResolvedElsewhere, an indexed range per
importer), whose import statement names this module, are resolved again
against the graph as it is (ReferenceResolver.resolveNow, writing
nothing). When the answer differs, the importer's edges through the name
and into the members of what the name match took go back for the sweep.
Regression test sync-name-matched-imports.test.ts: 4 cases comparing the
synced graph with a fresh index of the final tree. Fresh indexes are
byte-identical on 8 repos.
Co-Authored-By: Claude Opus 5.5 <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.
Problem
A named import that a name match linked to a declaration elsewhere, while its module did not export the name, kept that link in
codegraph syncand the file watcher after an edit gave the module the name. A fresh index of the same tree links it through the module:This is the residue PR #2744 handed over: the "retargets" in its gut runs (folo
EmptyState/CollapseCssthroughlayouts/shared/index.tsandui/collapse/index.ts, vbenBreadcrumb, miloInvocationProvider/ArtifactsProvider), plus about 670 lines where the target was the same and only theexact-match/importmetadata differed. PR #2749 found the mirror image:import { Widget } from '../x', wherex.tsexportsWidgetonly as its default, is matched by name to that class; once the edit drops the default, a fresh index finds nothing.Re-verified on main fc293f0 (CLI probes, sync vs a fresh index of the final tree):
export { default as Widget } from './impl')exact-match→import)Cause
A binding a name match linked never failed, so no retry looks for it: neither #1240's retry nor PR #2744's module keys, which park only failed bindings. The edit that makes it stale declares nothing: a re-export or an export clause gives no definition delta for the CG-33 rebind, and adds no file for the PR #2512 walk. The PR #2560 / #2665 walk below a barrel starts only from files added or whose definitions changed.
Fix
reopenNameMatchedImports(module-importers.ts), run after the default-export step for each JS/TS module the sync modified:importsedges into the module's file node: one indexed lookup.getImportBindingsResolvedElsewhereselects those importers' binding edges that resolved some other way than their import: an indexed range per importer, and few rows. For each, the importer's own import statement, from the sync's import reader, must name this module.ReferenceResolver.resolveNowresolves the binding again against the graph as it is, writing nothing. A binding that resolves exactly as before stays, for example a name match a fresh index makes too (bulletproof'snextjs-pagesapp has 24 such bindings).w.run()withw: Widget). Only edges bound before this sync resolved anything are taken.Validation
Tests.
__tests__/sync-name-matched-imports.test.tshas 4 cases. Each indexes a tree, edits it, syncs, and compares edges and unresolved refs with a full index of the final tree in a folder of its own:On main fc293f0 the 3 change cases fail and the no-op passes by design. All 4 pass with the native kernel (
CODEGRAPH_KERNEL_EXPECT=1) and withCODEGRAPH_KERNEL=0. The 12 neighbouring sync suites pass (93 tests).Mutations, each restored from the committed tree:
srcFresh indexes.
codegraph init -ywith the native kernel, compared withscripts/dump-graph.mjs, is byte-identical between main and this branch on 8 repos: vben, folo, milo-ui, excalidraw, bulletproof-react, outline, react-native, bitwarden. The change only runs in a sync.Gut runs, PR #2744's scenario. From each arm's own fresh index, every 7th JS/TS file is emptied (
export {};), synced, restored and synced again, then compared with that arm's own fresh index of the untouched tree. Differing dump lines:What's left is the same on main:
DocumentScenebinding. The rest are second-order: calls on typed receivers (context.t) and statics through model classes whose base class was gutted (User.createWithCtx).cn, 895 references past the by-name retry's 500 ceiling while its file was gutted.Cost, the
sync-name-matched-importsphase fromCODEGRAPH_SYNTH_TIMINGS. Body-only edits of the most-imported modules (PR #2749's picks: 12 in outline, 12 in react-native, 11 in bitwarden) re-open nothing and cost 64, 7 and 4 ms per sync. Sync wall times are within run-to-run noise of main's.Full suite: on 1335c9b (this branch on main bbf987e), with the native kernel built and
CODEGRAPH_KERNEL_EXPECT=1,--maxWorkers=4: 689 test files passed, 6 skipped; 8,226 tests passed, 82 skipped, 0 failed.Not in this PR
import→exact-match, same target), and it does not occur in PR fix(sync): a name imported through a barrel links when an edit gives the barrel its re-export #2744's gut-and-restore runs.🤖 Generated with Claude Code