| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- """Block/import reachability analysis."""
- from __future__ import annotations
- from mypy.nodes import (
- AssertStmt,
- AssignmentStmt,
- Block,
- ClassDef,
- ExpressionStmt,
- ForStmt,
- FuncDef,
- IfStmt,
- Import,
- ImportAll,
- ImportFrom,
- MatchStmt,
- MypyFile,
- ReturnStmt,
- )
- from mypy.options import Options
- from mypy.reachability import (
- assert_will_always_fail,
- infer_reachability_of_if_statement,
- infer_reachability_of_match_statement,
- )
- from mypy.traverser import TraverserVisitor
- class SemanticAnalyzerPreAnalysis(TraverserVisitor):
- """Analyze reachability of blocks and imports and other local things.
- This runs before semantic analysis, so names have not been bound. Imports are
- also not resolved yet, so we can only access the current module.
- This determines static reachability of blocks and imports due to version and
- platform checks, among others.
- The main entry point is 'visit_file'.
- Reachability of imports needs to be determined very early in the build since
- this affects which modules will ultimately be processed.
- Consider this example:
- import sys
- def do_stuff():
- # type: () -> None:
- if sys.python_version < (3,):
- import xyz # Only available in Python 2
- xyz.whatever()
- ...
- The block containing 'import xyz' is unreachable in Python 3 mode. The import
- shouldn't be processed in Python 3 mode, even if the module happens to exist.
- """
- def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None:
- self.platform = options.platform
- self.cur_mod_id = mod_id
- self.cur_mod_node = file
- self.options = options
- self.is_global_scope = True
- self.skipped_lines: set[int] = set()
- for i, defn in enumerate(file.defs):
- defn.accept(self)
- if isinstance(defn, AssertStmt) and assert_will_always_fail(defn, options):
- # We've encountered an assert that's always false,
- # e.g. assert sys.platform == 'lol'. Truncate the
- # list of statements. This mutates file.defs too.
- if i < len(file.defs) - 1:
- next_def, last = file.defs[i + 1], file.defs[-1]
- if last.end_line is not None:
- # We are on a Python version recent enough to support end lines.
- self.skipped_lines |= set(range(next_def.line, last.end_line + 1))
- del file.defs[i + 1 :]
- break
- file.skipped_lines = self.skipped_lines
- def visit_func_def(self, node: FuncDef) -> None:
- old_global_scope = self.is_global_scope
- self.is_global_scope = False
- super().visit_func_def(node)
- self.is_global_scope = old_global_scope
- file_node = self.cur_mod_node
- if (
- self.is_global_scope
- and file_node.is_stub
- and node.name == "__getattr__"
- and file_node.is_package_init_file()
- ):
- # __init__.pyi with __getattr__ means that any submodules are assumed
- # to exist, even if there is no stub. Note that we can't verify that the
- # return type is compatible, since we haven't bound types yet.
- file_node.is_partial_stub_package = True
- def visit_class_def(self, node: ClassDef) -> None:
- old_global_scope = self.is_global_scope
- self.is_global_scope = False
- super().visit_class_def(node)
- self.is_global_scope = old_global_scope
- def visit_import_from(self, node: ImportFrom) -> None:
- node.is_top_level = self.is_global_scope
- super().visit_import_from(node)
- def visit_import_all(self, node: ImportAll) -> None:
- node.is_top_level = self.is_global_scope
- super().visit_import_all(node)
- def visit_import(self, node: Import) -> None:
- node.is_top_level = self.is_global_scope
- super().visit_import(node)
- def visit_if_stmt(self, s: IfStmt) -> None:
- infer_reachability_of_if_statement(s, self.options)
- for expr in s.expr:
- expr.accept(self)
- for node in s.body:
- node.accept(self)
- if s.else_body:
- s.else_body.accept(self)
- def visit_block(self, b: Block) -> None:
- if b.is_unreachable:
- if b.end_line is not None:
- # We are on a Python version recent enough to support end lines.
- self.skipped_lines |= set(range(b.line, b.end_line + 1))
- return
- super().visit_block(b)
- def visit_match_stmt(self, s: MatchStmt) -> None:
- infer_reachability_of_match_statement(s, self.options)
- for guard in s.guards:
- if guard is not None:
- guard.accept(self)
- for body in s.bodies:
- body.accept(self)
- # The remaining methods are an optimization: don't visit nested expressions
- # of common statements, since they can have no effect.
- def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
- pass
- def visit_expression_stmt(self, s: ExpressionStmt) -> None:
- pass
- def visit_return_stmt(self, s: ReturnStmt) -> None:
- pass
- def visit_for_stmt(self, s: ForStmt) -> None:
- s.body.accept(self)
- if s.else_body is not None:
- s.else_body.accept(self)
|