From e6df5374a0b020b551ff1a4ee230719a7c4124ec Mon Sep 17 00:00:00 2001 From: Ilya Priven Date: Mon, 12 Jun 2023 07:33:40 -0400 Subject: [PATCH] Simplify suppression of spurious 'unused-ignore's --- mypy/build.py | 1 - mypy/errors.py | 9 --------- mypy/nodes.py | 9 ++++++--- mypy/semanal_pass1.py | 8 +++----- mypy/server/update.py | 1 - 5 files changed, 9 insertions(+), 19 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 7913eae9c6ed..d48d95062fcc 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2239,7 +2239,6 @@ def semantic_analysis_pass1(self) -> None: analyzer = SemanticAnalyzerPreAnalysis() with self.wrap_context(): analyzer.visit_file(self.tree, self.xpath, self.id, options) - self.manager.errors.set_unreachable_lines(self.xpath, self.tree.unreachable_lines) # TODO: Do this while constructing the AST? self.tree.names = SymbolTable() if not self.tree.is_stub: diff --git a/mypy/errors.py b/mypy/errors.py index 9d29259e943c..91a9509591c1 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -222,9 +222,6 @@ class Errors: # (path -> line -> error-codes) ignored_lines: dict[str, dict[int, list[str]]] - # Lines that are statically unreachable (e.g. due to platform/version check). - unreachable_lines: dict[str, set[int]] - # Lines on which an error was actually ignored. used_ignored_lines: dict[str, dict[int, list[str]]] @@ -280,7 +277,6 @@ def initialize(self) -> None: self.import_ctx = [] self.function_or_member = [None] self.ignored_lines = {} - self.unreachable_lines = {} self.used_ignored_lines = defaultdict(lambda: defaultdict(list)) self.ignored_files = set() self.only_once_messages = set() @@ -329,9 +325,6 @@ def set_file_ignored_lines( if ignore_all: self.ignored_files.add(file) - def set_unreachable_lines(self, file: str, unreachable_lines: set[int]) -> None: - self.unreachable_lines[file] = unreachable_lines - def current_target(self) -> str | None: """Retrieves the current target from the associated scope. @@ -630,8 +623,6 @@ def generate_unused_ignore_errors(self, file: str) -> None: ignored_lines = self.ignored_lines[file] used_ignored_lines = self.used_ignored_lines[file] for line, ignored_codes in ignored_lines.items(): - if line in self.unreachable_lines[file]: - continue if codes.UNUSED_IGNORE.code in ignored_codes: continue used_ignored_codes = used_ignored_lines[line] diff --git a/mypy/nodes.py b/mypy/nodes.py index 8457e39b6aa1..f7d9f799c533 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -11,6 +11,7 @@ Any, Callable, Dict, + Iterable, Iterator, List, Optional, @@ -314,8 +315,6 @@ class MypyFile(SymbolNode): # If the value is empty, ignore all errors; otherwise, the list contains all # error codes to ignore. ignored_lines: dict[int, list[str]] - # Lines that are statically unreachable (e.g. due to platform/version check). - unreachable_lines: set[int] # Is this file represented by a stub file (.pyi)? is_stub: bool # Is this loaded from the cache and thus missing the actual body of the file? @@ -348,7 +347,6 @@ def __init__( self.ignored_lines = ignored_lines else: self.ignored_lines = {} - self.unreachable_lines = set() self.path = "" self.is_stub = False @@ -380,6 +378,11 @@ def is_package_init_file(self) -> bool: def is_future_flag_set(self, flag: str) -> bool: return flag in self.future_import_flags + def remove_ignored_lines(self, lines: Iterable[int]) -> None: + """Called to prevent spurious 'unused-ignore' errors in lines that we skip checking.""" + for line in lines: + self.ignored_lines.pop(line, None) + def serialize(self) -> JsonDict: return { ".class": "MypyFile", diff --git a/mypy/semanal_pass1.py b/mypy/semanal_pass1.py index 659f33e65ead..101aab3af141 100644 --- a/mypy/semanal_pass1.py +++ b/mypy/semanal_pass1.py @@ -47,7 +47,7 @@ class SemanticAnalyzerPreAnalysis(TraverserVisitor): def do_stuff(): # type: () -> None: - if sys.python_version < (3,): + if sys.version_info < (3,): import xyz # Only available in Python 2 xyz.whatever() ... @@ -62,7 +62,6 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) - self.cur_mod_node = file self.options = options self.is_global_scope = True - self.unreachable_lines: set[int] = set() for i, defn in enumerate(file.defs): defn.accept(self) @@ -74,10 +73,9 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: Options) - 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.unreachable_lines |= set(range(next_def.line, last.end_line + 1)) + file.remove_ignored_lines(range(next_def.line, last.end_line + 1)) del file.defs[i + 1 :] break - file.unreachable_lines = self.unreachable_lines def visit_func_def(self, node: FuncDef) -> None: old_global_scope = self.is_global_scope @@ -127,7 +125,7 @@ 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.unreachable_lines |= set(range(b.line, b.end_line + 1)) + self.cur_mod_node.remove_ignored_lines(range(b.line, b.end_line + 1)) return super().visit_block(b) diff --git a/mypy/server/update.py b/mypy/server/update.py index 7b439eb0ab9f..439cededda01 100644 --- a/mypy/server/update.py +++ b/mypy/server/update.py @@ -986,7 +986,6 @@ def key(node: FineGrainedDeferredNode) -> int: manager.errors.set_file_ignored_lines( file_node.path, file_node.ignored_lines, options.ignore_errors or state.ignore_all ) - manager.errors.set_unreachable_lines(file_node.path, file_node.unreachable_lines) targets = set() for node in nodes: