Skip to content

fix(extract): emit references edges for Python decorators (#2154)#2175

Open
Rishet11 wants to merge 2 commits into
Graphify-Labs:v8from
Rishet11:fix/2154-python-decorator-edges
Open

fix(extract): emit references edges for Python decorators (#2154)#2175
Rishet11 wants to merge 2 commits into
Graphify-Labs:v8from
Rishet11:fix/2154-python-decorator-edges

Conversation

@Rishet11

Copy link
Copy Markdown
Contributor

Closes #2154

Python now emits the same decorator edges TS/JS already emits.

Problem

Applying a Python decorator emitted no edge to the decorator symbol, so affected <decorator> answered "No affected nodes found" for every function it wraps — a silent false negative on reverse-impact queries.

decorated_definition was handled as a transparent wrapper so a decorated method keeps its class-qualified node id (#1050). Its decorator children were walked, but no branch consumed them:

if t == "decorated_definition":
    for child in node.children:
        walk(child, parent_class_nid=parent_class_nid)
    return

TS/JS already handled this via _ts_emit_decorator_edges. Python was never wired up.

Fix

A _python_decorator_name helper placed directly above its twin _ts_decorator_name, differing only in grammar node names (Python call/attribute vs TS call_expression/member_expression), plus emission inside the existing decorated_definition branch.

  • Same shape as TS: a references edge with context="decorator" from the decorated function/class to each decorator symbol. No new edge type, so no schema, HTML exporter, MCP server, or affected change.
  • Owner ids reuse the definition branches' own formulas verbatim (_make_id(stem, ns, name) for classes, _make_id(parent_class_nid, name) for methods, _make_id(stem, name) for module-level functions), so the edge lands on the node the walk is about to create.
  • Targets go through ensure_named_node, so an imported decorator becomes a sourceless stub the corpus rewire collapses onto its real definition — this is what makes cross-file affected work.
  • Gated on config.ts_module == "tree_sitter_python"; decorated_definition is a Python-only grammar node.
  • The #1050 parent_class_nid propagation is untouched, with a regression test pinning it.

Covered forms: @deco, @deco(arg), @mod.deco(arg) (targets the symbol, not the module alias, matching _ts_decorator_name), stacked decorators, decorated classes, and decorated methods. Names that normalize_id() empties are skipped, matching the #1899 guard in the definition branches, so no edge can dangle.

Verification

End-to-end on the issue's repro, before:

Affected nodes for my_decorator()
- consumer.py [imports] pkg/consumer.py:L1

after:

Affected nodes for my_decorator()
- consumer.py [imports] pkg/consumer.py:L1
- business_logic() [references] pkg/consumer.py:L4
Check Result
New tests before the fix 7 of 9 fail (real negative controls)
New tests after 9 passed
tests/test_ts_decorators.py + Python extraction subsets 61 passed
Full suite, base 66d8110 15 failed, 3620 passed
Full suite, this branch 15 failed, 3629 passed
Failure-set diff identical, no new failures
ruff check passed
python -m tools.skillgen --check OK, 134 artifacts match

The 15 pre-existing failures are unrelated (test_ollama.py backend-detection and similar) and identical before and after; measured with -p no:randomly in the same env.

Known limitation

A comment placed between @ and the decorator expression (@ # note newline deco) drops that one edge. It is a missed edge, never an incorrect one, and does not affect decorators written in normal style. Left out of scope to keep the diff surgical; happy to fold in a fix if you would prefer it here.

Rishet11 added 2 commits July 25, 2026 06:11
…abs#2154)

Applying a Python decorator emitted no edge to the decorator symbol, so
`affected <decorator>` answered "No affected nodes found" for every
function it wraps — a silent false negative on reverse-impact queries.

TS/JS already emit these edges via `_ts_emit_decorator_edges`. The Python
`decorated_definition` branch walked its children only to propagate the
parent class id (Graphify-Labs#1050) and never looked at the `decorator` children.

Python now emits the same shape: a `references` edge with
context="decorator" from the decorated function/class to each decorator
symbol. Owner ids reuse the definition branches' own formulas, so the
edge lands on the node the walk creates. Targets go through
`ensure_named_node`, so an imported decorator becomes a sourceless stub
the corpus rewire collapses onto its real definition. Stacked, called
(`@retry(3)`) and attribute (`@app.route`) decorators are covered; the
attribute form targets the symbol, not the module alias, matching
`_ts_decorator_name`.
…2154)

Nine cases: the issue's imported-decorator repro, same-file resolution to
the local definition, called and attribute decorators, stacked
decorators, class-qualified method owners, a decorated class, the Graphify-Labs#1050
@Property class-qualification regression guard, and an absence control.
Copilot AI review requested due to automatic review settings July 25, 2026 00:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: applying a decorator creates no edge — affected <decorator> returns "No affected nodes found" for every function it wraps

2 participants