Skip to content

fix(python): extract a decorated class nested in a class body - #2690

Merged
DeusData merged 4 commits into
mainfrom
fix/py-decorated-nested-class
Oct 19, 2026
Merged

DeusData merged 4 commits into
mainfrom
fix/py-decorated-nested-class

Conversation

@DeusData

Copy link
Copy Markdown
Owner

What

A Python class body can declare classes behind a decorator:

class BoxStyle:
    @_register_style(_style_list)
    class Circle:
        def __call__(self, x0, y0, width, height, mutation_size): ...

push_nested_class_nodes (extract_defs.c) descended into a class body's direct children only, so a
nested class wrapped in a decorated_definition was never extracted. The class, its methods and every edge
they own were missing (matplotlib's BoxStyle / ArrowStyle styles, wagtail's Panel.BoundPanel family,
textual's nested Message subclasses, airflow's nested deadline models). For Python the walk now also
descends into decorated_definition, just as a top-level decorated class is handled. Plain nested
classes are unchanged.

Builds on #2686 (#1548 nested defs, merged). No index-format change.

Evidence

Base #2686 @090580b3 vs this change @4d71327c, 26 Python repos (manifest v1), production builds, macOS
arm64, --repeat 2, every repeat byte-identical (noise 0 on every type). The branch has since merged
main (1346ac4, which includes #2686 and #2679); its diff against main is the two files of this change,
and PR CI tests the merged tree.

type base this PR +added -removed
DEFINES 2,515,174 2,515,377 203 0
DEFINES_METHOD 397,865 397,995 130 0
DECORATES 243,846 243,951 105 0
OVERRIDE 82,497 82,576 79 0
INHERITS 78,598 78,660 78 16
MENTIONS 42,891 42,902 11 0
CALLS 1,354,959 1,355,032 234 161
USAGE 1,672,257 1,672,554 512 215
WRITES 536,179 536,222 147 104
TESTS 511,518 511,529 64 53
IMPORTS 477,476 477,476 30 30

18 of the 26 repos are identical apart from SEMANTICALLY_RELATED noise. Sampled and read (271 edges,
seed 20261017):

  • New definitions and their own edges: the decorated nested classes and their methods now exist, with
    correct DECORATES, OVERRIDE and INHERITS (to _Base, _Curve, Message) and correct calls inside them
    (e.g. BoxStyle.Ellipse.__call__ -> Path.unit_circle).
  • Re-attributed (same target, same line, new source): 33 CALLS, 55 USAGE, 21 WRITES. Calls and accesses
    inside a decorated nested class used to hang on the enclosing class or file. They now start at the
    nested class's own methods.
  • Retargeted (same source, new target): most of the remaining removals. With the new classes in the
    registry, edges that are resolved by name alone pick a different candidate:
    • Better: a name used inside its own class now finds that class's nested member. Examples: textual's
      Input._watch_value -> Input.Changed instead of Checkbox.Changed, and Tab.Clicked inherits
      Tab.TabMessage instead of Tabs.TabMessage. wagtail's MultiFieldPanel.BoundPanel inherits
      PanelGroup.BoundPanel, and Panel.get_bound_panel -> Panel.BoundPanel. airflow's test calls through
      SerializedReferenceModels.AverageRuntimeDeadline(...) reach that class instead of the task-sdk
      namesake, and textual's TextArea message tests go to TextArea.Changed.
    • Neutral: wagtail attribute names (classname, icon, attrs, classes: 88 USAGE, 81 WRITES) resolved
      by name only move from one wrong candidate to another (Table.Row.classname or the icon template
      tag become Panel.BoundPanel.classname / .icon).
    • Worse: self-calls inside a nested class are not walked by the Python LSP, so they stay low-confidence
      suffix_match guesses. With more candidates, the guess can now pick a subclass override. Example:
      airflow ReferenceModels.BaseDeadlineReference.evaluate_with's self._evaluate_with used to reach
      the class's own method and now reaches AverageRuntimeDeadline._evaluate_with. A follow-up walks
      nested classes in the Python LSP under their outer class, which binds these self-calls.

Exposed approximation (documented, not changed here)

matplotlib's patches.py defines both the top-level Circle / Ellipse / Wedge and the decorated
style classes BoxStyle.Circle, BoxStyle.Ellipse and ArrowStyle.Wedge. With matplotlib's lib/ layout,
from matplotlib.patches import Circle misses the module-path strategy, and the symbol-name fallback
picks the lexicographically smallest QN with that name. That is now patches.BoxStyle.Circle
(< patches.Circle). Result: 30 IMPORTS are retargeted to the nested style class, 74 CALLS go to
BoxStyle.Circle (40), BoxStyle.Ellipse (23) and ArrowStyle.Wedge (11), and 30 lsp_constructor plus
53 unique_name edges are lost because Circle is no longer unique. Two things cause this: the
fallback can bind an import to a class member, and lib/ packages are not mapped. The new nodes only make
it visible. The follow-up fixes the fallback: an imported name is a module-level binding, never a class
member, and lib/ packages get a package map.

Tests

  • pipeline_python_decorated_nested_class_extracted{,_parallel}: a decorated and a plain class nested
    in one class body. Both classes and their methods are extracted, on the sequential and the parallel
    pipeline. RED without the fix (ASSERT(dec_class)).
  • Affected suites (complexity, extraction, registry, pipeline, cross_repo, py_lsp,
    lsp_resolution_probe, incremental) pass on the merged tree.

RED on its own: push_nested_class_nodes does not look through decorated_definition.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
push_nested_class_nodes descended into field/template/declaration and conditional wrappers but not into Python's decorated_definition, so a decorated nested class (`@dataclass class Meta:` inside a class) was never extracted: no Class node, no Method nodes, and the calls in it fell to the FILE node.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…ed-class (pick up ef90708: origin/main incl. the Smart CI selection fix #2684)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…346ac4)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData merged commit 4a057e4 into main Oct 19, 2026
29 checks passed
DeusData added a commit that referenced this pull request Oct 19, 2026
…57e4)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
thugrock7 pushed a commit to thugrock7/codebase-memory-mcp that referenced this pull request Oct 19, 2026
…a#2680, DeusData#2690, DeusData#2695, DeusData#2696)

py_lsp.c: the module-member lookup of this branch and the C3 super() lookup of DeusData#2696 were added at the same place; both are kept.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
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.

1 participant