fix(resolution): don't believe a bare-name call across language families - #1662
Open
ferrine wants to merge 1 commit into
Open
fix(resolution): don't believe a bare-name call across language families#1662ferrine wants to merge 1 commit into
ferrine wants to merge 1 commit into
Conversation
Two problems at the same comparison, one masking the other. The single-candidate confidence penalty compared raw language tags, so a `.tsx` file calling a `.ts` helper counted as cross-language and every ordinary call in a React codebase was scored 0.5 instead of 0.9. Families already exist for exactly this (`sameLanguageFamily`); using them makes 0.5 mean what it says. With that fixed, 0.5 identifies the real problem: a call resolved to a same-named symbol in an unrelated language on no evidence but the name. Cross-family calls are deliberately allowed — FFI bindings, native bridges are real, which is why the language gate covers `references` and `imports` but not `calls`. But a language's own builtins and macros have no definition of their own in the graph to bind to, so the lone same-named foreign symbol won by default: an ExUnit `test` binding to a test runner's fixture in TypeScript, `dataclasses.field` binding to an unrelated Elixir helper. The matcher already recognised these and wrote the verdict into the edge's metadata as a low confidence — and every consumer then believed the edge anyway, so `callers` and `impact` reported it as fact. On a mixed-language repository that was thousands of impossible edges, concentrated on a small set of generically-named symbols whose reported callers were then mostly or entirely fictional. Matches at or below the floor are now declined rather than recorded. The precise strategies are untouched, which is how a genuine binding keeps its edge: it resolves through an import or a qualified name, well above the floor, never through a bare one. Tunable via `CODEGRAPH_CROSS_FAMILY_CALL_FLOOR`, and `=0` restores the old behaviour. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Bsi9EH64kMisnik4E1gf7
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.
Branch:
ferrine:fix/cross-language-call-confidence→mainSize: 3 files, +181/−12 · Opt-out:
CODEGRAPH_CROSS_FAMILY_CALL_FLOOR=0Why
Two problems at the same comparison, one masking the other.
1. The single-candidate confidence penalty compares raw language tags. A
.tsxfile calling a.tshelper counts as cross-language and is scored 0.5instead of 0.9 — that is most calls in any React codebase.
sameLanguageFamilyalready exists two functions above for exactly this.
2. With that fixed, 0.5 identifies the real problem. Cross-family
callsare deliberately allowed — FFI bindings and native bridges are real. But a
language's own builtins and macros have no definition in the graph to bind to,
so the lone same-named foreign symbol wins by default. Observed on a
mixed-language repository: an ExUnit
testmacro binding to a test runner'sfixture in TypeScript; Python's
dataclasses.fieldbinding to an unrelatedElixir helper;
apply,max,min,path.The matcher already recognised these and wrote the verdict into the edge as a
low confidence — and every consumer then believed the edge anyway, so
callersand
impactreported it as fact. On that repo it was thousands of impossibleedges, concentrated on a small set of generically-named symbols whose reported
callers were then mostly or entirely fictional. One helper's
impactreturneddozens of affected symbols, nearly all of them in a language that cannot call it.
It is also unstable under indexing scope: indexing more files of one
language moved thousands of these edges elsewhere, because the fallback fires
only when no same-language candidate exists.
Why this is your rule, not a new one
src/resolution/index.tsalready hardcodes exactly this, for one language:This PR generalizes that from a per-language special case to the rule it is an
instance of.
What changed
language tag.
declined rather than recorded.
CODEGRAPH_CROSS_FAMILY_CALL_FLOOR,=0restores the oldbehaviour — same convention as
CODEGRAPH_AMBIGUOUS_NAME_CEILING.Blast radius, checked
strategy (Strategy 1/2); this gate is in name matching (Strategy 3). The
Cross-language type/import gate (RN name collisions)test passes.qualified name (0.85+), never a bare one.
~3,800 to ~100, with no loss of same-language edges and all
tsx↔tsedges retained.
the repo. Every removed
python↔cppedge pointed at C++ test files andheaders via Python builtins (
bytes,iter); none pointed at the actualbindings file. The real bridge was never captured before or after — that is a
separate coverage gap, not a regression from this change.