Repository: python/mypy
Pull request: Do not show unused-ignore errors in unreachable code, and make it a real error code
Fixes #8823 

The issue above makes `--warn-unused-ignores` problematic for cross-platform/cross-version code (btw it is one of the most upvoted bugs). Due to how the unused ignore errors are generated, it is hard to not generate them in unreachable code without having precise span information for blocks. So my fix will not work on Python 3.7, where end lines are not available, but taking into account that 3.7 reaches end of life in  less than 2 month, it is not worth the effort.

Also this PR makes `unused-ignore` work much more like a regular error code, so it can be used with `--enable-error-code`/`--disable-error-code` (including per-file) etc. More importantly this will also allow to use `--warn-unused-ignores` in code that uses e.g. `try/except` imports (instead of `if` version checks) with the help of `# type: ignore[import,unused-ignore]` (which btw will also help on Python 3.7).

Note also currently line numbers for `Block`s are actually wrong (and hence few TODOs in `fastparse.py`). So in this PR I set them all correctly and update a lot of parse tests (I went through all test updates and verified they are reasonable).

Problem statement:
Cannot silence "unused 'type: ignore'" in version specific code
I need to execute some code only on specific Python version (it is the use of `TextIOWrapper.reconfigure()` added in Python 3.7, but in following example I will use more abstract code).
```python
import sys

if sys.version_info >= (3, 7):
    x: int = 'a'
```
Since MyPy on Python 3.7 raises an error for it (actually because of the error in `typeshed`) I need to add `# type: ignore`:
```python
import sys

if sys.version_info >= (3, 7):
    x: int = 'a'  # type: ignore
```
But now I will get an error `unused 'type: ignore' comment` when run MyPy with `warn_unused_ignores = True` on Python 3.6.

So I cannot use variant 1, because it is an error on 3.7, and cannot use variant 2, because it is an error on 3.6.

MyPy should not complain about unused 'type: ignore' comment in the code which it does not analyze.

MyPy version is 0.770.

Hints from issue discussion:
As a workaround for this issue, you can use `cast`
It does not help in my particular issue.

The problem is that MyPy skips some parts of the code and complains about unused `type: ignore` in this code (which would be not ignored if this code be not skipped). It is not consistent and does not have a workaround.
Agree that there is an issue, but I think it can be worked around using cast, since that lets you remove the type ignore altogether:

```
~/delete λ cat test84.py
import sys
from typing import cast, Any

if sys.version_info >= (3, 7):
    x: int = cast(Any, 'a')
~/delete λ mypy --python-version 3.6 --warn-unused-ignores --warn-redundant-casts test84.py
Success: no issues found in 1 source file
~/delete λ mypy --python-version 3.7 --warn-unused-ignores --warn-redundant-casts test84.py
Success: no issues found in 1 source file
```

Looking at the issue on typeshed, you'd need to do something like `cast(Any, sys.stdout).reconfigure(errors="replace")`. It's not pretty, but it should work.
`x: int = 'a'` was just an example. It is the simplest way that I know to trigger an error. In real code I use
```python
sys.stdout.reconfigure(errors="replace")
```
and I do not know how to silence an error in this case.
Oh, and thank you for your suggestion. I tried similar workaround, but made an error in the code, so it did not work. Now I see my mistake and fixed it thank to you.
I think we need a way to silence unused type ignore messages for individual type ignores short of disabling the warning.
I'm running into a by-platform equivalent of this.  If I try to import `epoll` from the `select` module on macOS, I get an `[attr-defined]` error, but on Linux, that's fine.  Conversely, on Linux, one can't import `KQ_FILTER_READ` from `select`.

So, depending on which platform you run `mypy`, you get a different error, and I don't know how to suppress that class of error message.

That might be a different ticket, but it seems similar enough that maybe mentioning it in this one will do…?
Note the `select` issue I mention above appears to be new between Mypy `0.770` and `0.782`.
@wsanchez That is different. Are you using the right syntax for the platform check? Please ask on Gitter.
@gvanrossum Oh, OK, I didn't realize that Mypy could be told to do platform-specific things.  I'll read more.  https://github.com/twisted/twisted/pull/1416#issuecomment-702425277 describes the issue in a bit more detail.

Commit message: Move couple tests to 3.8+

Patch to review:
diff --git a/docs/source/error_code_list2.rst b/docs/source/error_code_list2.rst
index f160515f0a9e..7b06f8407ee4 100644
--- a/docs/source/error_code_list2.rst
+++ b/docs/source/error_code_list2.rst
@@ -347,3 +347,47 @@ silence the error:
 
     async def g() -> None:
         _ = asyncio.create_task(f())  # No error
+
+Check that ``# type: ignore`` comment is used [unused-ignore]
+-------------------------------------------------------------
+
+If you use :option:`--enable-error-code unused-ignore <mypy --enable-error-code>`,
+or :option:`--warn-unused-ignores <mypy --warn-unused-ignores>`
+mypy generates an error if you don't use a ``# type: ignore`` comment, i.e. if
+there is a comment, but there would be no error generated by mypy on this line
+anyway.
+
+Example:
+
+.. code-block:: python
+
+    # Use "mypy --warn-unused-ignores ..."
+
+    def add(a: int, b: int) -> int:
+        # Error: unused "type: ignore" comment
+        return a + b  # type: ignore
+
+Note that due to a specific nature of this comment, the only way to selectively
+silence it, is to include the error code explicitly. Also note that this error is
+not shown if the the ``# type: ignore`` is not used due to code being statically
+unreachable (e.g. due to platform or version check).
+
+Example:
+
+.. code-block:: python
+
+    # Use "mypy --warn-unused-ignores ..."
+
+    import sys
+
+    try:
+        # The "[unused-ignore]" is needed to get clean mypy run
+        # on both Python 3.8, and 3.9 where this module was added
+        import graphlib  # type: ignore[import,unused-ignore]
+    except ImportError:
+        pass
+
+    if sys.version_info >= (3, 9):
+        # The following will not generate an error on either
+        # Python 3.8, or Python 3.9
+        42 + "testing..."  # type: ignore
diff --git a/mypy/build.py b/mypy/build.py
index 01bc80c2fe81..c239afb56236 100644
--- a/mypy/build.py
+++ b/mypy/build.py
@@ -2238,6 +2238,7 @@ 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:
@@ -2572,7 +2573,10 @@ def dependency_lines(self) -> list[int]:
         return [self.dep_line_map.get(dep, 1) for dep in self.dependencies + self.suppressed]
 
     def generate_unused_ignore_notes(self) -> None:
-        if self.options.warn_unused_ignores:
+        if (
+            self.options.warn_unused_ignores
+            or codes.UNUSED_IGNORE in self.options.enabled_error_codes
+        ) and codes.UNUSED_IGNORE not in self.options.disabled_error_codes:
             # If this file was initially loaded from the cache, it may have suppressed
             # dependencies due to imports with ignores on them. We need to generate
             # those errors to avoid spuriously flagging them as unused ignores.
diff --git a/mypy/errorcodes.py b/mypy/errorcodes.py
index d274bb6dd5f0..6b63bad72683 100644
--- a/mypy/errorcodes.py
+++ b/mypy/errorcodes.py
@@ -221,6 +221,9 @@ def __str__(self) -> str:
 USED_BEFORE_DEF: Final[ErrorCode] = ErrorCode(
     "used-before-def", "Warn about variables that are used before they are defined", "General"
 )
+UNUSED_IGNORE: Final = ErrorCode(
+    "unused-ignore", "Ensure that all type ignores are used", "General", default_enabled=False
+)
 
 
 # Syntax errors are often blocking.
diff --git a/mypy/errors.py b/mypy/errors.py
index 09a905ccac54..7a45972b71a2 100644
--- a/mypy/errors.py
+++ b/mypy/errors.py
@@ -222,6 +222,9 @@ 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]]]
 
@@ -277,6 +280,7 @@ 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()
@@ -325,6 +329,9 @@ 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.
 
@@ -623,6 +630,10 @@ 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]
             unused_ignored_codes = set(ignored_codes) - set(used_ignored_codes)
             # `ignore` is used
@@ -639,7 +650,7 @@ def generate_unused_ignore_errors(self, file: str) -> None:
             for unused in unused_ignored_codes:
                 narrower = set(used_ignored_codes) & codes.sub_code_map[unused]
                 if narrower:
