Summary
A member call whose receiver is a call expression loses the receiver at extraction time, degrades to a bare method-name ref, and then exact-matches any top-level project symbol with that name — fabricating a call edge from an unrelated function.
Reproduced in Python and JavaScript, so this is the shared extractor, not a per-language heuristic.
Not the same as #1230/#1317 (literal receivers, fixed — control C below passes) or #645 (C++ chained getters binding to the wrong class; here the receiver is dropped entirely and the edge lands on a top-level function).
Environment
@colbymchenry/codegraph 1.5.0 and 1.6.0 (npm latest), Linux, fresh codegraph init per run.
Repro — Python
# pkg/ledger.py
def append(row): return row
def path(): return "/x"
# pkg/uses.py
from . import ledger
def plain_identifier(out, k):
out.append(k) # C: plain ident -> no edge (correct)
def chained_receiver(d, k, v):
d.setdefault(k, []).append(v) # D: call-expr recv -> calls ledger.append WRONG
Repro — JavaScript
// src/lib.js
export function push(row) { return row; }
export function path() { return "/x"; }
// src/use.js
import * as lib from './lib.js';
export function realNamespaceCall(row) {
lib.push(row); // A -> calls lib.push "import" (correct)
}
export function plainIdent(out, k) {
out.push(k); // C -> no edge (correct)
}
export function chained(m, k, v) {
m.getOrInit(k, []).push(v); // D -> calls lib.push "exact-match" WRONG
}
Observed — both languages, identical
| shape |
receiver |
edge |
plain identifier (out.append(k)) |
ident |
none — correct |
literal (", ".join(v)) |
literal |
none — correct (#1317) |
| namespace/module member |
ident |
resolves via import — correct |
call expression (d.setdefault(k,[]).append(v)) |
call |
fabricated exact-match edge to the top-level append/push |
Only the receiver shape differs between the correct and incorrect rows.
Impact
Any project with a top-level export named like a common collection method (append, push, get, update, run, close) accumulates fabricated inbound edges proportional to how often that method is used on chained locals elsewhere. callers/impact then return a populated, plausible list that is wrong — worse than an empty one, which is visibly wrong.
Suspected mechanism
In the extractor, the qualified receiver.method shape is preserved only when the receiver is a plain identifier; a call-chain receiver degrades to a bare method ref. That bare ref passes the codebase-wide name check (a real top-level symbol of that name exists) and is exact-matched as the sole candidate.
Verification
Both versions, both languages, fresh index each time; cross-checked via the codegraph CLI and direct SQL against .codegraph/codegraph.db, which agree.
Summary
A member call whose receiver is a call expression loses the receiver at extraction time, degrades to a bare method-name ref, and then exact-matches any top-level project symbol with that name — fabricating a call edge from an unrelated function.
Reproduced in Python and JavaScript, so this is the shared extractor, not a per-language heuristic.
Not the same as #1230/#1317 (literal receivers, fixed — control C below passes) or #645 (C++ chained getters binding to the wrong class; here the receiver is dropped entirely and the edge lands on a top-level function).
Environment
@colbymchenry/codegraph1.5.0 and 1.6.0 (npm latest), Linux, freshcodegraph initper run.Repro — Python
Repro — JavaScript
Observed — both languages, identical
out.append(k))", ".join(v))import— correctd.setdefault(k,[]).append(v))exact-matchedge to the top-levelappend/pushOnly the receiver shape differs between the correct and incorrect rows.
Impact
Any project with a top-level export named like a common collection method (
append,push,get,update,run,close) accumulates fabricated inbound edges proportional to how often that method is used on chained locals elsewhere.callers/impactthen return a populated, plausible list that is wrong — worse than an empty one, which is visibly wrong.Suspected mechanism
In the extractor, the qualified
receiver.methodshape is preserved only when the receiver is a plain identifier; a call-chain receiver degrades to a baremethodref. That bare ref passes the codebase-wide name check (a real top-level symbol of that name exists) and is exact-matched as the sole candidate.Verification
Both versions, both languages, fresh index each time; cross-checked via the
codegraphCLI and direct SQL against.codegraph/codegraph.db, which agree.