Repository navigation
fix(resolution): a call reaches the caller's own copy of a duplicated module - #2923
Merged
Merged
Conversation
… module A module kept in several copies, like two Android build flavors that each hold one package, gives every name in it one candidate per copy. Directory proximity is capped at six shared folders, so in a deep tree the copies scored the same and the first indexed won: a call in one flavor could land on the other flavor's copy. And since the name is not in doubt among copies, only which copy, scoring the right pick by proximity (0.7, or 0.4 in a shallow tree) understated it. findBestMatch now breaks a tie among copies of one symbol (same qualified name, kind and file name) toward the copy whose file shares the longest directory prefix with the reference, when that copy is uniquely closest. matchByExactName scores that pick 0.8: above a proximity guess, below a unique name or the file's own definition (0.9, which keeps precedence). On top of the PR: the copies are judged among the candidates findBestMatch weighed, so a namesake in another language no longer blocks the 0.8; a match of the caller itself keeps the proximity score, as it does for a same-file match; and Kotlin and Java flavor tests, since the PR's Python fixture calls a function it never imports. Validation, main (cde4cd7) vs this commit, 21 repos in 11 languages: no edge added or removed; 4 targets moved, all right: spring-modulith's example-full tests now reach example-full's own OrderManagement and OrderCompleted, where Spring's naming rule (0.7) took the epr-jdbc example's copy, and playframework's shutdown-happy-path test its own src/main HomeController, not the copy its script swaps in later. 109 edges keep their target and go to 0.8, each onto the copy in the caller's own module or app; 4 of them are vben's `defineConfig` calls from `import { defineConfig } from 'tsdown'`, which main already links to a namesake because that dependency is declared only in the root package.json. In 18 sites the 0.8 now beats a framework rule's 0.7. Index CPU, median of 3 interleaved: okhttp 34.8 vs 34.8 s, etcd 36.5 vs 36.5 s. Tests: 3 of 6 fail on main, 2 on the PR as written. Original PR #1936 by @danusha2345. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 27, 2026
danusha2345
pushed a commit
to danusha2345/codegraph
that referenced
this pull request
Oct 28, 2026
…mchenry#2972; 10 landed PRs yield to main; EXTRACTION_VERSION 56) Main landed ten more of our PRs as our own commits with the maintainer's additions (colbymchenry#2923 ← colbymchenry#1936, colbymchenry#2924 ← colbymchenry#1951, colbymchenry#2925 ← colbymchenry#1939, colbymchenry#2930 ← colbymchenry#2921, colbymchenry#2940 ← colbymchenry#1736, colbymchenry#2942 ← colbymchenry#1947, colbymchenry#2950 ← colbymchenry#1952, colbymchenry#2961 ← colbymchenry#1954, colbymchenry#2965 ← colbymchenry#1942, colbymchenry#2972 ← colbymchenry#1933) and the module-path half of colbymchenry#2308 (colbymchenry#2955). Where the integration carried our version, main's wins: the enclosing-scope member pick and its tests, the Go and Java receiver typers, the delegating-call self-loop rule, the duplicated-module copy confidence, the C #if collapse, the module-path resolution of Rust paths, the trail/path hardening in utils.ts. Kept beside main: colbymchenry#1875's Verilog hooks and status fields, colbymchenry#2839's implementations command, colbymchenry#1923, colbymchenry#2922, colbymchenry#2258, colbymchenry#1761, colbymchenry#1583, the bare-call half of colbymchenry#2308 (rust-use-bindings.ts). CHANGELOG is main's plus the open PRs' bullets. EXTRACTION_VERSION 56: the C #if group handling (colbymchenry#2940) and the kernel change. Co-Authored-By: Claude Fable 5.1 <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.
Summary
When a project keeps copies of the same code in several folders, like Android build flavors that each hold one package, or several example apps that each carry the same packages, a call now reaches the copy in the caller's own folder. In a deep tree it used to take whichever copy was indexed first, and the right pick was understated as a proximity guess.
Root cause
findBestMatchscores directory proximity at 15 points per shared leading folder, capped at 80, so two copies that both share six or more folders with the caller tie, and the first one indexed wins. InmatchByExactNamethe winner's confidence came from proximity alone (0.7, or 0.4 in a shallow tree), though among copies of one symbol the name is never in doubt, only which copy.Reproduced on main with a realistic layout: two Kotlin flavor source sets
platform/apps/mobile/android/app/src/rc2/…and…/src/rcpro2/…holding one package. Both flavors'run()linked to rc2'scompute(), and rcpro2'snew Util()in Java to rc2's class.What came from the contributor, and what we changed
From @danusha2345 (#1936):
closestCopyandsharedDirDepth, the tie-break infindBestMatch(only among candidates that tie on the full score, and only when every one is a copy: same qualified name, kind and file name), the 0.8 confidence, the Python tests, and the CHANGELOG bullet.Ours, on top:
findBestMatchweighed. It skips other languages whenever a same-language candidate exists, so the tie-break already ignored them, but the 0.8 check didn't: a Pythoncomputein the same repo kept two Kotlin copies at 0.7. In playframework this raised 13 more right picks (each sample project's ownmodels.UserandApplicationmethods).compute()without importing it, which Python can't do. Added Kotlin flavor copies (bare call in one package) and Java flavor copies (new Util()), plus the self-match case.Thanks @danusha2345.Validation
Tests.
__tests__/same-module-copy-resolution.test.ts, 6 cases: 3 fail on main on the targets themselves (Python, Kotlin and Java copies), 2 fail on the PR as written, re-checked on its current head 5abdf25 (the Kotlin confidence and the self-match), all 6 pass here.A/B, main (with #2874) vs this change, fresh indexes, site by site, 21 repos in 11 languages (flask, click, fastapi, gin, cobra, etcd, ripgrep, jq, Slim, serilog, eShopOnWeb, nowinandroid, okhttp, http4s, playframework, spring-modulith, express, zod, bulletproof-react, excalidraw, vben):
Triage:
spring-modulith-example-fulltests now reach that example's ownOrderManagementandOrderCompleted, where Spring's naming rule (0.7) had taken theepr-jdbcexample's copy (3). playframework'sshutdown-happy-pathscripted test now instantiates its ownsrc/mainHomeController, not thechanges/copy its script swaps in later (1, the case the PR describes).main.tsreaching its ownbootstrap.ts, each bulletproof-react app's mocks reaching its owndb.ts, etcd'spkg/proxytest reachingpkg/proxy. In Go, qualified names carry no package, so same-named functions in same-named files of different packages count as copies; the own-package pick is what a bare Go call means anyway. 4 of the 109 are wrong targets main already had at 0.7: vben'sdefineConfigcalls fromimport { defineConfig } from 'tsdown'link to the folder's ownsrc/index.tsnamesake, becausetsdownis declared only in the rootpackage.json(a pnpmcatalog:entry) and so isn't recognized as an outside package from a workspace package. That gap predates this change.Cost, index process CPU time, interleaved, median of 3: okhttp 34.8 s vs 34.8 s, etcd 36.5 s vs 36.5 s.
Suite on this commit (
CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4 --minWorkers=1, Windows): 760 files passed, 6 skipped; 9,032 tests passed, 83 skipped, 0 failed.Lands #1936 by @danusha2345.
🤖 Generated with Claude Code