Repository navigation
Call graph: reference edges (Python) — functions used as values; nested definitions win over the file's first - #3712
Merged
Conversation
…unit
Python sliced only defs, so the code a module runs at import -- app =
FastAPI(), @app.post("/items"), setup_logging(), the __main__ guard -- was
recorded nowhere, and a web app's route registration with it. The Mode C
slicer now emits Mode D's synthetic __global_context__ bucket for Python and
embedded_python (top-level module_level_unit flag): every sliced unit's span
and each `class Name` header's name are blanked, the rest is scanned for
calls (qualifiers and receiver types included, so app.post is typed
FastAPI.post).
It is calls-only: magnitude/impact 0 and flagged calls_only, which the file
mass, function-analysis report, function-impact forensics, file-archetype
function count, named-structure retention and folder function count skip, so
no file's size or classification moves because of it.
Synthetic units have no function_data row (#2691), so their calls now ride
on a synthetic_unit_data child table (FK cascade) that the state rehydrator
restores; a delta scan resolves them like a fresh scan. This also covers
Mode D's existing buckets, which were lost on delta scans.
Golden masters (both, 42 differences each): fastapi applications.py direct
downstream 44 -> 191, routing.py 13 -> 30, param_functions.py 92 -> 103
(module-level app/router construction and calls now link to the defining
file), and what those move: Composition Fit z-scores, within-repo file
clusters, network macros, three forensic top lists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
… audit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
…decorator A decorator's wrapper runs around every call to the function it decorates, and neither the call graph nor the module unit recorded that link. The detector now captures, per def, the contiguous decorator block directly above it (argument lines included, shielded text, bounded look-back) as decorated_by / decorated_by_qualifiers (`@app.post(...)` -> post via app); python and embedded_python opt in with a top-level decorator_edges flag. The resolver links them through the usual ladder as fcall_data kind 'decorator' -- counted in decorators_by_step, never in the call-resolution rates -- and they reach function fan-in/PageRank like COBOL transfers do. A function now also falls back to its module's receiver types (`app = FastAPI()` at top level) for a receiver it does not bind itself; a name the function binds with no known class is recorded as "" and hides the module type. Both columns persist in function_data and rehydrate. call_graph_resolution.py python (pyan3): calls-only precision 99.4% (1806 judged), recall 51.3 -> 51.4, resolution recall 96.9 -> 97.0; new, not gated: decorator edges 100% precision (575 judged, 0 wrong), recall with decorators 67.9%. Golden masters: no drift. Stacked on #3704 (module-level unit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
…or-edges # Conflicts: # gitgalaxy/core/detector.py # gitgalaxy/standards/language_standards/languages/embedded_python.py # gitgalaxy/standards/language_standards/languages/python.py # tests/tools_recorders/test_fcall_data.py
… the file's first A function name used as a value -- Depends(get_db), key=sort_key, callback=self.on_done, return wrapper -- is a link the framework or caller invokes later, and was recorded nowhere. The detector (python and embedded_python, top-level reference_edges flag) records candidates in argument, keyword, assignment, return/yield/lambda and collection positions as references_to / references_qualifiers. Variables are excluded: the function's parameters and locals (tuple and starred targets included), every nested def's parameters, the bindings of every enclosing function (closure variables), the module's own variables (`app = FastAPI()`), and in a signature anything but a default or a call argument (so `Sequence[Depends]` is not one). Names the function also invokes are already edges. The resolver keeps a reference only when it reaches a function confidently through a scoped step (class, qualified, typed, file, import) -- `unique` is not enough for a bare name -- as fcall_data kind 'reference', counted in references_by_step and never in the call rates. A bare call or reference to a name the caller defines nested inside itself now resolves to that nested definition, not the file's first of that name (every decorator's own `wrapper`); the rehydrator restores loc for the caller's span. The module unit carries no references yet (synthetic_unit_data does not persist them). call_graph_resolution.py python (pyan3): call precision 99.4 -> 99.8 (1806 judged), recall 51.4 -> 51.6, resolution recall 97.0 -> 97.5; new, not gated: reference precision 99.7% (323 judged), recall across calls, decorators and references 77.3%. Golden masters: no drift. Stacked on #3708 (decorator edges). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
…coped to calls `return helper` now draws a reference edge o2 -> helper, which is the point of this PR; the C5 test's claim is that the nested header is not a call, so it checks call-kind rows and asserts the reference separately. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
This was referenced Sep 26, 2026
Merged
Contributor
…ce-edges # Conflicts: # gitgalaxy/core/call_resolver.py # gitgalaxy/core/detector.py # gitgalaxy/core/state_rehydrator.py # gitgalaxy/recorders/record_keeper.py # gitgalaxy/standards/language_standards/languages/embedded_python.py # gitgalaxy/standards/language_standards/languages/python.py # tests/call_graph_resolution_baseline.json # tests/core_engine/test_call_resolver.py # tests/tools/call_graph_resolution.py # tests/tools/test_call_graph_resolution_gate.py # tests/tools_recorders/test_fcall_data.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Part of the Level 2 call-graph work (epic #3265). Stacked on #3708 (decorator edges): this PR's base is that branch, so the diff shows only the reference work. Merge #3708 first, then retarget this to
main.Problem. A function name used as a value (
Depends(get_db),key=sort_key,callback=self.on_done,return wrapper) is a function the framework or caller invokes later. It's how frameworks wire programs together, and it was recorded nowhere. In the classification of pyan3's unlinked Python edges, these were ~330 edges (~9%).Fix.
_python_references; python and embedded_python via a top-levelreference_edgesflag, per orphaned_logic reports 100% dead code for five languages whose invocation model never names the callee — the family #2727 closed without covering #2806):references_to/references_qualifiers(self.on_donebecomeson_doneviaself).with/except/ walrus;app = FastAPI());Sequence[Depends]isn't a reference butDepends(get_db)is;def f(header is not an invocation, soreturn fis a reference.class,qualified,typed,file,import).uniqueisn't enough: a local variable named like the repo's one function of that name is still a variable.fcall_datakind'reference', counted inreferences_by_stepand never in the call rates.wrappercase.iter_datawrong-target examples for plain calls.loc, so the caller's span survives a delta scan.function_data.references_to/references_qualifiers(healed), restored by the state rehydrator.synthetic_unit_datadoesn't persist them, and a delta scan must resolve exactly like a fresh one. That's a follow-up.call_graph_resolution.pyscores reference edges separately and addsreference_precision_pct,reference_judgedandrecall_all_kinds_pctto the baseline, reported but not gated.Type of change
detector.py,call_resolver.py,record_keeper.py,state_rehydrator.py, python profiles)Measured:
call_graph_resolution.py python(pyan3 reference, language-crucible)How the precision got there, since these rules read the real source:
TestClient(app)resolving to an innerappinrouting.py), tuple targets, closure variables, signature annotations and a truncated 2,000-char signature. All are fixed, which brought unconfirmed down to 65.return <nested def>then dropped precision to 90.5% (31 wrong: the file's firstwrapper). The nested-definition rule brought it back to 99.7%.@propertyreads (self._data,self.code,self.timeOut; reading them runs a function, and pyan doesn't count that);app.dependency_overrides[common_parameters] = ..., where the key is the function.Golden masters
crucible_check.py --mode both --update --yes: no drift. The changes are function-level and within a file.Tests
tests/extraction/languages/test_python_references.py:tests/core_engine/test_call_resolver.py: a reference is kept only when it reaches an in-scope function (not auniquevariable, not a class); a nested definition wins over the file's first, for calls and references.tests/tools_recorders/test_fcall_data.py: the reference columns andlocpersist and rehydrate.test_calls_out_nested_decl_3360.py::test_no_false_fcall_edge_from_outer_to_nested_declaration: the C5 claim (a nested header isn't a call) now checks call-kind rows.return helperdraws the new reference edge, which is asserted explicitly.test_call_graph_resolution_gate.py: covers the new baseline fields.Verification
🤖 Generated with Claude Code
https://claude.ai/code/session_01GCpfMCUMpco98QGbsuv2sr
Generated by Claude Code