fix(resolution): literal-receiver builtins and nested locals stop fabricating call edges (#1317)
", ".join(sorted(x)) resolved by bare name to a project function named join — one nested inside a DIFFERENT function, so scope alone rules the edge out. Both defects from #1230, fixed independently: 1. Extraction: a member call on a LITERAL receiver (string, number, collection, regex — across grammars) emits no call ref at all. A literal's methods are the language's builtins, never project symbols; the bare-name fallback let them exact-match any same-named project function. Silent miss, never a wrong edge. 2. Resolution: matchByExactName filters out candidates nested inside a same-file FUNCTION container unless the ref originates within that container's line range. Class members (parent is a class-like node), top-level symbols, and C++ namespace prefixes (no parent node) are untouched. requests re-index: byte-identical (813 calls edges). excalidraw: -27 edges, all literal-receiver refs by construction. The issue's repro is pinned: join has exactly one caller (format_fields), report_missing has zero project callees. Fixes #1230 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
41c2029798
commit
c472cfb52e
@@ -340,6 +340,42 @@ export function matchFunctionRef(
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function nested inside another FUNCTION is only callable from within its
|
||||
* container — Python, JS/TS, and every closure language scope it lexically.
|
||||
* Resolving a bare name from elsewhere to a nested local fabricates an edge
|
||||
* scope already rules out: `join(...)` in one function must never bind to a
|
||||
* `join` defined inside a DIFFERENT function (#1230). A candidate whose
|
||||
* qualifiedName parent is a same-file function/method is kept only when the
|
||||
* ref originates inside that parent's line range. Class members are
|
||||
* unaffected (their parent resolves to a class-like node), as are top-level
|
||||
* symbols and C++ namespace-prefixed names (the prefix has no node).
|
||||
*/
|
||||
function isLexicallyReachable(
|
||||
candidate: Node,
|
||||
ref: UnresolvedRef,
|
||||
context: ResolutionContext
|
||||
): boolean {
|
||||
if (candidate.kind !== 'function') return true;
|
||||
const qn = candidate.qualifiedName;
|
||||
if (!qn || !qn.includes('::')) return true;
|
||||
const parentQn = qn.slice(0, qn.lastIndexOf('::'));
|
||||
const containers = context
|
||||
.getNodesByQualifiedName(parentQn)
|
||||
.filter(
|
||||
(p) =>
|
||||
p.filePath === candidate.filePath &&
|
||||
(p.kind === 'function' || p.kind === 'method') &&
|
||||
p.startLine <= candidate.startLine &&
|
||||
p.endLine >= candidate.endLine
|
||||
);
|
||||
if (containers.length === 0) return true;
|
||||
return (
|
||||
ref.filePath === candidate.filePath &&
|
||||
containers.some((p) => ref.line >= p.startLine && ref.line <= p.endLine)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to resolve a reference by exact name match
|
||||
*/
|
||||
@@ -357,7 +393,9 @@ export function matchByExactName(
|
||||
// findBestMatch — O(K²) per package, the dominant cost of "Resolving refs" on
|
||||
// large import-heavy (front-end + back-end) repos (#915).
|
||||
const candidates = applyLanguageGate(context.getNodesByName(ref.referenceName), ref)
|
||||
.filter((n) => n.kind !== 'import');
|
||||
.filter((n) => n.kind !== 'import')
|
||||
// Nested locals are only reachable from inside their container (#1230).
|
||||
.filter((n) => isLexicallyReachable(n, ref, context));
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user