What happens
matchByExactName filters its candidates with isLexicallyReachable (#1230): a function node whose qualified name puts it inside another function is only a candidate for references from inside that container. matchFuzzy does not apply that filter. So when the only project symbol with a given (lower-cased) name is a nested function, exact-match correctly returns null, the ref falls through to fuzzy, and fuzzy adopts the nested function as its "unique" candidate at confidence 0.5.
Trigger: any common method name called on a receiver the extractor collapses to a bare name (res.text(), items.push(x), set.clear()), when some file in the repo happens to declare a closure with that name (const text = () => … inside a function, function push() {} inside buildRows).
Measured
Real TS repo, 584 files, on main + #1706:
- 11 fuzzy
calls edges land on nested functions, 9 of them from a different file than the closure. Six calls named isOpen from various components resolve to useOverlayStack::isOpen, a closure inside a composable, because it is the only symbol with that name.
Same repo with #1679 (#1669, which extracts closures that were previously invisible, so many more nested functions exist to be hit):
- fuzzy
calls edges rise from 89 to 226. 160 of them land on nested functions, 139 cross-file: 72 .text() calls (Response#text, test-utils wrapper.text()) resolve to readSeedFailureState::text, 41 .push() calls to buildRows::push, 10 to captureListeners::event, 7 to resolveClosestName::fallback, 6 to startWarRoomBlankBoardHeal::clear.
So #1679 is not the bug, it just multiplies the targets; the gap is in matchFuzzy.
Proposed fix
Apply the same reachability filter in matchFuzzy:
const callableCandidates = applyLanguageGate(
candidates.filter((n) => callableKinds.has(n.kind) && isLexicallyReachable(n, ref, context)),
ref
);
Measured on the same repo with that one change: 41,378 → 41,367 edges, fuzzy 26 → 15, fuzzy-onto-nested-function 11 → 0, nothing else moves. With #1679 applied it should take the 160 closure hits back out the same way (not measured). PR to follow with a test (two files, a closure named text in one, a res.text() call in the other, assert no calls edge).
What happens
matchByExactNamefilters its candidates withisLexicallyReachable(#1230): afunctionnode whose qualified name puts it inside another function is only a candidate for references from inside that container.matchFuzzydoes not apply that filter. So when the only project symbol with a given (lower-cased) name is a nested function, exact-match correctly returns null, the ref falls through to fuzzy, and fuzzy adopts the nested function as its "unique" candidate at confidence 0.5.Trigger: any common method name called on a receiver the extractor collapses to a bare name (
res.text(),items.push(x),set.clear()), when some file in the repo happens to declare a closure with that name (const text = () => …inside a function,function push() {}insidebuildRows).Measured
Real TS repo, 584 files, on
main+ #1706:callsedges land on nested functions, 9 of them from a different file than the closure. Six calls namedisOpenfrom various components resolve touseOverlayStack::isOpen, a closure inside a composable, because it is the only symbol with that name.Same repo with #1679 (#1669, which extracts closures that were previously invisible, so many more nested functions exist to be hit):
callsedges rise from 89 to 226. 160 of them land on nested functions, 139 cross-file: 72.text()calls (Response#text, test-utilswrapper.text()) resolve toreadSeedFailureState::text, 41.push()calls tobuildRows::push, 10 tocaptureListeners::event, 7 toresolveClosestName::fallback, 6 tostartWarRoomBlankBoardHeal::clear.So #1679 is not the bug, it just multiplies the targets; the gap is in
matchFuzzy.Proposed fix
Apply the same reachability filter in
matchFuzzy:Measured on the same repo with that one change: 41,378 → 41,367 edges, fuzzy 26 → 15, fuzzy-onto-nested-function 11 → 0, nothing else moves. With #1679 applied it should take the 160 closure hits back out the same way (not measured). PR to follow with a test (two files, a closure named
textin one, ares.text()call in the other, assert nocallsedge).