From e666217f6487ec7e6490981f7d6fdd5c7e57d81b Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Sun, 1 Dec 2024 19:10:26 -0500 Subject: [PATCH] Preserve block unreachablility when checking function definitions with constrained TypeVars (#18217) Fixes #18210 When checking function definitions with constrained type variables (i.e. type variables with value restrictions), mypy expands the function definition for each possible type variable value. However, blocks in the expanded function definitions have their `is_unreachable` reset to `False`, which leads to spurious errors in blocks that were marked as unreachable during semantic analysis. This PR preserves the value of `is_unreachable` on blocks in the expanded function definitions. --- mypy/nodes.py | 4 ++-- mypy/treetransform.py | 2 +- test-data/unit/check-unreachable-code.test | 10 ++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 56c61ce26d63..9e26103e2f58 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1263,7 +1263,7 @@ class Block(Statement): __match_args__ = ("body", "is_unreachable") - def __init__(self, body: list[Statement]) -> None: + def __init__(self, body: list[Statement], *, is_unreachable: bool = False) -> None: super().__init__() self.body = body # True if we can determine that this block is not executed during semantic @@ -1271,7 +1271,7 @@ def __init__(self, body: list[Statement]) -> None: # something like "if PY3:" when using Python 2. However, some code is # only considered unreachable during type checking and this is not true # in those cases. - self.is_unreachable = False + self.is_unreachable = is_unreachable def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_block(self) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index bb34d8de2884..aafa4e95d530 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -276,7 +276,7 @@ def visit_nonlocal_decl(self, node: NonlocalDecl) -> NonlocalDecl: return NonlocalDecl(node.names.copy()) def visit_block(self, node: Block) -> Block: - return Block(self.statements(node.body)) + return Block(self.statements(node.body), is_unreachable=node.is_unreachable) def visit_decorator(self, node: Decorator) -> Decorator: # Note that a Decorator must be transformed to a Decorator. diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index cbad1bd5449e..e6818ab5c3c7 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -1000,6 +1000,16 @@ class Test4(Generic[T3]): [builtins fixtures/isinstancelist.pyi] +[case testUnreachableBlockStaysUnreachableWithTypeVarConstraints] +# flags: --always-false COMPILE_TIME_FALSE +from typing import TypeVar +COMPILE_TIME_FALSE = False +T = TypeVar("T", int, str) +def foo(x: T) -> T: + if COMPILE_TIME_FALSE: + return "bad" + return x + [case testUnreachableFlagContextManagersNoSuppress] # flags: --warn-unreachable from contextlib import contextmanager