Problem
At -a 2, one decorator on a function produces a call-graph edge and another on the same
function produces none. Verified on main (6f02581) with a fixture whose module-level
plain_function carries @trace('hot') and @functools.lru_cache(maxsize=128):
app -> trace(tag) ['pycg']
Account -> trace(tag) ['pycg']
deco(fn) -> wraps ['jedi', 'pycg']
functools.wraps gets an edge. functools.lru_cache — same module, same file, same decorator
position — gets none, and never appears in external_symbols either.
This was found while investigating #129. That issue closed because decoration is being made
recoverable through qualified_name rather than through edges (see
the spec, D5),
but the missing edge is a separate defect that survives that decision: it is a hole in the call
graph, not a question about how decoration is modelled.
Scope boundary
Diagnoses and fixes why this call edge is missing. Does not change how decoration is
represented (that is #128), does not add a decoration edge family, and does not re-attribute the
existing app -> trace(tag) edges — those are correct for evaluation order.
Goals
Caveats and known risks
- Fix the cause, not the symptom. If PyCG mishandles a broader class of calls, patching only
the decorator path leaves every sibling caller still broken. - PyCG is archived upstream (Nov 2023) and already carries local patches in
semantic_analysis/pycg/pycg_analysis.py. A fix likely lands as another local patch, which is
maintenance debt worth naming rather than absorbing silently. - L2 contract. New edges at L2 are additive and safe for
L1 ⊆ L2. Changing existing edge
endpoints is not, and is out of scope. - The Jedi backend may already resolve this correctly — worth checking whether the gap is
PyCG-only before patching PyCG.
Definition of done
- The root cause is stated in the PR description, not just fixed
@functools.lru_cache(maxsize=128) produces a call edge whose target is homed in
external_symbols- The expected edge set for the fixture is written by hand first, then matched exactly
analysis.json(-a 1) ⊆ analysis.json(-a 2) still holds
Problem
At
-a 2, one decorator on a function produces a call-graph edge and another on the samefunction produces none. Verified on
main(6f02581) with a fixture whose module-levelplain_functioncarries@trace('hot')and@functools.lru_cache(maxsize=128):functools.wrapsgets an edge.functools.lru_cache— same module, same file, same decoratorposition — gets none, and never appears in
external_symbolseither.This was found while investigating #129. That issue closed because decoration is being made
recoverable through
qualified_namerather than through edges (seethe spec, D5),
but the missing edge is a separate defect that survives that decision: it is a hole in the call
graph, not a question about how decoration is modelled.
Scope boundary
Diagnoses and fixes why this call edge is missing. Does not change how decoration is
represented (that is #128), does not add a decoration edge family, and does not re-attribute the
existing
app -> trace(tag)edges — those are correct for evaluation order.Goals
lru_cacheis dropped wherewrapsis not — written down, not just patchedcalls too; if the latter, the fix belongs at the shared site, not the decorator path
external_symbolsCaveats and known risks
the decorator path leaves every sibling caller still broken.
semantic_analysis/pycg/pycg_analysis.py. A fix likely lands as another local patch, which ismaintenance debt worth naming rather than absorbing silently.
L1 ⊆ L2. Changing existing edgeendpoints is not, and is out of scope.
PyCG-only before patching PyCG.
Definition of done
@functools.lru_cache(maxsize=128)produces a call edge whose target is homed inexternal_symbolsanalysis.json(-a 1) ⊆ analysis.json(-a 2)still holds