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
| 28 | |
| 29 | |
| 30 | class SemanticAnalyzerPreAnalysis(TraverserVisitor): |
| 31 | """Analyze reachability of blocks and imports and other local things. |
| 32 | |
| 33 | This runs before semantic analysis, so names have not been bound. Imports are |
| 34 | also not resolved yet, so we can only access the current module. |
| 35 | |
| 36 | This determines static reachability of blocks and imports due to version and |
| 37 | platform checks, among others. |
| 38 | |
| 39 | The main entry point is 'visit_file'. |
| 40 | |
| 41 | Reachability of imports needs to be determined very early in the build since |
| 42 | this affects which modules will ultimately be processed. |
| 43 | |
| 44 | Consider this example: |
| 45 | |
| 46 | import sys |
| 47 | |
| 48 | def do_stuff() -> None: |
| 49 | if sys.version_info >= (3, 11): |
| 50 | import xyz # Only available in Python 3.11+ |
| 51 | xyz.whatever() |
| 52 | ... |
| 53 | |
| 54 | The block containing 'import xyz' is unreachable in Python 3.10 mode. The import |
| 55 | shouldn't be processed in Python 3.10 mode, even if the module happens to exist. |
| 56 | """ |
| 57 | |
| 58 | def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) -> None: |
| 59 | self.platform = options.platform |
| 60 | self.cur_mod_id = mod_id |
| 61 | self.cur_mod_node = file |
| 62 | self.options = options |
| 63 | self.is_global_scope = True |
| 64 | self.skipped_lines: set[int] = set() |
| 65 | |
| 66 | for i, defn in enumerate(file.defs): |
| 67 | defn.accept(self) |
| 68 | if isinstance(defn, AssertStmt) and assert_will_always_fail(defn, options): |
| 69 | # We've encountered an assert that's always false, |
| 70 | # e.g. assert sys.platform == 'lol'. Truncate the |
| 71 | # list of statements. This mutates file.defs too. |
| 72 | if i < len(file.defs) - 1: |
| 73 | next_def, last = file.defs[i + 1], file.defs[-1] |
| 74 | if last.end_line is not None: |
| 75 | # We are on a Python version recent enough to support end lines. |
| 76 | self.skipped_lines |= set(range(next_def.line, last.end_line + 1)) |
| 77 | file.imports = [ |
| 78 | i for i in file.imports if (i.line, i.column) <= (defn.line, defn.column) |
| 79 | ] |
| 80 | del file.defs[i + 1 :] |
| 81 | break |
| 82 | file.skipped_lines = self.skipped_lines |
| 83 | |
| 84 | def visit_func_def(self, node: FuncDef) -> None: |
| 85 | old_global_scope = self.is_global_scope |
| 86 | self.is_global_scope = False |
| 87 | super().visit_func_def(node) |
no outgoing calls
no test coverage detected
searching dependent graphs…