2626
2727from __future__ import annotations
2828
29+ from collections .abc import Iterator
2930from contextlib import nullcontext
30- from functools import cmp_to_key
31+ from itertools import groupby
3132from typing import TYPE_CHECKING , Callable , Final , Optional , Union
3233from 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
250273def 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