Skip to content

fix(python): walk a class nested in a class under its outer class - #2691

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

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

Conversation

@DeusData

Copy link
Copy Markdown
Owner

What

The Python LSP walked a class nested in a class body (plain or decorated) as if it were declared at module
level. Its methods were walked under proj.m.Inner while extraction defines them under
proj.m.Outer.Inner, so:

  • calls inside those methods never joined their extracted callers, and
  • self.method(), super().method() and builtin calls inside a nested class fell back to name guesses
    (suffix_match) that pick an unrelated same-named method elsewhere in the project.

py_process_class_in (py_lsp.c) now walks a nested class, and a decorated nested class, under the outer
class's QN, which is the QN extraction gives it. Classes nested in functions (#1548) and top-level classes
are unchanged.

Stacked on #2690 (decorated nested classes are extracted). This PR's base branch is #2690's branch,
fix/py-decorated-nested-class; once #2690 merges, this PR's diff against main is this change alone. No
index-format change.

Evidence

Base #2690 @4d71327c vs this change @ace384c9, 26 Python repos (manifest v1), production builds, macOS
arm64, --repeat 2, every repeat byte-identical. The corpus delta was measured against #2690 at 4d71327,
before #2679 (dotted base QNs, a different py_lsp.c function) reached main. The branch has since merged
main, and PR CI tests the merged tree. Nodes are unchanged, and so is every edge type except:

type base this PR +added -removed
CALLS 1,355,032 1,355,095 173 110
TESTS 511,529 511,531 5 3

Another 250 CALLS keep their target and move from a name guess to an LSP strategy.

  • Every one of the 110 removed CALLS is a suffix_match guess. Each one was replaced, on the same call
    line, by an LSP-resolved edge to a different target. The 173 added CALLS are all LSP-resolved
    (lsp_method 112, lsp_builtin_method 28, lsp_builtin_constructor 15, lsp_super 8, others 10).
  • Read per repo, every replacement swaps a wrong guess for the right binding:
    • mitmproxy (64): the kaitai-generated nested classes (DtlsClientHello.CipherSuites, .Cookie, ...)
      call their own _read, not Alpn._read.
    • sqlalchemy (17): HSTORE.Comparator.contains / has_all / ... reach the inherited
      TypeEngine.Comparator.operate, not associationproxy's operate.
    • wagtail (9): FieldPanel.BoundPanel / InlinePanel.BoundPanel self.is_shown / id_for_label /
      js_opts reach their own methods.
    • airflow (6): ReferenceModels.BaseDeadlineReference.evaluate_with's self._evaluate_with and
      SerializedBaseDeadlineReference.__eq__ / __hash__'s self.serialize_reference reach the class's
      own methods, not a subclass override.
    • home-assistant (5): Config._ConfigStore.async_save's super() call reaches
      helpers.storage.Store.async_save. A test-local TestFlow.SubentryFlowHandler calls its own
      async_step_*, not broadlink's BroadlinkFlowHandler.async_step_finish.
    • Smaller cases (sentry 4, matplotlib 3, django 1, sympy 1): super() reaches the real base (django
      UUIDField.get_db_prep_value), list() / dict() / str() reach the builtins, and
      ArrowStyle._Base.__call__'s self.transmute reaches _Base.transmute.
  • TESTS: the 3 removed edges came from the wrong home-assistant guess above. The 5 added edges are
    celery smoke tests in nested test classes reaching canvas.group.apply_async and Signature.delay.

Tests

  • pipeline_python_nested_class_methods_resolve{,_parallel}: calls inside the methods of a class nested
    in a class resolve through the LSP to the right targets, on the sequential and the parallel pipeline.
    RED without the fix (ASSERT(typed)).
  • Affected suites (complexity, extraction, registry, pipeline, cross_repo, py_lsp, py_lsp_stress,
    py_lsp_scale, lsp_resolution_probe, incremental) pass on the merged tree.

…e LSP

RED on F11a alone: py_process_class walks a nested class under the module, so none of its LSP rows join the method nodes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
py_process_class qualified every class under the module, so a class nested
in a class (proj.m.Outer.Inner, the QN extraction gives it) was walked as
proj.m.Inner: its methods' LSP rows carried callers no node has and never
joined. A typed-parameter call in such a method got no edge at all, a self
call fell to unique_name and a constructor to same_module. A nested class,
decorated or not, is now walked under its enclosing class through the same
py_process_class_in the function-local classes use.

Tests: pipeline_python_nested_class_methods_resolve(_parallel).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
…11a with origin/main 1346ac4)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData changed the base branch from fix/py-decorated-nested-class to main October 19, 2026 00:58
…57e4)

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData merged commit 4123268 into main Oct 19, 2026
28 checks passed
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