Summary
_LANGUAGE_BUILTIN_GLOBALS (extractors/base.py) is one union across every language. The gate that
consults it decides both whether a call gets a same-file bare-name edge and whether it is
recorded in raw_calls — the only route to the receiver-typed resolvers. The union is right for a
bare call (String(x) really would become a god node), but applied to a member call it deletes
the call outright in whatever language does not own the name, with no warning.
open and set are Python builtins; Set, Map, URL, String are JavaScript ones. So
session.open() in Swift, xhr.open() in JavaScript and _server.Set() in C# are all silently
discarded.
Reproduction
// probe.swift
enum T { static func run() { p.open() } } // 0 calls extracted
enum T { static func run() { p.doWorkXyz() } } // 1 call extracted
Measured on graphify 0.9.54, every grammar installed locally. Each probe is compared against the
same source with the callee renamed to a name no builtin table claims:
| grammar |
probe |
probe calls |
control calls |
| python, go, rust, java, kotlin, ruby, php, scala, cpp, swift |
p.open() |
0 |
1 |
| javascript, typescript |
xhr.open() |
1 |
2 |
| c_sharp |
_server.Set() |
0 |
1 |
13 of 13 drop the call. On one real 338-file Swift codebase this removed 236 member calls,
including AppleReadyStreamFinder.find -> RemuxPlayback.open, which left two tightly coupled
subsystems in separate Louvain communities with no path between them except through Logger.
Suggested fix
A member call carries a receiver, so it is not the ambiguous case the union guards: every
receiver-typed resolver only emits an edge when the receiver's type resolves to exactly one
definition, and the shared cross-file pass drops member calls outright (#543/#1219). Letting a
builtin-named member call through to raw_calls — while forcing tgt_nid = None so it never takes
the unguarded bare-name path — can therefore only add guarded edges.
_builtin_member_call = (
callee_name is not None
and is_member_call
and callee_name in _LANGUAGE_BUILTIN_GLOBALS
)
if callee_name and (
_builtin_member_call or callee_name not in _LANGUAGE_BUILTIN_GLOBALS
):
...
# and in the defer condition:
if _python_defer or _java_defer or _builtin_member_call or (...):
tgt_nid = None
The same gate is duplicated in extractors/go.py (~L480) and extractors/rust.py (~L372) and needs
the same change; in Go, tgt_nid also has to fold _builtin_member_call into the existing
import_path guard.
Verification
With the change applied, all 13 grammars keep the call, and graphify's own 86-file Python source
tree extracts byte-identically — 2441 nodes / 5624 edges / 2033 calls, with the same top call-target
in-degrees (91 / 56 / 45 / 35 / 27 / 21). No god-node inflation.
Happy to open a PR if the approach looks right.
Summary
_LANGUAGE_BUILTIN_GLOBALS(extractors/base.py) is one union across every language. The gate thatconsults it decides both whether a call gets a same-file bare-name edge and whether it is
recorded in
raw_calls— the only route to the receiver-typed resolvers. The union is right for abare call (
String(x)really would become a god node), but applied to a member call it deletesthe call outright in whatever language does not own the name, with no warning.
openandsetare Python builtins;Set,Map,URL,Stringare JavaScript ones. Sosession.open()in Swift,xhr.open()in JavaScript and_server.Set()in C# are all silentlydiscarded.
Reproduction
Measured on graphify 0.9.54, every grammar installed locally. Each probe is compared against the
same source with the callee renamed to a name no builtin table claims:
p.open()xhr.open()_server.Set()13 of 13 drop the call. On one real 338-file Swift codebase this removed 236 member calls,
including
AppleReadyStreamFinder.find -> RemuxPlayback.open, which left two tightly coupledsubsystems in separate Louvain communities with no path between them except through
Logger.Suggested fix
A member call carries a receiver, so it is not the ambiguous case the union guards: every
receiver-typed resolver only emits an edge when the receiver's type resolves to exactly one
definition, and the shared cross-file pass drops member calls outright (#543/#1219). Letting a
builtin-named member call through to
raw_calls— while forcingtgt_nid = Noneso it never takesthe unguarded bare-name path — can therefore only add guarded edges.
The same gate is duplicated in
extractors/go.py(~L480) andextractors/rust.py(~L372) and needsthe same change; in Go,
tgt_nidalso has to fold_builtin_member_callinto the existingimport_pathguard.Verification
With the change applied, all 13 grammars keep the call, and graphify's own 86-file Python source
tree extracts byte-identically — 2441 nodes / 5624 edges / 2033 calls, with the same top call-target
in-degrees (91 / 56 / 45 / 35 / 27 / 21). No god-node inflation.
Happy to open a PR if the approach looks right.