-                    message += f", use narrower [{', '.join(narrower)}] instead of [{unused}]"
+                    message += f", use narrower [{', '.join(narrower)}] instead of [{unused}] code"
             # Don't use report since add_error_info will ignore the error!
             info = ErrorInfo(
                 self.import_context(),
@@ -653,7 +664,7 @@ def generate_unused_ignore_errors(self, file: str) -> None:
                 -1,
                 "error",
                 message,
-                None,
+                codes.UNUSED_IGNORE,
                 False,
                 False,
                 False,
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
index b619bbe1368c..b34b5cf0f7df 100644
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -516,6 +516,7 @@ def translate_stmt_list(
                 codes.FILE.code
             )
             block = Block(self.fix_function_overloads(self.translate_stmt_list(stmts)))
+            self.set_block_lines(block, stmts)
             mark_block_unreachable(block)
             return [block]
 
@@ -611,20 +612,30 @@ def from_comp_operator(self, op: ast3.cmpop) -> str:
         else:
             return op_name
 
-    def as_block(self, stmts: list[ast3.stmt], lineno: int) -> Block | None:
+    def set_block_lines(self, b: Block, stmts: Sequence[ast3.stmt]) -> None:
+        first, last = stmts[0], stmts[-1]
+        b.line = first.lineno
+        b.column = first.col_offset
+        b.end_line = getattr(last, "end_lineno", None)
+        b.end_column = getattr(last, "end_col_offset", None)
+        if not b.body:
+            return
+        new_first = b.body[0]
+        if isinstance(new_first, (Decorator, OverloadedFuncDef)):
+            # Decorated function lines are different between Python versions.
+            # copy the normalization we do for them to block first lines.
+            b.line = new_first.line
+            b.column = new_first.column
+
+    def as_block(self, stmts: list[ast3.stmt]) -> Block | None:
         b = None
         if stmts:
             b = Block(self.fix_function_overloads(self.translate_stmt_list(stmts)))
-            b.set_line(lineno)
+            self.set_block_lines(b, stmts)
         return b
 
     def as_required_block(
-        self,
-        stmts: list[ast3.stmt],
-        lineno: int,
-        *,
-        can_strip: bool = False,
-        is_coroutine: bool = False,
+        self, stmts: list[ast3.stmt], *, can_strip: bool = False, is_coroutine: bool = False
     ) -> Block:
         assert stmts  # must be non-empty
         b = Block(
@@ -632,9 +643,7 @@ def as_required_block(
                 self.translate_stmt_list(stmts, can_strip=can_strip, is_coroutine=is_coroutine)
             )
         )
-        # TODO: in most call sites line is wrong (includes first line of enclosing statement)
-        # TODO: also we need to set the column, and the end position here.
-        b.set_line(lineno)
+        self.set_block_lines(b, stmts)
         return b
 
     def fix_function_overloads(self, stmts: list[Statement]) -> list[Statement]:
@@ -1023,7 +1032,7 @@ def do_func_def(
 
         self.class_and_function_stack.pop()
         self.class_and_function_stack.append("F")
-        body = self.as_required_block(n.body, lineno, can_strip=True, is_coroutine=is_coroutine)
+        body = self.as_required_block(n.body, can_strip=True, is_coroutine=is_coroutine)
         func_def = FuncDef(n.name, args, body, func_type)
         if isinstance(func_def.type, CallableType):
             # semanal.py does some in-place modifications we want to avoid
@@ -1052,9 +1061,6 @@ def do_func_def(
             func_def.is_decorated = True
             func_def.deco_line = deco_line
             func_def.set_line(lineno, n.col_offset, end_line, end_column)
-            # Set the line again after we updated it (to make value same in Python 3.7/3.8)
-            # Note that TODOs in as_required_block() apply here as well.
-            func_def.body.set_line(lineno)
 
             deco = Decorator(func_def, self.translate_expr_list(n.decorator_list), var)
             first = n.decorator_list[0]
@@ -1165,7 +1171,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
 
         cdef = ClassDef(
             n.name,
-            self.as_required_block(n.body, n.lineno),
+            self.as_required_block(n.body),
             None,
             self.translate_expr_list(n.bases),
             metaclass=dict(keywords).get("metaclass"),
@@ -1237,8 +1243,8 @@ def visit_For(self, n: ast3.For) -> ForStmt:
         node = ForStmt(
             self.visit(n.target),
             self.visit(n.iter),
-            self.as_required_block(n.body, n.lineno),
-            self.as_block(n.orelse, n.lineno),
+            self.as_required_block(n.body),
+            self.as_block(n.orelse),
             target_type,
         )
         return self.set_line(node, n)
@@ -1249,8 +1255,8 @@ def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt:
         node = ForStmt(
             self.visit(n.target),
             self.visit(n.iter),
-            self.as_required_block(n.body, n.lineno),
-            self.as_block(n.orelse, n.lineno),
+            self.as_required_block(n.body),
+            self.as_block(n.orelse),
             target_type,
         )
         node.is_async = True
@@ -1259,19 +1265,14 @@ def visit_AsyncFor(self, n: ast3.AsyncFor) -> ForStmt:
     # While(expr test, stmt* body, stmt* orelse)
     def visit_While(self, n: ast3.While) -> WhileStmt:
         node = WhileStmt(
-            self.visit(n.test),
-            self.as_required_block(n.body, n.lineno),
-            self.as_block(n.orelse, n.lineno),
+            self.visit(n.test), self.as_required_block(n.body), self.as_block(n.orelse)
         )
         return self.set_line(node, n)
 
     # If(expr test, stmt* body, stmt* orelse)
     def visit_If(self, n: ast3.If) -> IfStmt:
-        lineno = n.lineno
         node = IfStmt(
-            [self.visit(n.test)],
-            [self.as_required_block(n.body, lineno)],
-            self.as_block(n.orelse, lineno),
+            [self.visit(n.test)], [self.as_required_block(n.body)], self.as_block(n.orelse)
         )
         return self.set_line(node, n)
 
@@ -1281,7 +1282,7 @@ def visit_With(self, n: ast3.With) -> WithStmt:
         node = WithStmt(
             [self.visit(i.context_expr) for i in n.items],
             [self.visit(i.optional_vars) for i in n.items],
-            self.as_required_block(n.body, n.lineno),
+            self.as_required_block(n.body),
             target_type,
         )
         return self.set_line(node, n)
@@ -1292,7 +1293,7 @@ def visit_AsyncWith(self, n: ast3.AsyncWith) -> WithStmt:
         s = WithStmt(
             [self.visit(i.context_expr) for i in n.items],
             [self.visit(i.optional_vars) for i in n.items],
-            self.as_required_block(n.body, n.lineno),
+            self.as_required_block(n.body),
             target_type,
         )
         s.is_async = True
@@ -1309,15 +1310,15 @@ def visit_Try(self, n: ast3.Try) -> TryStmt:
             self.set_line(NameExpr(h.name), h) if h.name is not None else None for h in n.handlers
         ]
         types = [self.visit(h.type) for h in n.handlers]
-        handlers = [self.as_required_block(h.body, h.lineno) for h in n.handlers]
+        handlers = [self.as_required_block(h.body) for h in n.handlers]
 
         node = TryStmt(
-            self.as_required_block(n.body, n.lineno),
+            self.as_required_block(n.body),
             vs,
             types,
             handlers,
-            self.as_block(n.orelse, n.lineno),
-            self.as_block(n.finalbody, n.lineno),
+            self.as_block(n.orelse),
+            self.as_block(n.finalbody),
         )
         return self.set_line(node, n)
 
@@ -1326,15 +1327,15 @@ def visit_TryStar(self, n: TryStar) -> TryStmt:
             self.set_line(NameExpr(h.name), h) if h.name is not None else None for h in n.handlers
         ]
         types = [self.visit(h.type) for h in n.handlers]
-        handlers = [self.as_required_block(h.body, h.lineno) for h in n.handlers]
+        handlers = [self.as_required_block(h.body) for h in n.handlers]
 
         node = TryStmt(
-            self.as_required_block(n.body, n.lineno),
+            self.as_required_block(n.body),
             vs,
             types,
             handlers,
-            self.as_block(n.orelse, n.lineno),
-            self.as_block(n.finalbody, n.lineno),
+            self.as_block(n.orelse),
+            self.as_block(n.finalbody),
         )
         node.is_star = True
         return self.set_line(node, n)
@@ -1469,9 +1470,7 @@ def visit_Lambda(self, n: ast3.Lambda) -> LambdaExpr:
         body.col_offset = n.body.col_offset
 
         self.class_and_function_stack.append("L")
-        e = LambdaExpr(
-            self.transform_args(n.args, n.lineno), self.as_required_block([body], n.lineno)
-        )
+        e = LambdaExpr(self.transform_args(n.args, n.lineno), self.as_required_block([body]))
         self.class_and_function_stack.pop()
         e.set_line(n.lineno, n.col_offset)  # Overrides set_line -- can't use self.set_line
         return e
@@ -1743,7 +1742,7 @@ def visit_Match(self, n: Match) -> MatchStmt:
             self.visit(n.subject),
             [self.visit(c.pattern) for c in n.cases],
             [self.visit(c.guard) for c in n.cases],
-            [self.as_required_block(c.body, n.lineno) for c in n.cases],
+            [self.as_required_block(c.body) for c in n.cases],
         )
         return self.set_line(node, n)
 
diff --git a/mypy/nodes.py b/mypy/nodes.py
index 0ec0d81cfbc8..f36bda13d53c 100644
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -287,6 +287,7 @@ class MypyFile(SymbolNode):
         "names",
         "imports",
         "ignored_lines",
+        "unreachable_lines",
         "is_stub",
         "is_cache_skeleton",
         "is_partial_stub_package",
@@ -313,6 +314,8 @@ 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?
@@ -345,6 +348,7 @@ def __init__(
             self.ignored_lines = ignored_lines
         else:
             self.ignored_lines = {}
+        self.unreachable_lines = set()
 
         self.path = ""
         self.is_stub = False
diff --git a/mypy/semanal_pass1.py b/mypy/semanal_pass1.py
index 55430be00a1e..659f33e65ead 100644
--- a/mypy/semanal_pass1.py
+++ b/mypy/semanal_pass1.py
@@ -62,6 +62,7 @@ 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)
@@ -69,8 +70,14 @@ def visit_file(self, file: MypyFile, fnam: str, mod_id: str, options: 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.unreachable_lines |= set(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
@@ -118,6 +125,9 @@ def visit_if_stmt(self, s: IfStmt) -> None:
 
     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))
             return
         super().visit_block(b)
 
diff --git a/mypy/server/update.py b/mypy/server/update.py
index 70c1a2df0195..8a92fb62e43e 100644
--- a/mypy/server/update.py
+++ b/mypy/server/update.py
@@ -988,6 +988,7 @@ 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:
diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test
index 33c8f7365375..fc498c9aa6c0 100644
--- a/test-data/unit/check-errorcodes.test
+++ b/test-data/unit/check-errorcodes.test
@@ -83,7 +83,7 @@ b = 'x'.foobar(c) # type: int # type: ignore[name-defined, xyz]  # E: "str" has
 
 [case testErrorCodeWarnUnusedIgnores1]
 # flags: --warn-unused-ignores
-x # type: ignore[name-defined, attr-defined] # E: Unused "type: ignore[attr-defined]" comment
+x # type: ignore[name-defined, attr-defined] # E: Unused "type: ignore[attr-defined]" comment  [unused-ignore]
 
 [case testErrorCodeWarnUnusedIgnores2]
 # flags: --warn-unused-ignores
@@ -91,19 +91,19 @@ x # type: ignore[name-defined, attr-defined] # E: Unused "type: ignore[attr-defi
 
 [case testErrorCodeWarnUnusedIgnores3]
 # flags: --warn-unused-ignores
-"x".foobar(y) # type: ignore[name-defined, attr-defined, xyz] # E: Unused "type: ignore[xyz]" comment
+"x".foobar(y) # type: ignore[name-defined, attr-defined, xyz] # E: Unused "type: ignore[xyz]" comment  [unused-ignore]
 
 [case testErrorCodeWarnUnusedIgnores4]
 # flags: --warn-unused-ignores
-"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type] # E: Unused "type: ignore[valid-type]" comment
+"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type] # E: Unused "type: ignore[valid-type]" comment  [unused-ignore]
 
 [case testErrorCodeWarnUnusedIgnores5]
 # flags: --warn-unused-ignores
-"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type, xyz] # E: Unused "type: ignore[valid-type, xyz]" comment
+"x".foobar(y) # type: ignore[name-defined, attr-defined, valid-type, xyz] # E: Unused "type: ignore[valid-type, xyz]" comment  [unused-ignore]
 
 [case testErrorCodeWarnUnusedIgnores6_NoDetailWhenSingleErrorCode]
 # flags: --warn-unused-ignores
-"x" # type: ignore[name-defined] # E: Unused "type: ignore" comment
+"x" # type: ignore[name-defined] # E: Unused "type: ignore" comment  [unused-ignore]
 
 [case testErrorCodeMissingWhenRequired]
 # flags: --enable-error-code ignore-without-code
@@ -114,9 +114,11 @@ z # type: ignore[name-defined]
 
 [case testErrorCodeMissingDoesntTrampleUnusedIgnoresWarning]
 # flags: --enable-error-code ignore-without-code --warn-unused-ignores
-"x" # type: ignore # E: Unused "type: ignore" comment
-"y" # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment
-z # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment # E: Name "z" is not defined  [name-defined] # N: Error code "name-defined" not covered by "type: ignore" comment
+"x" # type: ignore # E: Unused "type: ignore" comment  [unused-ignore]
+"y" # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment  [unused-ignore]
+z # type: ignore[ignore-without-code] # E: Unused "type: ignore" comment  [unused-ignore] \
+                                      # E: Name "z" is not defined  [name-defined] \
+                                      # N: Error code "name-defined" not covered by "type: ignore" comment
 
 [case testErrorCodeMissingWholeFileIgnores]
 # flags: --enable-error-code ignore-without-code
@@ -1070,4 +1072,8 @@ A.f = h  # OK
 class A:
     def f(self) -> None: pass
 def h(self: A) -> None: pass
-A.f = h  # type: ignore[assignment]  # E: Unused "type: ignore" comment, use narrower [method-assign] instead of [assignment]
+A.f = h  # type: ignore[assignment]  # E: Unused "type: ignore" comment, use narrower [method-assign] instead of [assignment] code  [unused-ignore]
+
+[case testUnusedIgnoreEnableCode]
+# flags: --enable-error-code=unused-ignore
+x = 1  # type: ignore  # E: Unused "type: ignore" comment  [unused-ignore]
diff --git a/test-data/unit/check-ignore.test b/test-data/unit/check-ignore.test
index 982fb67f4e7f..fa451f373e70 100644
--- a/test-data/unit/check-ignore.test
+++ b/test-data/unit/check-ignore.test
@@ -220,29 +220,24 @@ def f() -> None: pass
 yield  # type: ignore  # E: "yield" outside function
 
 [case testIgnoreWholeModule1]
-# flags: --warn-unused-ignores
-# type: ignore
-IGNORE # type: ignore  # E: Unused "type: ignore" comment
-
-[case testIgnoreWholeModule2]
 # type: ignore
 if True:
     IGNORE
 
-[case testIgnoreWholeModule3]
+[case testIgnoreWholeModule2]
 # type: ignore
 @d
 class C: ...
 IGNORE
 
-[case testIgnoreWholeModule4]
+[case testIgnoreWholeModule3]
 # type: ignore
 @d
 
 def f(): ...
 IGNORE
 
-[case testIgnoreWholeModule5]
+[case testIgnoreWholeModule4]
 # type: ignore
 import MISSING
 
@@ -279,3 +274,19 @@ class CD(six.with_metaclass(M)):  # E: Multiple metaclass definitions
     42 + 'no way'  # type: ignore
 
 [builtins fixtures/tuple.pyi]
+
+[case testUnusedIgnoreTryExcept]
+# flags: --warn-unused-ignores
+try:
+    import foo  # type: ignore  # E: Unused "type: ignore" comment
+    import bar  # type: ignore[import]  # E: Unused "type: ignore" comment
+    import foobar  # type: ignore[unused-ignore]
+    import barfoo  # type: ignore[import,unused-ignore]
+    import missing  # type: ignore[import,unused-ignore]
+except Exception:
+    pass
+[file foo.py]
+[file bar.py]
+[file foobar.py]
+[file barfoo.py]
+[builtins fixtures/exception.pyi]
diff --git a/test-data/unit/check-python38.test b/test-data/unit/check-python38.test
index b9f9f2173ae1..bb1768d1d17d 100644
--- a/test-data/unit/check-python38.test
+++ b/test-data/unit/check-python38.test
@@ -775,3 +775,18 @@ main:9: note: Revealed type is "builtins.int"
 class C:
     [(j := i) for i in [1, 2, 3]]  # E: Assignment expression within a comprehension cannot be used in a class body
 [builtins fixtures/list.pyi]
+
+[case testIgnoreWholeModule]
+# flags: --warn-unused-ignores
+# type: ignore
+IGNORE # type: ignore
+
+[case testUnusedIgnoreVersionCheck]
+# flags: --warn-unused-ignores
+import sys
+
+if sys.version_info < (3, 6):
+    42  # type: ignore
+else:
+    42  # type: ignore  # E: Unused "type: ignore" comment
+[builtins fixtures/ops.pyi]
diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test
index 6522391899de..a9e025632e24 100644
--- a/test-data/unit/check-unreachable-code.test
+++ b/test-data/unit/check-unreachable-code.test
@@ -1446,4 +1446,4 @@ class Foo:
 def f() -> None:
     Foo()['a'] = 'a'
     x = 0 # This should not be reported as unreachable
-[builtins fixtures/exception.pyi]
\ No newline at end of file
+[builtins fixtures/exception.pyi]
diff --git a/test-data/unit/daemon.test b/test-data/unit/daemon.test
index 8bc85c5cd101..785577e7fbe6 100644
--- a/test-data/unit/daemon.test
+++ b/test-data/unit/daemon.test
@@ -528,10 +528,10 @@ class B:
 $ dmypy start -- --warn-unused-ignores --no-error-summary
 Daemon started
 $ dmypy check -- bar.py
-bar.py:2: error: Unused "type: ignore" comment
+bar.py:2: error: Unused "type: ignore" comment  [unused-ignore]
 == Return code: 1
 $ dmypy check -- bar.py
-bar.py:2: error: Unused "type: ignore" comment
+bar.py:2: error: Unused "type: ignore" comment  [unused-ignore]
 == Return code: 1
 
 [file foo/__init__.py]
@@ -562,11 +562,11 @@ a = 1  # type: ignore
 $ dmypy start -- --warn-unused-ignores --no-error-summary
 Daemon started
 $ dmypy check -- bar.py
-bar.py:2: error: Unused "type: ignore" comment
+bar.py:2: error: Unused "type: ignore" comment  [unused-ignore]
 == Return code: 1
 $ {python} -c "print('\n')" >> bar.py
 $ dmypy check -- bar.py
-bar.py:2: error: Unused "type: ignore" comment
+bar.py:2: error: Unused "type: ignore" comment  [unused-ignore]
 == Return code: 1
 
 [file foo/__init__.py]
diff --git a/test-data/unit/merge.test b/test-data/unit/merge.test
index 42d38c89482c..19b1839f86c0 100644
--- a/test-data/unit/merge.test
+++ b/test-data/unit/merge.test
@@ -39,7 +39,7 @@ MypyFile:1<1>(
   FuncDef:1<2>(
     f
     def () -> builtins.int<3>
-    Block:1<4>(
+    Block:2<4>(
       PassStmt:2<5>())))
 ==>
 MypyFile:1<0>(
@@ -50,7 +50,7 @@ MypyFile:1<1>(
   FuncDef:1<2>(
     f
     def () -> builtins.int<3>
-    Block:1<6>(
+    Block:2<6>(
       PassStmt:2<7>())))
 
 [case testClass]
@@ -77,7 +77,7 @@ MypyFile:1<1>(
         Var(self)
         Var(x))
       def (self: target.A<4>, x: builtins.str<5>) -> builtins.int<6>
-      Block:2<7>(
+      Block:3<7>(
         PassStmt:3<8>()))))
 ==>
 MypyFile:1<0>(
@@ -93,7 +93,7 @@ MypyFile:1<1>(
         Var(self)
         Var(x))
       def (self: target.A<4>, x: builtins.int<6>) -> builtins.str<5>
-      Block:2<10>(
+      Block:3<10>(
         PassStmt:3<11>()))))
 
 [case testClass_typeinfo]
@@ -149,7 +149,7 @@ MypyFile:1<1>(
       Args(
         Var(self))
       def (self: target.A<4>) -> target.B<5>
-      Block:2<6>(
+      Block:3<6>(
         ReturnStmt:3<7>(
           CallExpr:3<8>(
             NameExpr(B [target.B<5>])
@@ -173,7 +173,7 @@ MypyFile:1<1>(
       Args(
         Var(self))
       def (self: target.A<4>) -> target.B<5>
-      Block:3<14>(
+      Block:4<14>(
         ExpressionStmt:4<15>(
           IntExpr(1))
         ReturnStmt:5<16>(
@@ -204,7 +204,7 @@ MypyFile:1<1>(
       Args(
         Var(self))
       def (self: target.A<4>)
-      Block:2<5>(
+      Block:3<5>(
         ExpressionStmt:3<6>(
           CallExpr:3<7>(
             MemberExpr:3<8>(
@@ -224,7 +224,7 @@ MypyFile:1<1>(
       Args(
         Var(self))
       def (self: target.A<4>)
-      Block:2<11>(
+      Block:3<11>(
         ExpressionStmt:3<12>(
           CallExpr:3<13>(
             MemberExpr:3<14>(
@@ -257,7 +257,7 @@ MypyFile:1<1>(
       Args(
         Var(self))
       def (self: target.A<4>)
-      Block:2<5>(
+      Block:3<5>(
         AssignmentStmt:3<6>(
           MemberExpr:3<8>(
             NameExpr(self [l<9>])
@@ -280,7 +280,7 @@ MypyFile:1<1>(
       Args(
         Var(self))
       def (self: target.A<4>)
-      Block:2<13>(
+      Block:3<13>(
         AssignmentStmt:3<14>(
           MemberExpr:3<15>(
             NameExpr(self [l<16>])
diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test
index 22ebf31a7dbe..10ceaa947fd4 100644
--- a/test-data/unit/parse.test
+++ b/test-data/unit/parse.test
@@ -201,7 +201,7 @@ def main():
 MypyFile:1(
   FuncDef:1(
     main
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         IntExpr(1)))))
 
@@ -212,7 +212,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testIf]
@@ -286,7 +286,7 @@ while 1:
 MypyFile:1(
   WhileStmt:1(
     IntExpr(1)
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testReturn]
@@ -296,7 +296,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ReturnStmt:2(
         IntExpr(1)))))
 
@@ -308,7 +308,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ReturnStmt:2())))
 
 [case testBreak]
@@ -318,7 +318,7 @@ while 1:
 MypyFile:1(
   WhileStmt:1(
     IntExpr(1)
-    Block:1(
+    Block:2(
       BreakStmt:2())))
 
 [case testLargeBlock]
@@ -338,7 +338,7 @@ MypyFile:1(
         IntExpr(1))
       WhileStmt:3(
         IntExpr(2)
-        Block:3(
+        Block:4(
           PassStmt:4()))
       AssignmentStmt:5(
         NameExpr(y)
@@ -356,7 +356,7 @@ MypyFile:1(
       f
       Args(
         Var(self))
-      Block:2(
+      Block:3(
         PassStmt:3()))))
 
 [case testGlobalVarWithType]
@@ -382,7 +382,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(x)
         IntExpr(0)
@@ -411,7 +411,7 @@ MypyFile:1(
     Args(
       Var(y))
     def (y: str?) -> int?
-    Block:1(
+    Block:2(
       ReturnStmt:2()))
   ClassDef:3(
     A
@@ -422,14 +422,14 @@ MypyFile:1(
         Var(a)
         Var(b))
       def (self: Any, a: int?, b: Any?) -> x?
-      Block:4(
+      Block:5(
         PassStmt:5()))
     FuncDef:6(
       g
       Args(
         Var(self))
       def (self: Any) -> Any?
-      Block:6(
+      Block:7(
         PassStmt:7()))))
 
 [case testFuncWithNoneReturn]
@@ -440,7 +440,7 @@ MypyFile:1(
   FuncDef:1(
     f
     def () -> None?
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testVarDefWithGenericType]
@@ -467,7 +467,7 @@ MypyFile:1(
     Args(
       Var(y))
     def (y: t?[Any?, x?]) -> a?[b?[c?], d?]
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testParsingExpressionsWithLessAndGreaterThan]
@@ -587,7 +587,7 @@ MypyFile:1(
       __init__
       Args(
         Var(self))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           MemberExpr:3(
             NameExpr(self)
@@ -783,7 +783,7 @@ MypyFile:1(
   ForStmt:1(
     NameExpr(x)
     NameExpr(y)
-    Block:1(
+    Block:2(
       PassStmt:2()))
   ForStmt:3(
     TupleExpr:3(
@@ -792,7 +792,7 @@ MypyFile:1(
         NameExpr(y)
         NameExpr(w)))
     NameExpr(z)
-    Block:3(
+    Block:4(
       ExpressionStmt:4(
         IntExpr(1))))
   ForStmt:5(
@@ -802,7 +802,7 @@ MypyFile:1(
         NameExpr(y)
         NameExpr(w)))
     NameExpr(z)
-    Block:5(
+    Block:6(
       ExpressionStmt:6(
         IntExpr(1)))))
 
@@ -816,7 +816,7 @@ MypyFile:1(
     x)
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       GlobalDecl:3(
         x
         y))))
@@ -829,10 +829,10 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       FuncDef:2(
         g
-        Block:2(
+        Block:3(
           NonlocalDecl:3(
             x
             y))))))
@@ -852,9 +852,9 @@ except:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
-    Block:3(
+    Block:4(
       RaiseStmt:4())))
 
 [case testRaiseFrom]
@@ -1049,7 +1049,7 @@ MypyFile:1(
   Import:2(x)
   FuncDef:3(
     f
-    Block:3(
+    Block:4(
       ImportFrom:4(x, [y])
       ImportAll:5(z))))
 
@@ -1072,7 +1072,7 @@ MypyFile:1(
       default(
         Var(x)
         IntExpr(1)))
-    Block:1(
+    Block:2(
       PassStmt:2()))
   FuncDef:3(
     g
@@ -1089,7 +1089,7 @@ MypyFile:1(
         TupleExpr:3(
           IntExpr(1)
           IntExpr(2))))
-    Block:3(
+    Block:4(
       PassStmt:4())))
 
 [case testTryFinally]
@@ -1100,7 +1100,7 @@ finally:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         IntExpr(1)))
     Finally(
@@ -1115,11 +1115,11 @@ except x:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         IntExpr(1)))
     NameExpr(x)
-    Block:3(
+    Block:4(
       ExpressionStmt:4(
         IntExpr(2)))))
 
@@ -1133,18 +1133,18 @@ except x.y:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         IntExpr(1)))
     NameExpr(x)
     NameExpr(y)
-    Block:3(
+    Block:4(
       ExpressionStmt:4(
         IntExpr(2)))
     MemberExpr:5(
       NameExpr(x)
       y)
-    Block:5(
+    Block:6(
       ExpressionStmt:6(
         IntExpr(3)))))
 
@@ -1298,7 +1298,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         YieldExpr:2(
           OpExpr:2(
@@ -1313,7 +1313,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         YieldFromExpr:2(
           CallExpr:2(
@@ -1327,7 +1327,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(a)
         YieldFromExpr:2(
@@ -1387,7 +1387,7 @@ MypyFile:1(
     f
     Args(
       Var(x))
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testLambda]
@@ -1458,7 +1458,7 @@ MypyFile:1(
       NameExpr(i)
       NameExpr(j))
     NameExpr(x)
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testForAndTrailingCommaAfterIndexVar]
@@ -1470,7 +1470,7 @@ MypyFile:1(
     TupleExpr:1(
       NameExpr(i))
     NameExpr(x)
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testListComprehensionAndTrailingCommaAfterIndexVar]
@@ -1496,7 +1496,7 @@ MypyFile:1(
       NameExpr(i)
       NameExpr(j))
     NameExpr(x)
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testGeneratorWithCondition]
@@ -1628,7 +1628,7 @@ MypyFile:1(
           StrExpr(foo))))
     Target(
       NameExpr(f))
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testWithStatementWithoutTarget]
@@ -1639,7 +1639,7 @@ MypyFile:1(
   WithStmt:1(
     Expr(
       NameExpr(foo))
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testHexOctBinLiterals]
@@ -1671,7 +1671,7 @@ while 1:
 MypyFile:1(
   WhileStmt:1(
     IntExpr(1)
-    Block:1(
+    Block:2(
       ContinueStmt:2())))
 
 [case testStrLiteralConcatenate]
@@ -1700,19 +1700,19 @@ except:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         IntExpr(1)))
-    Block:3(
+    Block:4(
       PassStmt:4()))
   TryStmt:5(
-    Block:5(
+    Block:6(
       ExpressionStmt:6(
         IntExpr(1)))
     NameExpr(x)
-    Block:7(
+    Block:8(
       PassStmt:8())
-    Block:9(
+    Block:10(
       ExpressionStmt:10(
         IntExpr(2)))))
 
@@ -1726,10 +1726,10 @@ else:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
     NameExpr(x)
-    Block:3(
+    Block:4(
       ExpressionStmt:4(
         IntExpr(1)))
     Else(
@@ -1746,19 +1746,19 @@ except (a, b, c) as e:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
     TupleExpr:3(
       NameExpr(x)
       NameExpr(y))
-    Block:3(
+    Block:4(
       PassStmt:4())
     TupleExpr:5(
       NameExpr(a)
       NameExpr(b)
       NameExpr(c))
     NameExpr(e)
-    Block:5(
+    Block:6(
       PassStmt:6())))
 
 [case testNestedFunctions]
@@ -1772,19 +1772,19 @@ def h() -> int:
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       FuncDef:2(
         g
-        Block:2(
+        Block:3(
           PassStmt:3()))))
   FuncDef:4(
     h
     def () -> int?
-    Block:4(
+    Block:5(
       FuncDef:5(
         g
         def () -> int?
-        Block:5(
+        Block:6(
           PassStmt:6())))))
 
 [case testStatementsAndDocStringsInClassBody]
@@ -1806,7 +1806,7 @@ MypyFile:1(
       f
       Args(
         Var(self))
-      Block:4(
+      Block:5(
         PassStmt:5()))))
 
 [case testSingleLineClass]
@@ -1828,7 +1828,7 @@ MypyFile:1(
     NameExpr(property)
     FuncDef:2(
       f
-      Block:2(
+      Block:3(
         PassStmt:3()))))
 
 [case testComplexDecorator]
@@ -1849,7 +1849,7 @@ MypyFile:1(
     FuncDef:3(
       f
       def () -> int?
-      Block:3(
+      Block:4(
         PassStmt:4()))))
 
 [case testKeywordArgInCall]
@@ -2034,7 +2034,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         YieldExpr:2()))))
 
@@ -2120,7 +2120,7 @@ MypyFile:1(
   ForStmt:1(
     NameExpr(x)
     NameExpr(y)
-    Block:1(
+    Block:2(
       PassStmt:2())
     Else(
       ExpressionStmt:4(
@@ -2135,7 +2135,7 @@ else:
 MypyFile:1(
   WhileStmt:1(
     NameExpr(x)
-    Block:1(
+    Block:2(
       PassStmt:2())
     Else(
       ExpressionStmt:4(
@@ -2157,7 +2157,7 @@ MypyFile:1(
       NameExpr(a))
     Target(
       NameExpr(b))
-    Block:1(
+    Block:2(
       PassStmt:2()))
   WithStmt:3(
     Expr(
@@ -2168,7 +2168,7 @@ MypyFile:1(
       CallExpr:3(
         NameExpr(y)
         Args()))
-    Block:3(
+    Block:4(
       PassStmt:4())))
 
 [case testOperatorAssignment]
@@ -2262,10 +2262,10 @@ finally:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
     NameExpr(x)
-    Block:3(
+    Block:4(
       ExpressionStmt:4(
         NameExpr(x)))
     Finally(
@@ -2640,7 +2640,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       OverloadedFuncDef:2(
         Decorator:2(
           Var(g)
@@ -2668,14 +2668,14 @@ MypyFile:1(
   FuncDef:1(
     f
     def () -> A?
-    Block:1(
+    Block:2(
       PassStmt:2()))
   FuncDef:3(
     g
     Args(
       Var(x))
     def (x: A?) -> B?
-    Block:3(
+    Block:4(
       PassStmt:4())))
 
 [case testCommentMethodAnnotation]
@@ -2693,7 +2693,7 @@ MypyFile:1(
       Args(
         Var(self))
       def (self: Any) -> A?
-      Block:2(
+      Block:3(
         PassStmt:3()))
     FuncDef:4(
       g
@@ -2701,7 +2701,7 @@ MypyFile:1(
         Var(xself)
         Var(x))
       def (xself: Any, x: A?) -> B?
-      Block:4(
+      Block:5(
         PassStmt:5()))))
 
 [case testCommentMethodAnnotationAndNestedFunction]
@@ -2718,13 +2718,13 @@ MypyFile:1(
       Args(
         Var(self))
       def (self: Any) -> A?
-      Block:2(
+      Block:3(
         FuncDef:3(
           g
           Args(
             Var(x))
           def (x: A?) -> B?
-          Block:3(
+          Block:4(
             PassStmt:4()))))))
 
 [case testCommentFunctionAnnotationOnSeparateLine]
@@ -2738,7 +2738,7 @@ MypyFile:1(
     Args(
       Var(x))
     def (x: X?) -> Y?
-    Block:1(
+    Block:3(
       PassStmt:3())))
 
 [case testCommentFunctionAnnotationOnSeparateLine2]
@@ -2754,7 +2754,7 @@ MypyFile:1(
     Args(
       Var(x))
     def (x: X?) -> Y?
-    Block:1(
+    Block:5(
       PassStmt:5())))
 
 [case testCommentFunctionAnnotationAndVarArg]
@@ -2769,7 +2769,7 @@ MypyFile:1(
     def (x: X?, *y: Y?) -> Z?
     VarArg(
       Var(y))
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testCommentFunctionAnnotationAndAllVarArgs]
@@ -2786,7 +2786,7 @@ MypyFile:1(
       Var(y))
     DictVarArg(
       Var(z))
-    Block:1(
+    Block:2(
       PassStmt:2())))
 
 [case testClassDecorator]
@@ -2824,11 +2824,11 @@ def y():
 MypyFile:1(
   FuncDef:1(
     x
-    Block:1(
+    Block:2(
       PassStmt:2()))
   FuncDef:4(
     y
-    Block:4(
+    Block:5(
       PassStmt:5())))
 
 [case testEmptySuperClass]
@@ -2905,7 +2905,7 @@ MypyFile:1(
     StarExpr:1(
       NameExpr(a))
     NameExpr(b)
-    Block:1(
+    Block:2(
       PassStmt:2()))
   ForStmt:4(
     TupleExpr:4(
@@ -2913,7 +2913,7 @@ MypyFile:1(
       StarExpr:4(
         NameExpr(b)))
     NameExpr(c)
-    Block:4(
+    Block:5(
       PassStmt:5()))
   ForStmt:7(
     TupleExpr:7(
@@ -2921,7 +2921,7 @@ MypyFile:1(
         NameExpr(a))
       NameExpr(b))
     NameExpr(c)
-    Block:7(
+    Block:8(
       PassStmt:8())))
 
 [case testStarExprInGeneratorExpr]
@@ -3030,7 +3030,7 @@ while 2:
 MypyFile:1(
   WhileStmt:1(
     IntExpr(2)
-    Block:1(
+    Block:2(
       IfStmt:2(
         If(
           IntExpr(1))
@@ -3073,7 +3073,7 @@ while 2:
 MypyFile:1(
   WhileStmt:1(
     IntExpr(2)
-    Block:1(
+    Block:2(
       IfStmt:2(
         If(
           IntExpr(1))
@@ -3298,7 +3298,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(x)
         YieldExpr:2(
@@ -3339,7 +3339,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         YieldExpr:2()))))
 
@@ -3494,7 +3494,7 @@ MypyFile:1(
     f
     Args(
       Var(self))
-    Block:2()))
+    Block:3()))
 
 [case testStripMethodBodiesIfIgnoringErrors]
 # mypy: ignore-errors=True
@@ -3516,7 +3516,7 @@ MypyFile:1(
       f
       Args(
         Var(self))
-      Block:3())))
+      Block:4())))
 
 [case testDoNotStripModuleTopLevelOrClassBody]
 # mypy: ignore-errors=True
@@ -3550,7 +3550,7 @@ MypyFile:1(
       m1
       Args(
         Var(self))
-      Block:3(
+      Block:4(
         AssignmentStmt:4(
           MemberExpr:4(
             NameExpr(self)
@@ -3560,7 +3560,7 @@ MypyFile:1(
       m2
       Args(
         Var(self))
-      Block:5(
+      Block:6(
         AssignmentStmt:6(
           TupleExpr:6(
             NameExpr(a)
@@ -3591,11 +3591,11 @@ MypyFile:1(
       m1
       Args(
         Var(self))
-      Block:3(
+      Block:4(
         ForStmt:4(
           NameExpr(x)
           NameExpr(y)
-          Block:4(
+          Block:5(
             AssignmentStmt:5(
               MemberExpr:5(
                 NameExpr(self)
@@ -3605,11 +3605,11 @@ MypyFile:1(
       m2
       Args(
         Var(self))
-      Block:6(
+      Block:7(
         WithStmt:7(
           Expr(
             NameExpr(x))
-          Block:7(
+          Block:8(
             AssignmentStmt:8(
               MemberExpr:8(
                 NameExpr(self)
@@ -3619,7 +3619,7 @@ MypyFile:1(
       m3
       Args(
         Var(self))
-      Block:9(
+      Block:10(
         IfStmt:10(
           If(
             NameExpr(x))
@@ -3651,7 +3651,7 @@ MypyFile:1(
       m1
       Args(
         Var(self))
-      Block:3(
+      Block:4(
         WithStmt:4(
           Expr(
             NameExpr(y))
@@ -3659,19 +3659,19 @@ MypyFile:1(
             MemberExpr:4(
               NameExpr(self)
               x))
-          Block:4(
+          Block:5(
             PassStmt:5()))))
     FuncDef:6(
       m2
       Args(
         Var(self))
-      Block:6(
+      Block:7(
         ForStmt:7(
           MemberExpr:7(
             NameExpr(self)
             y)
           NameExpr(x)
-          Block:7(
+          Block:8(
             PassStmt:8()))))))
 
 [case testStripDecoratedFunctionOrMethod]
@@ -3695,7 +3695,7 @@ MypyFile:1(
     NameExpr(deco)
     FuncDef:3(
       f
-      Block:3()))
+      Block:4()))
   ClassDef:6(
     C
     Decorator:7(
@@ -3705,7 +3705,7 @@ MypyFile:1(
         m1
         Args(
           Var(self))
-        Block:8()))
+        Block:9()))
     Decorator:11(
       Var(m2)
       NameExpr(deco)
@@ -3713,7 +3713,7 @@ MypyFile:1(
         m2
         Args(
           Var(self))
-        Block:12(
+        Block:13(
           AssignmentStmt:13(
             MemberExpr:13(
               NameExpr(self)
@@ -3770,7 +3770,7 @@ MypyFile:1(
         Args(
           Var(self)
           Var(x))
-        Block:7()))
+        Block:8()))
     OverloadedFuncDef:10(
       Decorator:10(
         Var(m2)
@@ -3801,7 +3801,7 @@ MypyFile:1(
         Args(
           Var(self)
           Var(x))
-        Block:14(
+        Block:15(
           AssignmentStmt:15(
             MemberExpr:15(
               NameExpr(self)
@@ -3826,7 +3826,7 @@ MypyFile:1(
         m1
         Args(
           Var(self))
-        Block:4(
+        Block:5(
           AssignmentStmt:5(
             MemberExpr:5(
               NameExpr(self)
@@ -3836,4 +3836,4 @@ MypyFile:1(
         m2
         Args(
           Var(self))
-        Block:6()))))
+        Block:7()))))
diff --git a/test-data/unit/semanal-basic.test b/test-data/unit/semanal-basic.test
index 20443517e03e..169769f06a00 100644
--- a/test-data/unit/semanal-basic.test
+++ b/test-data/unit/semanal-basic.test
@@ -82,7 +82,7 @@ MypyFile:1(
     Args(
       Var(x)
       Var(y))
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         TupleExpr:2(
           NameExpr(x [l])
@@ -96,7 +96,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(x* [l])
         IntExpr(1))
@@ -113,7 +113,7 @@ def g(): pass
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         NameExpr(x [__main__.x]))
       ExpressionStmt:3(
@@ -149,7 +149,7 @@ MypyFile:1(
     f
     Args(
       Var(y))
-    Block:3(
+    Block:4(
       AssignmentStmt:4(
         NameExpr(y [l])
         IntExpr(1))
@@ -174,7 +174,7 @@ MypyFile:1(
     builtins.int)
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       AssignmentStmt:3(
         NameExpr(x* [l])
         IntExpr(2))
@@ -197,7 +197,7 @@ MypyFile:1(
       default(
         Var(y)
         NameExpr(object [builtins.object])))
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         TupleExpr:2(
           NameExpr(x [l])
@@ -214,7 +214,7 @@ MypyFile:1(
       Var(x))
     VarArg(
       Var(y))
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         TupleExpr:2(
           NameExpr(x [l])
@@ -234,7 +234,7 @@ MypyFile:1(
     NameExpr(None [builtins.None]))
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       GlobalDecl:3(
         x)
       AssignmentStmt:4(
@@ -262,7 +262,7 @@ MypyFile:1(
       NameExpr(None [builtins.None])))
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       GlobalDecl:3(
         x
         y)
@@ -283,12 +283,12 @@ MypyFile:1(
     NameExpr(None [builtins.None]))
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       GlobalDecl:3(
         x)))
   FuncDef:4(
     g
-    Block:4(
+    Block:5(
       AssignmentStmt:5(
         NameExpr(x* [l])
         NameExpr(None [builtins.None])))))
@@ -306,12 +306,12 @@ MypyFile:1(
     NameExpr(None [builtins.None]))
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       GlobalDecl:3(
         x)))
   FuncDef:4(
     g
-    Block:4(
+    Block:5(
       AssignmentStmt:5(
         NameExpr(x* [l])
         NameExpr(None [builtins.None])))))
@@ -333,7 +333,7 @@ MypyFile:1(
       f
       Args(
         Var(self))
-      Block:3(
+      Block:4(
         GlobalDecl:4(
           x)
         AssignmentStmt:5(
@@ -374,13 +374,13 @@ def g():
 MypyFile:1(
   FuncDef:1(
     g
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(x* [l])
         NameExpr(None [builtins.None]))
       FuncDef:3(
         f
-        Block:3(
+        Block:4(
           NonlocalDecl:4(
             x)
           AssignmentStmt:5(
@@ -400,7 +400,7 @@ MypyFile:1(
   FuncDef:1(
     f
     def ()
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(a* [l])
         IntExpr(0))
@@ -422,7 +422,7 @@ def g():
 MypyFile:1(
   FuncDef:1(
     g
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         TupleExpr:2(
           NameExpr(x* [l])
@@ -434,7 +434,7 @@ MypyFile:1(
         f
         Args(
           Var(z))
-        Block:3(
+        Block:4(
           NonlocalDecl:4(
             x
             y)
@@ -453,12 +453,12 @@ MypyFile:1(
     f
     Args(
       Var(x))
-    Block:1(
+    Block:2(
       FuncDef:2(
         g
         Args(
           Var(y))
-        Block:2(
+        Block:3(
           AssignmentStmt:3(
             NameExpr(z* [l])
             OpExpr:3(
@@ -478,10 +478,10 @@ MypyFile:1(
     f
     Args(
       Var(x))
-    Block:1(
+    Block:2(
       FuncDef:2(
         g
-        Block:2(
+        Block:3(
           AssignmentStmt:3(
             NameExpr(x* [l])
             IntExpr(1)))))))
diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test
index 86f8b8656fb6..951791e23490 100644
--- a/test-data/unit/semanal-classes.test
+++ b/test-data/unit/semanal-classes.test
@@ -27,7 +27,7 @@ MypyFile:1(
       Args(
         Var(self)
         Var(x))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           NameExpr(y* [l])
           NameExpr(x [l]))))
@@ -35,7 +35,7 @@ MypyFile:1(
       f
       Args(
         Var(self))
-      Block:4(
+      Block:5(
         AssignmentStmt:5(
           NameExpr(y* [l])
           NameExpr(self [l]))))))
@@ -53,7 +53,7 @@ MypyFile:1(
       __init__
       Args(
         Var(self))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           MemberExpr:3(
             NameExpr(self [l])
@@ -79,7 +79,7 @@ MypyFile:1(
       f
       Args(
         Var(self))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           MemberExpr:3(
             NameExpr(self [l])
@@ -89,7 +89,7 @@ MypyFile:1(
     __init__
     Args(
       Var(self))
-    Block:4(
+    Block:5(
       AssignmentStmt:5(
         MemberExpr:5(
           NameExpr(self [l])
@@ -113,7 +113,7 @@ MypyFile:1(
       Args(
         Var(x)
         Var(self))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           MemberExpr:3(
             NameExpr(self [l])
@@ -125,7 +125,7 @@ MypyFile:1(
       __init__
       Args(
         Var(x))
-      Block:5(
+      Block:6(
         AssignmentStmt:6(
           NameExpr(self* [l])
           NameExpr(x [l]))
@@ -147,7 +147,7 @@ MypyFile:1(
       __init__
       Args(
         Var(x))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           MemberExpr:3(
             NameExpr(x [l])
@@ -167,7 +167,7 @@ MypyFile:1(
       __init__
       Args(
         Var(self))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           MemberExpr:3(
             NameExpr(self [l])
@@ -309,7 +309,7 @@ MypyFile:1(
       ListExpr:2(
         IntExpr(1)
         IntExpr(2))
-      Block:2(
+      Block:3(
         AssignmentStmt:3(
           NameExpr(y* [m])
           NameExpr(x [__main__.A.x]))))))
@@ -322,7 +322,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ClassDef:2(
         A
         PassStmt:2())
@@ -369,7 +369,7 @@ MypyFile:1(
   FuncDef:1(
     f
     def ()
-    Block:1(
+    Block:2(
       ClassDef:2(
         A
         PassStmt:2())
@@ -390,7 +390,7 @@ MypyFile:1(
     f
     Args(
       Var(x))
-    Block:1(
+    Block:2(
       ClassDef:2(
         A
         AssignmentStmt:3(
@@ -400,7 +400,7 @@ MypyFile:1(
           g
           Args(
             Var(self))
-          Block:4(
+          Block:5(
             AssignmentStmt:5(
               NameExpr(z* [l])
               NameExpr(x [l]))))))))
diff --git a/test-data/unit/semanal-expressions.test b/test-data/unit/semanal-expressions.test
index fa07e533a842..4c9baf6b1b75 100644
--- a/test-data/unit/semanal-expressions.test
+++ b/test-data/unit/semanal-expressions.test
@@ -212,7 +212,7 @@ MypyFile:1(
     Args(
       Var(a))
     def (a: Any)
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         ListComprehension:2(
           GeneratorExpr:2(
diff --git a/test-data/unit/semanal-lambda.test b/test-data/unit/semanal-lambda.test
index 1cde1a794dc2..cc2307b97217 100644
--- a/test-data/unit/semanal-lambda.test
+++ b/test-data/unit/semanal-lambda.test
@@ -5,7 +5,7 @@ def g():
 MypyFile:1(
   FuncDef:1(
     g
-    Block:1(
+    Block:2(
       ReturnStmt:2(
         LambdaExpr:2(
           Args(
@@ -52,7 +52,7 @@ def g():
 MypyFile:1(
   FuncDef:1(
     g
-    Block:1(
+    Block:2(
       ReturnStmt:2(
         LambdaExpr:2(
           Block:2(
diff --git a/test-data/unit/semanal-modules.test b/test-data/unit/semanal-modules.test
index 116747ae5cb9..d52dd953aea2 100644
--- a/test-data/unit/semanal-modules.test
+++ b/test-data/unit/semanal-modules.test
@@ -552,7 +552,7 @@ MypyFile:1(
   FuncDef:1(
     f
     def ()
-    Block:1(
+    Block:2(
       Import:2(_x)
       ExpressionStmt:3(
         MemberExpr:3(
@@ -603,7 +603,7 @@ MypyFile:1(
   FuncDef:1(
     f
     def ()
-    Block:1(
+    Block:2(
       Import:2(x)
       Import:3(x)
       ExpressionStmt:4(
@@ -917,7 +917,7 @@ MypyFile:1(
   FuncDef:3(
     f
     def ()
-    Block:3(
+    Block:4(
       ImportFrom:4(x, [a])
       ImportFrom:5(x, [a])))
   Import:6(x)
diff --git a/test-data/unit/semanal-statements.test b/test-data/unit/semanal-statements.test
index 013452068cf1..f602c236c949 100644
--- a/test-data/unit/semanal-statements.test
+++ b/test-data/unit/semanal-statements.test
@@ -76,7 +76,7 @@ MypyFile:1(
     IntExpr(1))
   WhileStmt:2(
     NameExpr(x [__main__.x])
-    Block:2(
+    Block:3(
       ExpressionStmt:3(
         NameExpr(y [__main__.y])))))
 
@@ -88,7 +88,7 @@ MypyFile:1(
   ForStmt:1(
     NameExpr(x* [__main__.x])
     NameExpr(object [builtins.object])
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         NameExpr(x [__main__.x])))))
 
@@ -100,11 +100,11 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ForStmt:2(
         NameExpr(x* [l])
         NameExpr(f [__main__.f])
-        Block:2(
+        Block:3(
           ExpressionStmt:3(
             NameExpr(x [l])))))))
 
@@ -118,7 +118,7 @@ MypyFile:1(
       NameExpr(x* [__main__.x])
       NameExpr(y* [__main__.y]))
     ListExpr:1()
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         TupleExpr:2(
           NameExpr(x [__main__.x])
@@ -133,7 +133,7 @@ MypyFile:1(
   ForStmt:1(
     NameExpr(x* [__main__.x])
     ListExpr:1()
-    Block:1(
+    Block:2(
       PassStmt:2()))
   ExpressionStmt:3(
     NameExpr(x [__main__.x])))
@@ -147,11 +147,11 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       ForStmt:2(
         NameExpr(x* [l])
         ListExpr:2()
-        Block:2(
+        Block:3(
           PassStmt:3()))
       ExpressionStmt:4(
         NameExpr(x [l])))))
@@ -167,12 +167,12 @@ MypyFile:1(
   ForStmt:2(
     NameExpr(x'* [__main__.x'])
     NameExpr(None [builtins.None])
-    Block:2(
+    Block:3(
       PassStmt:3()))
   ForStmt:4(
     NameExpr(x* [__main__.x])
     NameExpr(None [builtins.None])
-    Block:4(
+    Block:5(
       PassStmt:5())))
 
 [case testReusingForLoopIndexVariable2]
@@ -186,16 +186,16 @@ def f():
 MypyFile:1(
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       ForStmt:3(
         NameExpr(x* [l])
         NameExpr(None [builtins.None])
-        Block:3(
+        Block:4(
           PassStmt:4()))
       ForStmt:5(
         NameExpr(x'* [l])
         NameExpr(None [builtins.None])
-        Block:5(
+        Block:6(
           PassStmt:6())))))
 
 [case testLoopWithElse]
@@ -212,14 +212,14 @@ MypyFile:1(
   ForStmt:1(
     NameExpr(x* [__main__.x])
     ListExpr:1()
-    Block:1(
+    Block:2(
       PassStmt:2())
     Else(
       ExpressionStmt:4(
         NameExpr(x [__main__.x]))))
   WhileStmt:5(
     IntExpr(1)
-    Block:5(
+    Block:6(
       PassStmt:6())
     Else(
       ExpressionStmt:8(
@@ -234,12 +234,12 @@ for x in []:
 MypyFile:1(
   WhileStmt:1(
     IntExpr(1)
-    Block:1(
+    Block:2(
       BreakStmt:2()))
   ForStmt:3(
     NameExpr(x* [__main__.x])
     ListExpr:3()
-    Block:3(
+    Block:4(
       BreakStmt:4())))
 
 [case testContinue]
@@ -251,12 +251,12 @@ for x in []:
 MypyFile:1(
   WhileStmt:1(
     IntExpr(1)
-    Block:1(
+    Block:2(
       ContinueStmt:2()))
   ForStmt:3(
     NameExpr(x* [__main__.x])
     ListExpr:3()
-    Block:3(
+    Block:4(
       ContinueStmt:4())))
 
 [case testIf]
@@ -426,7 +426,7 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(x* [l])
         IntExpr(1))
@@ -531,7 +531,7 @@ MypyFile:1(
     f
     Args(
       Var(x))
-    Block:1(
+    Block:2(
       DelStmt:2(
         NameExpr(x [l])))))
 
@@ -545,7 +545,7 @@ MypyFile:1(
     Args(
       Var(x)
       Var(y))
-    Block:1(
+    Block:2(
       DelStmt:2(
         TupleExpr:2(
           NameExpr(x [l])
@@ -579,19 +579,19 @@ MypyFile:1(
     c
     PassStmt:1())
   TryStmt:2(
-    Block:2(
+    Block:3(
       ExpressionStmt:3(
         NameExpr(c [__main__.c])))
     NameExpr(object [builtins.object])
-    Block:4(
+    Block:5(
       ExpressionStmt:5(
         NameExpr(c [__main__.c])))
     NameExpr(c [__main__.c])
     NameExpr(e* [__main__.e])
-    Block:6(
+    Block:7(
       ExpressionStmt:7(
         NameExpr(e [__main__.e])))
-    Block:8(
+    Block:9(
       ExpressionStmt:9(
         NameExpr(c [__main__.c])))
     Finally(
@@ -608,9 +608,9 @@ else:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
-    Block:3(
+    Block:4(
       PassStmt:4())
     Else(
       ExpressionStmt:6(
@@ -624,7 +624,7 @@ finally:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
     Finally(
       PassStmt:4())))
@@ -641,13 +641,13 @@ MypyFile:1(
     c
     PassStmt:1())
   TryStmt:2(
-    Block:2(
+    Block:3(
       PassStmt:3())
     TupleExpr:4(
       NameExpr(c [__main__.c])
       NameExpr(object [builtins.object]))
     NameExpr(e* [__main__.e])
-    Block:4(
+    Block:5(
       ExpressionStmt:5(
         NameExpr(e [__main__.e])))))
 
@@ -665,7 +665,7 @@ MypyFile:1(
   WithStmt:1(
     Expr(
       NameExpr(object [builtins.object]))
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         NameExpr(object [builtins.object])))))
 
@@ -679,7 +679,7 @@ MypyFile:1(
       NameExpr(object [builtins.object]))
     Target(
       NameExpr(x* [__main__.x]))
-    Block:1(
+    Block:2(
       ExpressionStmt:2(
         NameExpr(x [__main__.x])))))
 
@@ -691,13 +691,13 @@ def f():
 MypyFile:1(
   FuncDef:1(
     f
-    Block:1(
+    Block:2(
       WithStmt:2(
         Expr(
           NameExpr(f [__main__.f]))
         Target(
           NameExpr(x* [l]))
-        Block:2(
+        Block:3(
           ExpressionStmt:3(
             NameExpr(x [l])))))))
 
@@ -713,7 +713,7 @@ MypyFile:1(
       NameExpr(object [builtins.object]))
     Expr(
       NameExpr(object [builtins.object]))
-    Block:1(
+    Block:2(
       PassStmt:2()))
   WithStmt:3(
     Expr(
@@ -724,7 +724,7 @@ MypyFile:1(
       NameExpr(object [builtins.object]))
     Target(
       NameExpr(b* [__main__.b]))
-    Block:3(
+    Block:4(
       PassStmt:4())))
 
 [case testVariableInBlock]
@@ -736,7 +736,7 @@ while object:
 MypyFile:1(
   WhileStmt:1(
     NameExpr(object [builtins.object])
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(x* [__main__.x])
         NameExpr(None [builtins.None]))
@@ -757,11 +757,11 @@ except object as o:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
     NameExpr(object [builtins.object])
     NameExpr(o* [__main__.o])
-    Block:3(
+    Block:4(
       AssignmentStmt:4(
         NameExpr(x* [__main__.x])
         NameExpr(None [builtins.None]))
@@ -777,11 +777,11 @@ except object as o:
 [out]
 MypyFile:1(
   TryStmt:1(
-    Block:1(
+    Block:2(
       PassStmt:2())
     NameExpr(object [builtins.object])
     NameExpr(o* [__main__.o])
-    Block:3(
+    Block:4(
       AssignmentStmt:4(
         NameExpr(o [__main__.o])
         CallExpr:4(
@@ -806,15 +806,15 @@ MypyFile:1(
       builtins.BaseException)
     PassStmt:1())
   TryStmt:2(
-    Block:2(
+    Block:3(
       PassStmt:3())
     NameExpr(BaseException [builtins.BaseException])
     NameExpr(e* [__main__.e])
-    Block:4(
+    Block:5(
       PassStmt:5())
     NameExpr(Err [__main__.Err])
     NameExpr(f* [__main__.f])
-    Block:6(
+    Block:7(
       AssignmentStmt:7(
         NameExpr(f [__main__.f])
         CallExpr:7(
@@ -860,7 +860,7 @@ MypyFile:1(
     NameExpr(decorate [__main__.decorate])
     FuncDef:3(
       g
-      Block:3(
+      Block:4(
         ExpressionStmt:4(
           CallExpr:4(
             NameExpr(g [__main__.g])
@@ -877,13 +877,13 @@ MypyFile:1(
   FuncDef:1(
     f
     def ()
-    Block:1(
+    Block:2(
       TryStmt:2(
-        Block:2(
+        Block:3(
           PassStmt:3())
         NameExpr(object [builtins.object])
         NameExpr(o* [l])
-        Block:4(
+        Block:5(
           PassStmt:5())))))
 
 [case testReuseExceptionVariable]
@@ -899,17 +899,17 @@ MypyFile:1(
   FuncDef:1(
     f
     def ()
-    Block:1(
+    Block:2(
       TryStmt:2(
-        Block:2(
+        Block:3(
           PassStmt:3())
         NameExpr(object [builtins.object])
         NameExpr(o* [l])
-        Block:4(
+        Block:5(
           PassStmt:5())
         NameExpr(object [builtins.object])
         NameExpr(o [l])
-        Block:6(
+        Block:7(
           PassStmt:7())))))
 
 [case testWithMultiple]
@@ -924,11 +924,11 @@ MypyFile:1(
     f
     Args(
       Var(a))
-    Block:1(
+    Block:2(
       PassStmt:2()))
   FuncDef:3(
     main
-    Block:3(
+    Block:4(
       WithStmt:4(
         Expr(
           CallExpr:4(
@@ -944,7 +944,7 @@ MypyFile:1(
               NameExpr(a [l]))))
         Target(
           NameExpr(b* [l]))
-        Block:4(
+        Block:5(
           AssignmentStmt:5(
             NameExpr(x* [l])
             TupleExpr:5(
@@ -1030,7 +1030,7 @@ MypyFile:1(
     f
     Args(
       Var(a))
-    Block:2(
+    Block:3(
       ExpressionStmt:3(
         CallExpr:3(
           NameExpr(f [__main__.f])
@@ -1082,7 +1082,7 @@ MypyFile:1(
       IntExpr(0))
     Target(
       NameExpr(y'* [__main__.y']))
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(z* [__main__.z])
         NameExpr(y' [__main__.y']))))
@@ -1091,7 +1091,7 @@ MypyFile:1(
       IntExpr(1))
     Target(
       NameExpr(y* [__main__.y]))
-    Block:3(
+    Block:4(
       AssignmentStmt:4(
         NameExpr(y [__main__.y])
         IntExpr(1)))))
@@ -1109,7 +1109,7 @@ MypyFile:1(
       IntExpr(0))
     Target(
       NameExpr(y* [__main__.y]))
-    Block:1(
+    Block:2(
       AssignmentStmt:2(
         NameExpr(z* [__main__.z])
         NameExpr(y [__main__.y]))))
@@ -1121,7 +1121,7 @@ MypyFile:1(
       IntExpr(1))
     Target(
       NameExpr(y [__main__.y]))
-    Block:4(
+    Block:5(
       AssignmentStmt:5(
         NameExpr(y [__main__.y])
         IntExpr(1)))))
diff --git a/test-data/unit/semanal-types.test b/test-data/unit/semanal-types.test
index 05fc08d8a49e..71a5c6dd87b5 100644
--- a/test-data/unit/semanal-types.test
+++ b/test-data/unit/semanal-types.test
@@ -31,7 +31,7 @@ MypyFile:1(
     PassStmt:1())
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       AssignmentStmt:3(
         NameExpr(x [l])
         NameExpr(None [builtins.None])
@@ -69,7 +69,7 @@ MypyFile:1(
       __init__
       Args(
         Var(self))
-      Block:3(
+      Block:4(
         AssignmentStmt:4(
           MemberExpr:4(
             NameExpr(self [l])
@@ -120,7 +120,7 @@ MypyFile:1(
       Var(x)
       Var(y))
     def (x: Any, y: __main__.A)
-    Block:4(
+    Block:5(
       AssignmentStmt:5(
         NameExpr(z* [l])
         TupleExpr:5(
@@ -255,7 +255,7 @@ MypyFile:1(
     IntExpr(1))
   FuncDef:6(
     f
-    Block:6(
+    Block:7(
       AssignmentStmt:7(
         NameExpr(b [l])
         NameExpr(None [builtins.None])
@@ -417,7 +417,7 @@ MypyFile:1(
     Args(
       Var(x))
     def [t] (x: t`-1)
-    Block:3(
+    Block:4(
       AssignmentStmt:4(
         NameExpr(y [l])
         NameExpr(None [builtins.None])
@@ -591,7 +591,7 @@ MypyFile:1(
   FuncDef:3(
     f
     def ()
-    Block:3(
+    Block:4(
       FuncDef:4(
         g
         def [t] () -> t`-1
@@ -841,7 +841,7 @@ MypyFile:1(
   ImportFrom:1(typing, [overload])
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       OverloadedFuncDef:3(
         FuncDef:8(
           g
@@ -1113,7 +1113,7 @@ MypyFile:1(
   ImportFrom:1(typing, [TypeVar])
   FuncDef:2(
     f
-    Block:2(
+    Block:3(
       AssignmentStmt:3(
         NameExpr(T* [l])
         TypeVarExpr:3())
@@ -1196,7 +1196,7 @@ MypyFile:1(
         Var(self)
         Var(x))
       def (self: __main__.A[_m.T`1], x: _m.T`1) -> Any
-      Block:5(
+      Block:6(
         AssignmentStmt:6(
           NameExpr(b [l])
           NameExpr(None [builtins.None])
@@ -1217,7 +1217,7 @@ MypyFile:1(
     Args(
       Var(x))
     def [_m.T] (x: _m.T`-1)
-    Block:2(
+    Block:3(
       AssignmentStmt:3(
         NameExpr(a [l])
         NameExpr(None [builtins.None])
@@ -1235,7 +1235,7 @@ MypyFile:1(
     Args(
       Var(x))
     def (x: builtins.int) -> Any
-    Block:2(
+    Block:3(
       AssignmentStmt:3(
         NameExpr(x [l])
         IntExpr(1)))))
@@ -1256,7 +1256,7 @@ MypyFile:1(
         Var(self)
         Var(x))
       def (self: __main__.A, x: builtins.int) -> builtins.str
-      Block:3(
+      Block:4(
         AssignmentStmt:4(
           NameExpr(x [l])
           IntExpr(1))))))
@@ -1582,7 +1582,7 @@ MypyFile:1(
     Args(
       Var(x))
     def [Ts] (x: def (*Unpack[Ts`-1]))
-    Block:5(
+    Block:6(
       PassStmt:6())))
 
 [builtins fixtures/tuple.pyi]


Code review comments:
diff_hunk: @@ -347,3 +347,47 @@ silence the error:
 
     async def g() -> None:
         _ = asyncio.create_task(f())  # No error
+
+Check that ``# type: ignore`` comment is used [unused-ignore]
+-------------------------------------------------------------
+
+If you use :option:`--enable-error-code unused-ignore <mypy --enable-error-code>`,
+or :option:`--warn-unused-ignores <mypy --warn-unused-ignores>`
+mypy generates an error if you don't use a ``# type: ignore`` comment, i.e. if
+there is a comment, but there would be no error generated by mypy on this line
+anyway.
+
+Example:
+
+.. code-block:: python
+
+    # Use "mypy --warn-unused-ignores ..."
+
+    def add(a: int, b: int) -> int:
+        # Error: unused "type: ignore" comment
+        return a + b  # type: ignore
+
+Note that due to a specific nature of this comment, the only way to selectively
+silence it, is to include the error code explicitly. Also note that this error is
+not shown if the the ``# type: ignore`` is not used due to code being statically
+unreachable (e.g. due to platform or version check).
+
+Example:
+
+.. code-block:: python
+
+    # Use "mypy --warn-unused-ignores ..."
+
+    import sys
+
+    try:
+        # The "[unused-ignore]" is needed to get clean mypy run
path: docs/source/error_code_list2.rst
text: @user1:
```suggestion
        # The "[unused-ignore]" is needed to get a clean mypy run
```

diff_hunk: @@ -347,3 +347,47 @@ silence the error:
 
     async def g() -> None:
         _ = asyncio.create_task(f())  # No error
+
+Check that ``# type: ignore`` comment is used [unused-ignore]
+-------------------------------------------------------------
+
+If you use :option:`--enable-error-code unused-ignore <mypy --enable-error-code>`,
+or :option:`--warn-unused-ignores <mypy --warn-unused-ignores>`
+mypy generates an error if you don't use a ``# type: ignore`` comment, i.e. if
+there is a comment, but there would be no error generated by mypy on this line
+anyway.
+
+Example:
+
+.. code-block:: python
+
+    # Use "mypy --warn-unused-ignores ..."
+
+    def add(a: int, b: int) -> int:
+        # Error: unused "type: ignore" comment
+        return a + b  # type: ignore
+
+Note that due to a specific nature of this comment, the only way to selectively
+silence it, is to include the error code explicitly. Also note that this error is
+not shown if the the ``# type: ignore`` is not used due to code being statically
path: docs/source/error_code_list2.rst
text: @user1:
```suggestion
not shown if the ``# type: ignore`` is not used due to code being statically
```