Tipragot
628be439b8
Cela permet de ne pas avoir de problèmes de compatibilité car python est dans le git.
157 lines
5.3 KiB
Python
157 lines
5.3 KiB
Python
"""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() -> None:
|
|
if sys.version_info >= (3, 10):
|
|
import xyz # Only available in Python 3.10+
|
|
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)
|