From ec5eec200077b20d6c968e324f0c576bbb996e65 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Mon, 5 Oct 2026 20:07:07 +0100 Subject: [PATCH] Wait for all superclass method groups before processing subclasses --- mypy/semanal_main.py | 10 ++++++---- test-data/unit/check-classes.test | 12 ++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/mypy/semanal_main.py b/mypy/semanal_main.py index 0f2a1e3f30ea3..876849c240f7b 100644 --- a/mypy/semanal_main.py +++ b/mypy/semanal_main.py @@ -26,6 +26,7 @@ from __future__ import annotations +from collections import Counter from collections.abc import Callable, Iterator from contextlib import nullcontext from itertools import groupby @@ -222,10 +223,9 @@ def order_by_subclassing(targets: list[FullTargetInfo]) -> Iterator[FullTargetIn that are already almost correctly ordered. """ - # First, group the targets by their TypeInfo (since targets are sorted by line, - # we know that each TypeInfo will appear as group key only once). + # A nested class can split its enclosing class's methods into multiple groups. grouped = [(k, list(g)) for k, g in groupby(targets, key=lambda x: x[3])] - remaining_infos = {info for info, _ in grouped if info is not None} + remaining_infos = Counter(info for info, _ in grouped if info is not None) next_group = 0 while grouped: @@ -245,7 +245,9 @@ def order_by_subclassing(targets: list[FullTargetInfo]) -> Iterator[FullTargetIn continue yield from group grouped.pop(next_group) - remaining_infos.discard(next_info) + remaining_infos[next_info] -= 1 + if not remaining_infos[next_info]: + del remaining_infos[next_info] # Each time after processing a method group we should retry from start, # since there may be some groups that are not blocked on parents anymore. next_group = 0 diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index fa742f3571f82..3ee2b02726936 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7322,6 +7322,18 @@ class E(C): [targets __main__, __main__, __main__.C.__init__, __main__.D.g, __main__.D.f, __main__.E.g, __main__.E.f] +[case testAttributeDefOrderNestedClass] +class E(D): + def e(self) -> None: + self.x = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +class D: + def d(self) -> None: pass + class N: + def n(self) -> None: pass + def d2(self) -> None: + self.x = 0 + [case testNewReturnType1] class A: def __new__(cls) -> B: