semanal_pass1.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. """Block/import reachability analysis."""
  2. from __future__ import annotations
  3. from mypy.nodes import (
  4. AssertStmt,
  5. AssignmentStmt,
  6. Block,
  7. ClassDef,
  8. ExpressionStmt,
  9. ForStmt,
  10. FuncDef,
  11. IfStmt,
  12. Import,
  13. ImportAll,
  14. ImportFrom,
  15. MatchStmt,
  16. MypyFile,
  17. ReturnStmt,
  18. )
  19. from mypy.options import Options
  20. from mypy.reachability import (
  21. assert_will_always_fail,
  22. infer_reachability_of_if_statement,
  23. infer_reachability_of_match_statement,
  24. )
  25. from mypy.traverser import TraverserVisitor
  26. class SemanticAnalyzerPreAnalysis(TraverserVisitor):
  27. """Analyze reachability of blocks and imports and other local things.
  28. This runs before semantic analysis, so names have not been bound. Imports are
  29. also not resolved yet, so we can only access the current module.
  30. This determines static reachability of blocks and imports due to version and
  31. platform checks, among others.
  32. The main entry point is 'visit_file'.
  33. Reachability of imports needs to be determined very early in the build since
  34. this affects which modules will ultimately be processed.
  35. Consider this example:
  36. import sys
  37. def do_stuff():
  38. # type: () -> None:
  39. if sys.python_version < (3,):
  40. import xyz # Only available in Python 2
  41. xyz.whatever()
  42. ...
  43. The block containing 'import xyz' is unreachable in Python 3 mode. The import
  44. shouldn't be processed in Python 3 mode, even if the module happens to exist.
  45. """
  46. def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None:
  47. self.platform = options.platform
  48. self.cur_mod_id = mod_id
  49. self.cur_mod_node = file
  50. self.options = options
  51. self.is_global_scope = True
  52. self.skipped_lines: set[int] = set()
  53. for i, defn in enumerate(file.defs):
  54. defn.accept(self)
  55. if isinstance(defn, AssertStmt) and assert_will_always_fail(defn, options):
  56. # We've encountered an assert that's always false,
  57. # e.g. assert sys.platform == 'lol'. Truncate the
  58. # list of statements. This mutates file.defs too.
  59. if i < len(file.defs) - 1:
  60. next_def, last = file.defs[i + 1], file.defs[-1]
  61. if last.end_line is not None:
  62. # We are on a Python version recent enough to support end lines.
  63. self.skipped_lines |= set(range(next_def.line, last.end_line + 1))
  64. del file.defs[i + 1 :]
  65. break
  66. file.skipped_lines = self.skipped_lines
  67. def visit_func_def(self, node: FuncDef) -> None:
  68. old_global_scope = self.is_global_scope
  69. self.is_global_scope = False
  70. super().visit_func_def(node)
  71. self.is_global_scope = old_global_scope
  72. file_node = self.cur_mod_node
  73. if (
  74. self.is_global_scope
  75. and file_node.is_stub
  76. and node.name == "__getattr__"
  77. and file_node.is_package_init_file()
  78. ):
  79. # __init__.pyi with __getattr__ means that any submodules are assumed
  80. # to exist, even if there is no stub. Note that we can't verify that the
  81. # return type is compatible, since we haven't bound types yet.
  82. file_node.is_partial_stub_package = True
  83. def visit_class_def(self, node: ClassDef) -> None:
  84. old_global_scope = self.is_global_scope
  85. self.is_global_scope = False
  86. super().visit_class_def(node)
  87. self.is_global_scope = old_global_scope
  88. def visit_import_from(self, node: ImportFrom) -> None:
  89. node.is_top_level = self.is_global_scope
  90. super().visit_import_from(node)
  91. def visit_import_all(self, node: ImportAll) -> None:
  92. node.is_top_level = self.is_global_scope
  93. super().visit_import_all(node)
  94. def visit_import(self, node: Import) -> None:
  95. node.is_top_level = self.is_global_scope
  96. super().visit_import(node)
  97. def visit_if_stmt(self, s: IfStmt) -> None:
  98. infer_reachability_of_if_statement(s, self.options)
  99. for expr in s.expr:
  100. expr.accept(self)
  101. for node in s.body:
  102. node.accept(self)
  103. if s.else_body:
  104. s.else_body.accept(self)
  105. def visit_block(self, b: Block) -> None:
  106. if b.is_unreachable:
  107. if b.end_line is not None:
  108. # We are on a Python version recent enough to support end lines.
  109. self.skipped_lines |= set(range(b.line, b.end_line + 1))
  110. return
  111. super().visit_block(b)
  112. def visit_match_stmt(self, s: MatchStmt) -> None:
  113. infer_reachability_of_match_statement(s, self.options)
  114. for guard in s.guards:
  115. if guard is not None:
  116. guard.accept(self)
  117. for body in s.bodies:
  118. body.accept(self)
  119. # The remaining methods are an optimization: don't visit nested expressions
  120. # of common statements, since they can have no effect.
  121. def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
  122. pass
  123. def visit_expression_stmt(self, s: ExpressionStmt) -> None:
  124. pass
  125. def visit_return_stmt(self, s: ReturnStmt) -> None:
  126. pass
  127. def visit_for_stmt(self, s: ForStmt) -> None:
  128. s.body.accept(self)
  129. if s.else_body is not None:
  130. s.else_body.accept(self)