fix(resolution): C, C++ and Objective-C rank as one language, and a lone candidate is judged by family (#3052)

Name matching compared a candidate's raw language tag with the
reference's: the single-candidate confidence in matchByExactName and
matchFuzzy, and findBestMatch's same-language skip and +50/-80 score.
So a call whose name had several definitions in another of C, C++ and
Objective-C scored -80 for each and linked to nothing (an Objective-C
file's _glfwPlatformSetTls(), a C++ call into Dear ImGui's imgui.h, which
reads as C), and the one definition of a name across TSX and TS, or C and
C++, was a 0.5 cross-language guess.

The single-candidate confidence now compares language families
(sameLanguageFamily). Ranking treats C, C++ and Objective-C as one
language (sameRankingLanguage), the very language worth a point that only
breaks a tie; other families keep the language itself, because ranking
them by family moved excalidraw's Store::scheduleCapture calls to the
App.tsx method beside them and gave okhttp 83 wrong Java-to-Kotlin
Builder picks.

Validation:
- __tests__/name-match-language-family.test.ts, 8 tests (default and
  wasm): 6 fail on the base main; the pin that a .ts call keeps its .ts
  target over a .tsx namesake beside it passes on both; all pass with
  the fix. 24 related test files (cross-language, React Native, Expo,
  Fabric, frameworks, mixed TS/JSX, Objective-C, resolution): 746 pass.
- A/B, main vs fix: glfw +4 calls from Objective-C into C, imgui +93
  from C++ into imgui.h (read as C) and imstb_rectpack.h, all to the
  definitions meant; excalidraw, okhttp, leveldb and jq gain or lose no
  edge (43, 9, 97 and 30 unique-name links go from 0.5 to 0.9 with the
  same targets); flask and gin byte-identical.
- Index time, median of 3 interleaved: imgui 7.9 s main, 7.7 s fix;
  excalidraw 9.2 s main, 9.4 s fix.

Reimplements PR #1662 by ferrine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-30 08:15:23 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 19b74d10f3
commit 6d3804cf57
3 changed files with 167 additions and 13 deletions
+34 -13
View File
@@ -175,6 +175,21 @@ export function sameLanguageFamily(a: string, b: string): boolean {
const fa = LANGUAGE_FAMILY[a];
return fa !== undefined && fa === LANGUAGE_FAMILY[b];
}
/**
* The languages whose files share one table of names: a C function is called
* by its bare name from C++ and Objective-C alike, and a `.h` file is C, C++
* or Objective-C only as far as sniffing its text can tell (Dear ImGui's
* C++ `imgui.h` reads as C), so name matching ranks them as one language.
* Other families keep the language itself: their files reach each other's
* names through imports, and a name-only pick across TypeScript and TSX, or
* Java and Kotlin, went to a same-named declaration in the next file more
* often than to the one meant (#1662).
*/
const C_LINKED_LANGUAGES: ReadonlySet<string> = new Set(['c', 'cpp', 'objc']);
function sameRankingLanguage(a: string, b: string): boolean {
return a === b || (C_LINKED_LANGUAGES.has(a) && C_LINKED_LANGUAGES.has(b));
}
/** Config/markup transitions stay open; every other code language has a family. */
const CODE_FAMILY: Record<string, string> = {
...LANGUAGE_FAMILY,
@@ -13283,10 +13298,11 @@ export function matchByExactName(
return null;
}
// If only one match, use it — but penalize cross-language matches
// If only one match, use it — but penalize a match in another language
// family (a `.tsx` component calling a `.ts` helper is not one, #1662)
if (candidates.length === 1) {
if (!isCrossFileReachable(candidates[0]!, ref, context)) return null;
const isCrossLanguage = candidates[0]!.language !== ref.language;
const isCrossLanguage = !sameLanguageFamily(candidates[0]!.language, ref.language);
return {
original: ref,
targetNodeId: candidates[0]!.id,
@@ -13334,10 +13350,11 @@ export function matchByExactName(
// above a proximity guess, but below a unique name or the file's own
// definition (0.9). The candidates are those findBestMatch weighed: a
// namesake in another language never wins while one in the reference's
// own language exists. The caller's own copy being the caller itself is
// no evidence, as for a same-file match (isDecisiveSameFileMatch).
const weighed = candidates.some((c) => c.language === ref.language)
? candidates.filter((c) => c.language === ref.language)
// own language (sameRankingLanguage) exists. The caller's own copy being
// the caller itself is no evidence, as for a same-file match
// (isDecisiveSameFileMatch).
const weighed = candidates.some((c) => sameRankingLanguage(c.language, ref.language))
? candidates.filter((c) => sameRankingLanguage(c.language, ref.language))
: candidates;
const confidence = isDecisiveSameFileMatch(bestMatch, ref, candidates, context)
? 0.9
@@ -29385,7 +29402,8 @@ function findBestMatch(
// Prioritization rules:
// 1. Same file > different file
// 2. Directory proximity (same module/package > different module)
// 3. Same language > different language
// 3. Same language > different language (C, C++ and Objective-C count as
// one: sameRankingLanguage), and the very language as a tie-break
// 4. Functions/methods > classes/types (for call references)
// 5. Exported > non-exported
@@ -29406,10 +29424,11 @@ function findBestMatch(
// scoring. Cuts the candidate set to same-language size on mixed front-end +
// back-end repos (#915). When ALL candidates are cross-language (a legitimate
// cross-language `calls` bridge), none are skipped and behavior is unchanged.
const hasSameLanguage = candidates.some((c) => c.language === ref.language);
const hasSameLanguage = candidates.some((c) => sameRankingLanguage(c.language, ref.language));
for (const candidate of candidates) {
if (hasSameLanguage && candidate.language !== ref.language) continue;
const sameLanguage = sameRankingLanguage(candidate.language, ref.language);
if (hasSameLanguage && !sameLanguage) continue;
let score = 0;
@@ -29428,9 +29447,11 @@ function findBestMatch(
score += 80;
}
// Language matching: strongly prefer same language, penalize cross-language
if (candidate.language === ref.language) {
score += 50;
// Language matching: strongly prefer same language, penalize cross-language.
// Between C, C++ and Objective-C the very language adds one point, which
// only breaks a tie: a directory nearer counts 15.
if (sameLanguage) {
score += candidate.language === ref.language ? 51 : 50;
} else {
score -= 80;
}
@@ -29662,7 +29683,7 @@ export function matchFuzzy(
!(finalCandidates[0]!.kind !== 'function' && isBarePhpCall(ref, context)) &&
isLexicallyReachable(finalCandidates[0]!, ref, context)
) {
const isCrossLanguage = finalCandidates[0]!.language !== ref.language;
const isCrossLanguage = !sameLanguageFamily(finalCandidates[0]!.language, ref.language);
return {
original: ref,
targetNodeId: finalCandidates[0]!.id,