|
45 | 45 | from mypy.checker import FineGrainedDeferredNode |
46 | 46 | from mypy.server.aststrip import SavedAttributes |
47 | 47 | from mypy.util import is_typeshed_file |
| 48 | +from mypy.options import Options |
| 49 | +from mypy.plugin import ClassDefContext |
48 | 50 | import mypy.build |
49 | 51 |
|
50 | 52 | if TYPE_CHECKING: |
@@ -82,6 +84,8 @@ def semantic_analysis_for_scc(graph: 'Graph', scc: List[str], errors: Errors) -> |
82 | 84 | apply_semantic_analyzer_patches(patches) |
83 | 85 | # This pass might need fallbacks calculated above. |
84 | 86 | check_type_arguments(graph, scc, errors) |
| 87 | + # Run class decorator hooks (they requite complete MROs and no placeholders). |
| 88 | + apply_class_plugin_hooks(graph, scc, errors) |
85 | 89 | calculate_class_properties(graph, scc, errors) |
86 | 90 | check_blockers(graph, scc) |
87 | 91 | # Clean-up builtins, so that TypeVar etc. are not accessible without importing. |
@@ -132,6 +136,7 @@ def semantic_analysis_for_targets( |
132 | 136 |
|
133 | 137 | check_type_arguments_in_targets(nodes, state, state.manager.errors) |
134 | 138 | calculate_class_properties(graph, [state.id], state.manager.errors) |
| 139 | + apply_class_plugin_hooks(graph, [state.id], state.manager.errors) |
135 | 140 |
|
136 | 141 |
|
137 | 142 | def restore_saved_attrs(saved_attrs: SavedAttributes) -> None: |
@@ -382,14 +387,62 @@ def check_type_arguments_in_targets(targets: List[FineGrainedDeferredNode], stat |
382 | 387 | target.node.accept(analyzer) |
383 | 388 |
|
384 | 389 |
|
| 390 | +def apply_class_plugin_hooks(graph: 'Graph', scc: List[str], errors: Errors) -> None: |
| 391 | + """Apply class plugin hooks within a SCC. |
| 392 | +
|
| 393 | + We run these after to the main semantic analysis so that the hooks |
| 394 | + don't need to deal with incomplete definitions such as placeholder |
| 395 | + types. |
| 396 | +
|
| 397 | + Note that some hooks incorrectly run during the main semantic |
| 398 | + analysis pass, for historical reasons. |
| 399 | + """ |
| 400 | + num_passes = 0 |
| 401 | + incomplete = True |
| 402 | + # If we encounter a base class that has not been processed, we'll run another |
| 403 | + # pass. This should eventually reach a fixed point. |
| 404 | + while incomplete: |
| 405 | + assert num_passes < 10, "Internal error: too many class plugin hook passes" |
| 406 | + num_passes += 1 |
| 407 | + incomplete = False |
| 408 | + for module in scc: |
| 409 | + state = graph[module] |
| 410 | + tree = state.tree |
| 411 | + assert tree |
| 412 | + for _, node, _ in tree.local_definitions(): |
| 413 | + if isinstance(node.node, TypeInfo): |
| 414 | + if not apply_hooks_to_class(state.manager.semantic_analyzer, |
| 415 | + module, node.node, state.options, tree, errors): |
| 416 | + incomplete = True |
| 417 | + |
| 418 | + |
| 419 | +def apply_hooks_to_class(self: SemanticAnalyzer, |
| 420 | + module: str, |
| 421 | + info: TypeInfo, |
| 422 | + options: Options, |
| 423 | + file_node: MypyFile, |
| 424 | + errors: Errors) -> bool: |
| 425 | + # TODO: Move more class-related hooks here? |
| 426 | + defn = info.defn |
| 427 | + ok = True |
| 428 | + for decorator in defn.decorators: |
| 429 | + with self.file_context(file_node, options, info): |
| 430 | + decorator_name = self.get_fullname_for_hook(decorator) |
| 431 | + if decorator_name: |
| 432 | + hook = self.plugin.get_class_decorator_hook_2(decorator_name) |
| 433 | + if hook: |
| 434 | + ok = ok and hook(ClassDefContext(defn, decorator, self)) |
| 435 | + return ok |
| 436 | + |
| 437 | + |
385 | 438 | def calculate_class_properties(graph: 'Graph', scc: List[str], errors: Errors) -> None: |
386 | 439 | for module in scc: |
387 | | - tree = graph[module].tree |
| 440 | + state = graph[module] |
| 441 | + tree = state.tree |
388 | 442 | assert tree |
389 | 443 | for _, node, _ in tree.local_definitions(): |
390 | 444 | if isinstance(node.node, TypeInfo): |
391 | | - saved = (module, node.node, None) # module, class, function |
392 | | - with errors.scope.saved_scope(saved) if errors.scope else nullcontext(): |
| 445 | + with state.manager.semantic_analyzer.file_context(tree, state.options, node.node): |
393 | 446 | calculate_class_abstract_status(node.node, tree.is_stub, errors) |
394 | 447 | check_protocol_status(node.node, errors) |
395 | 448 | calculate_class_vars(node.node) |
|
0 commit comments