|
26 | 26 |
|
27 | 27 | from __future__ import annotations
|
28 | 28 |
|
| 29 | +from collections.abc import Iterator |
29 | 30 | from contextlib import nullcontext
|
| 31 | +from itertools import groupby |
30 | 32 | from typing import TYPE_CHECKING, Callable, Final, Optional, Union
|
31 | 33 | from typing_extensions import TypeAlias as _TypeAlias
|
32 | 34 |
|
@@ -232,26 +234,66 @@ def process_top_levels(graph: Graph, scc: list[str], patches: Patches) -> None:
|
232 | 234 | final_iteration = not any_progress
|
233 | 235 |
|
234 | 236 |
|
| 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 |
| 271 | + |
| 272 | + |
235 | 273 | def process_functions(graph: Graph, scc: list[str], patches: Patches) -> None:
|
236 | 274 | # Process functions.
|
| 275 | + all_targets = [] |
237 | 276 | for module in scc:
|
238 | 277 | tree = graph[module].tree
|
239 | 278 | assert tree is not None
|
240 |
| - analyzer = graph[module].manager.semantic_analyzer |
241 | 279 | # In principle, functions can be processed in arbitrary order,
|
242 | 280 | # but _methods_ must be processed in the order they are defined,
|
243 | 281 | # because some features (most notably partial types) depend on
|
244 | 282 | # order of definitions on self.
|
245 | 283 | #
|
246 | 284 | # There can be multiple generated methods per line. Use target
|
247 |
| - # name as the second sort key to get a repeatable sort order on |
248 |
| - # Python 3.5, which doesn't preserve dictionary order. |
| 285 | + # name as the second sort key to get a repeatable sort order. |
249 | 286 | targets = sorted(get_all_leaf_targets(tree), key=lambda x: (x[1].line, x[0]))
|
250 |
| - for target, node, active_type in targets: |
251 |
| - assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator)) |
252 |
| - process_top_level_function( |
253 |
| - analyzer, graph[module], module, target, node, active_type, patches |
254 |
| - ) |
| 287 | + all_targets.extend( |
| 288 | + [(module, target, node, active_type) for target, node, active_type in targets] |
| 289 | + ) |
| 290 | + |
| 291 | + for module, target, node, active_type in order_by_subclassing(all_targets): |
| 292 | + analyzer = graph[module].manager.semantic_analyzer |
| 293 | + assert isinstance(node, (FuncDef, OverloadedFuncDef, Decorator)) |
| 294 | + process_top_level_function( |
| 295 | + analyzer, graph[module], module, target, node, active_type, patches |
| 296 | + ) |
255 | 297 |
|
256 | 298 |
|
257 | 299 | def process_top_level_function(
|
@@ -308,6 +350,11 @@ def process_top_level_function(
|
308 | 350 | str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo]
|
309 | 351 | ]
|
310 | 352 |
|
| 353 | +# Same as above but includes module as first item. |
| 354 | +FullTargetInfo: _TypeAlias = tuple[ |
| 355 | + str, str, Union[MypyFile, FuncDef, OverloadedFuncDef, Decorator], Optional[TypeInfo] |
| 356 | +] |
| 357 | + |
311 | 358 |
|
312 | 359 | def get_all_leaf_targets(file: MypyFile) -> list[TargetInfo]:
|
313 | 360 | """Return all leaf targets in a symbol table (module-level and methods)."""
|
|
0 commit comments