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
@@ -2541,6 +2541,57 @@ func main() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Literal receivers and nested-local scope (#1230)', () => {
|
||||
// Two stacked fabrications: `", ".join(...)` (a builtin on a string
|
||||
// literal) exact-matched a project function named `join` — one that was
|
||||
// moreover nested inside a DIFFERENT function and thus lexically
|
||||
// unreachable. Literal receivers now emit no call ref at all, and
|
||||
// exact-match refuses candidates nested in a function the ref isn't in.
|
||||
it("str-literal builtin calls don't bind to project symbols; nested locals only resolve from inside their container", async () => {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-1230-'));
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, 'repro.py'),
|
||||
`def format_fields(values):
|
||||
def join(vals):
|
||||
return "-".join(sorted(vals))
|
||||
|
||||
return join(values)
|
||||
|
||||
|
||||
def report_missing(unresolved):
|
||||
missing_list = ", ".join(sorted(unresolved))
|
||||
return f"Could not resolve: {missing_list}"
|
||||
`
|
||||
);
|
||||
|
||||
const cg = CodeGraph.initSync(tmpDir);
|
||||
await cg.indexAll();
|
||||
|
||||
const join = (await cg.searchNodes('join', { limit: 5 })).find(
|
||||
(r) => r.node.kind === 'function' && r.node.name === 'join'
|
||||
);
|
||||
expect(join).toBeDefined();
|
||||
|
||||
// Exactly one caller: the enclosing format_fields. Neither
|
||||
// report_missing (literal receiver) nor join itself (its own literal
|
||||
// "-".join) may appear.
|
||||
const callers = await cg.getCallers(join!.node.id);
|
||||
expect(callers.map((c) => c.node.name)).toEqual(['format_fields']);
|
||||
|
||||
// report_missing has zero project callees.
|
||||
const reportMissing = (await cg.searchNodes('report_missing', { limit: 5 })).find(
|
||||
(r) => r.node.kind === 'function'
|
||||
);
|
||||
const callees = await cg.getCallees(reportMissing!.node.id);
|
||||
expect(callees.filter((c) => c.node.name === 'join')).toHaveLength(0);
|
||||
cg.close();
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
describe('Go field-chain receiver calls (#1276)', () => {
|
||||
// `target.conn.Exec(...)` where `conn *sql.DB` used to emit a BARE `Exec`
|
||||
// ref, which exact-matched the only local `Exec` — an unrelated
|
||||
|
||||
Reference in New Issue
Block a user