Skip to content

Commit 3aee57d

Browse files
committed
Use an explicit ordering algorithm
1 parent 266b27e commit 3aee57d

File tree

1 file changed

+37
-18
lines changed

1 file changed

+37
-18
lines changed

mypy/semanal_main.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@
2626

2727
from __future__ import annotations
2828

29+
from collections.abc import Iterator
2930
from contextlib import nullcontext
30-
from functools import cmp_to_key
31+
from itertools import groupby
3132
from typing import TYPE_CHECKING, Callable, Final, Optional, Union
3233
from typing_extensions import TypeAlias as _TypeAlias
3334

@@ -233,18 +234,40 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None:
233234
final_iteration = not any_progress
234235

235236

236-
def method_order_by_subclassing(left: FullTargetInfo, right: FullTargetInfo) -> int:
237-
left_info = left[3]
238-
right_info = right[3]
239-
if left_info is None or right_info is None:
240-
return 0
241-
if left_info is right_info:
242-
return 0
243-
if left_info in right_info.mro:
244-
return -1
245-
if right_info in left_info.mro:
246-
return 1
247-
return 0
237+
def order_by_subclassing(targets: list[FullTargetInfo]) -> Iterator[FullTargetInfo]:
238+
"""Make sure that superclass methods are always processed before subclass methods.
239+
240+
This algorithm is not very optimal, but it is simple and should work well for lists
241+
that are already almost correctly ordered.
242+
"""
243+
244+
# First, group the targets by their TypeInfo (since targets are sorted by line,
245+
# we know that each TypeInfo will appear as group key only once).
246+
grouped = [(k, list(g)) for k, g in groupby(targets, key=lambda x: x[3])]
247+
remaining_infos = {info for info, _ in grouped if info is not None}
248+
249+
next_group = 0
250+
while grouped:
251+
if next_group >= len(grouped):
252+
# This should never happen, if there is an MRO cycle, it should be reported
253+
# and fixed during top-level processing.
254+
raise ValueError("Cannot order method targets by MRO")
255+
next_info, group = grouped[next_group]
256+
if next_info is None:
257+
# Trivial case, not methods but functions, process them straight away.
258+
yield from group
259+
grouped.pop(next_group)
260+
continue
261+
if any(parent in remaining_infos for parent in next_info.mro[1:]):
262+
# We cannot process this method group yet, try a next one.
263+
next_group += 1
264+
continue
265+
yield from group
266+
grouped.pop(next_group)
267+
remaining_infos.discard(next_info)
268+
# Each time after processing a method group we should retry from start,
269+
# since there may be some groups that are not blocked on parents anymore.
270+
next_group = 0
248271

249272

250273
def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None:
@@ -265,11 +288,7 @@ def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None:
265288
[(module, target, node, active_type) for target, node, active_type in targets]
266289
)
267290

268-
# Additionally, we process superclass methods before subclass methods. Here we rely
269-
# on stability of Python sort and just do a separate sort.
270-
for module, target, node, active_type in sorted(
271-
all_targets, key=cmp_to_key(method_order_by_subclassing)
272-
):
291+
for module, target, node, active_type in order_by_subclassing(all_targets):
273292
analyzer = graph[module].manager.semantic_analyzer
274293
assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator))
275294
process_top_level_function(

0 commit comments

Comments
 (0)