fix(go): filter Go predeclared functions from call-target resolution - #2296
fix(go): filter Go predeclared functions from call-target resolution#2296PathGao wants to merge 2 commits into
Conversation
graphify/extractors/go.py already consults _LANGUAGE_BUILTIN_GLOBALS when resolving a callee, but the table covered JS/TS, Python and Swift (Graphify-Labs#726, Graphify-Labs#2147) and never Go. Because the Go resolver looks the callee up by bare name, an unexported method sharing a builtin's name absorbs every builtin call in the repository. Measured on a real 8.9k-node Go+TS codebase: a 'func (h *metricHistory) append(...)' method collected 330 phantom inbound calls edges from the project's ordinary append(slice, x) calls, which in turn invented twelve database-layer -> service-layer edges — a layering violation absent from the source. After the fix, phantom cross-file edges to builtin-named nodes drop from 325 to zero; the 8 that remain are all same-file (struct -> method, and real calls to that method), and the user's method node is untouched. Builtin types are deliberately not listed. Go conversions are call-shaped too, but they produced no phantom edges on that corpus and listing them would suppress genuine constructor-like calls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR adds Go's predeclared function names (e.g. append, make, new, panic, copy, delete) to the _LANGUAGE_BUILTIN_GLOBALS frozenset in graphify/extractors/base.py, so the Go resolver treats them as builtins rather than binding calls to same-named user symbols. The stated intent is to prevent an unexported method sharing a builtin's name from absorbing all builtin calls as phantom inbound edges. It also adds a new test file (tests/test_go_builtin_call_targets.py) covering three cases: builtin append not binding to a user method, the user method node surviving the filter, and a genuine cross-file call still resolving. The change surface is limited to one edited set of string entries plus one new test file; note that Go builtin types were intentionally excluded from the list.
Worth a look
- Builtin name filter is language-agnostic and shadows user symbols in other languages —
graphify/extractors/base.py:58· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Adding common Go method names to builtin globals may suppress genuine method calls —
graphify/extractors/base.py:60· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Go builtin 'len' missing from builtin globals set —
graphify/extractors/base.py:60· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1817 functions depend on the 17 node(s) this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 346 callers, 27 callees
Verification — 1817 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1095 function(s) in the blast radius were not formally verified this run
· 1 more finding(s) on lines outside this diff (see the check run).
safishamsi
left a comment
There was a problem hiding this comment.
Thanks @PathGao — the diagnosis and test scaffolding are solid (bare append(x) phantom-binding cross-file is a real bug). But the set you added to is _LANGUAGE_BUILTIN_GLOBALS, which is shared across ~11 languages through the engine and the cross-file pass — adding append/make/copy/new/close/delete changes resolution for all of them, and it wraps the in-file EXTRACTED match too. Concretely it kills every in-file Rust Type::new() edge (new normalizes to the same token; Rust deliberately keeps its own name blocklist in the cross-file branch only), and drops genuine same-file Go h.append(v) selector calls. Please move this to a Go-local _GO_PREDECLARED_FUNCS set applied only when the callee node type is a bare identifier (mirroring Rust's language-local pattern), and gate Go raw_calls in the shared pass (raw_calls already carry language, like the bash gate). Keep the tests — happy to re-review once it's Go-scoped.
Review feedback on Graphify-Labs#2296: the previous revision added Go's predeclared names to _LANGUAGE_BUILTIN_GLOBALS, which ~11 languages consult through engine.py and the cross-file pass, and whose check wraps the in-file EXTRACTED branch as well as raw_calls. Two confirmed regressions: * Rust normalizes 'Widget::new(3)' to the bare token 'new', so every in-file 'Type::new()' edge disappeared. Rust keeps its own _RUST_TRAIT_METHOD_BLOCKLIST, deliberately on the cross-file branch only — this change follows that language-local pattern. * Go 'h.append(v)' is a selector_expression call to a real method and was dropped with it. On the 3x-ui corpus this cost a genuine 'systemMetrics.append(...)' -> '(*metricHistory).append' edge. The filter now lives in extractors/go.py as _GO_PREDECLARED_FUNCS and fires only when the callee node is a bare identifier, so selector calls ('h.append(v)', 'pkg.Delete(x)') and every other language are untouched. Go raw_calls now carry language="go" and the shared pass gates on it, mirroring the bash gate, as a backstop for Go raw_calls minted elsewhere. The set is the Go spec's predeclared list in full: being Go-local and bare-identifier-only makes 'len'/'max'/'min'/'print' safe to include, and a spec boundary beats a hand-picked subset. Remeasured on the same 466-file Go corpus: upstream v8 16904 edges, 334 inbound to 'append' previous rev 16571 edges, 1 inbound (332 phantom gone, but the genuine selector call too) this rev 16572 edges, 2 inbound (332 phantom gone, genuine call restored) Tests kept, plus three regressions: builtin 'append' must not bind in-file (the branch a cross-file-only gate would miss), the Go selector call must survive, and the in-file Rust 'Type::new()' edge must survive. The last two fail on the previous revision. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks @safishamsi — you were right on both counts, and I reproduced them before changing anything. Pushed Confirmed regressions. Against
The mechanism is exactly as you described: the What changed.
One judgement call to flag: the set is now the Go spec's predeclared function list in full, so it picks up Remeasured on the same 466-file Go corpus (3x-ui), counting inbound edges to nodes whose label is a predeclared name: That restored edge is the one your review predicted: Tests. The original three are unchanged. Three more added:
The last two fail on the previous revision and pass on this one. Full suite is green locally apart from 13 pre-existing failures from optional deps missing in my venv ( |
There was a problem hiding this comment.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
This PR adds Go's predeclared builtin functions (e.g. append, len, make, new, close, delete) as a new _GO_PREDECLARED_FUNCS set and filters them out as call targets so a bare builtin call cannot bind to a user-defined symbol that shares the name. The filtering is applied in two places: the Go extractor (extractors/go.py), which now skips bare-identifier builtin callees before recording edges or raw_calls and tags Go raw_calls with "language": "go", and the cross-file resolution pass in extract.py, which drops Go raw_calls whose callee is a predeclared func. It's deliberately Go-local and restricted to bare identifiers (not selector calls like h.append(v)), rather than being added to the shared builtin-globals set. A new test file covers cross-file and same-file shadowing scenarios plus the intentional no-op cases.
No blocking issues surfaced. 1 lower-confidence candidate did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1217 functions depend on the 217 functions this change touches.
Health — this change adds coupling hotspots:
- worse:
extract()— 347 callers, 27 callees
Verification — 1217 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 1097 function(s) in the blast radius were not formally verified this run
· 1 more finding(s) on lines outside this diff (see the check run).
Summary
_LANGUAGE_BUILTIN_GLOBALScovers JS/TS, Python and Swift (#726, #2147) but never covered Go — whilegraphify/extractors/go.py:358already consults that table when resolving a callee. Since the Go resolver looks the callee up by bare name, an unexported method that happens to share a builtin's name absorbs every builtin call in the repository. This adds Go's predeclared functions to the table; no resolver logic changes.Evidence (real corpus, 8.9k nodes / 26k edges, Go + TypeScript)
The bug was found while graphing a real project. A single method —
— collected 330 phantom inbound
callsedges from that project's ordinaryappend(slice, x)calls, spread across the whole repo. The knock-on effect was worse than the noise: it invented twelvedatabase→serviceedges, i.e. a reported layering violation that does not exist in the source. That is exactly the phantom-bridge shape #726/#2147 fixed for other languages.database→servicefalse violationsappendmethod nodeThe 8 remaining edges are all same-file and legitimate:
metricHistory --method--> .append(), plus four genuine same-file calls to that method.Scope decision: functions yes, types no
Only the 14 predeclared functions are added (
append,cap,clear,close,complex,copy,delete,imag,make,new,panic,println,real,recover—len,min,max,printwere already in the table via other languages).Predeclared types (
string,int64,byte,error, …) are deliberately left out even though Go conversions are call-shaped: they produced zero phantom edges on the corpus, and listing them would suppress genuine constructor-like calls. Evidence-driven minimum, not a blanket sweep.Trade-off
A genuine cross-file call to a user function named exactly like a builtin is now skipped. That is the same trade-off #2147 accepted for
Data/String, and the asymmetry is stark here: builtinappendappears hundreds of times in any Go project, a package-level function namedappendcannot exist without shadowing the builtin.Tests
tests/test_go_builtin_call_targets.py, three cases:append()in one file does not bind to a same-named method in another — fails without the fix, passes with it (verified by reverting onlybase.py);appendmethod still exists as a node (the filter must not delete symbols);Case 3 uses a package-level call on purpose: the Go resolver intentionally skips receiver method calls for lack of import evidence, so that shape would prove nothing about this filter.
Full suite: no new failures (the pre-existing reds are missing optional deps — terraform grammar, skillgen/ollama network — red on
v8too).🤖 Generated with Claude Code