diff --git a/mypy/checker.py b/mypy/checker.py index b87d594acde67..0033e994144ef 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -6699,8 +6699,15 @@ def narrow_type_by_identity_equality( # However, for non-value targets, we cannot do this narrowing, # and so we ignore else_map # e.g. if (x: str | None) != (y: str), we cannot narrow x to None - # TODO: this reachability gate is incorrect and should be removed - if not is_unreachable_map(if_map): + + # It is correct to always narrow here. It improves behaviour on tests and + # detects many inaccurate type annotations on primer. + # However, because mypy does not currently check unreachable code, it feels + # risky to narrow to unreachable without --warn-unreachable. + # See also this specific primer comment, where I force primer to run with + # --warn-unreachable to see what code we would stop checking: + # https://github.com/python/mypy/pull/20660#issuecomment-3865794148 + if self.options.warn_unreachable or not is_unreachable_map(if_map): all_if_maps.append(if_map) # Handle narrowing for operands with custom __eq__ methods specially diff --git a/mypy/server/aststrip.py b/mypy/server/aststrip.py index cf706e97c3da7..828e51895f0f9 100644 --- a/mypy/server/aststrip.py +++ b/mypy/server/aststrip.py @@ -137,7 +137,9 @@ def visit_class_def(self, node: ClassDef) -> None: node.base_type_exprs.extend(node.removed_base_type_exprs) node.removed_base_type_exprs = [] node.defs.body = [ - s for s in node.defs.body if s not in to_delete # type: ignore[comparison-overlap] + s + for s in node.defs.body + if s not in to_delete # type: ignore[comparison-overlap, redundant-expr] ] with self.enter_class(node.info): super().visit_class_def(node) diff --git a/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi b/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi index b6358a0022f3b..7f7b9a89d8c53 100644 --- a/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi +++ b/mypy/typeshed/stubs/mypy-extensions/mypy_extensions.pyi @@ -111,6 +111,7 @@ class i64: def __ge__(self, x: i64) -> bool: ... def __gt__(self, x: i64) -> bool: ... def __index__(self) -> int: ... + def __eq__(self, x: object) -> bool: ... class i32: @overload @@ -146,6 +147,7 @@ class i32: def __ge__(self, x: i32) -> bool: ... def __gt__(self, x: i32) -> bool: ... def __index__(self) -> int: ... + def __eq__(self, x: object) -> bool: ... class i16: @overload @@ -181,6 +183,7 @@ class i16: def __ge__(self, x: i16) -> bool: ... def __gt__(self, x: i16) -> bool: ... def __index__(self) -> int: ... + def __eq__(self, x: object) -> bool: ... class u8: @overload @@ -216,3 +219,4 @@ class u8: def __ge__(self, x: u8) -> bool: ... def __gt__(self, x: u8) -> bool: ... def __index__(self) -> int: ... + def __eq__(self, x: object) -> bool: ... diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index 2161f94e93fa9..874056e8883a6 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -2138,7 +2138,7 @@ x: List[str] y: Optional[int] if y in x: - reveal_type(y) # N: Revealed type is "builtins.int | None" + reveal_type(y) # E: Statement is unreachable else: reveal_type(y) # N: Revealed type is "builtins.int | None" [builtins fixtures/list.pyi] diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 257e7c9d39e07..ea088270e0170 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -2722,7 +2722,7 @@ while True: break x = str() if x == int(): # E: Non-overlapping equality check (left operand type: "str", right operand type: "int") - break + break # E: Statement is unreachable [builtins fixtures/primitives.pyi] [case testAvoidFalseNonOverlappingEqualityCheckInLoop2] @@ -2735,7 +2735,7 @@ class C: ... x = A() while True: if x == C(): # E: Non-overlapping equality check (left operand type: "A | B", right operand type: "C") - break + break # E: Statement is unreachable x = B() [builtins fixtures/primitives.pyi] @@ -3127,7 +3127,7 @@ def narrow_tuple(x: Literal['c'], overlap: list[Literal['b', 'c']], no_overlap: reveal_type(x) # N: Revealed type is "Literal['c']" if x in no_overlap: - reveal_type(x) # N: Revealed type is "Literal['c']" + reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "Literal['c']" [builtins fixtures/tuple.pyi] @@ -3169,8 +3169,8 @@ def f2(x: Any) -> None: def bad_compare_has_key(has_key: bool, key: str, s: tuple[str, ...]) -> None: if has_key == key in s: # E: Non-overlapping equality check (left operand type: "bool", right operand type: "str") - reveal_type(has_key) # N: Revealed type is "builtins.bool" - reveal_type(key) # N: Revealed type is "builtins.str" + reveal_type(has_key) # E: Statement is unreachable + reveal_type(key) def bad_but_should_pass(has_key: bool, key: bool, s: tuple[bool, ...]) -> None: if has_key == key in s: @@ -3537,6 +3537,7 @@ def bar(y: Any): reveal_type(y) # N: Revealed type is "Any" [builtins fixtures/dict-full.pyi] + [case testNarrowTypeVarType] # flags: --strict-equality --warn-unreachable from typing import TypeVar @@ -3567,14 +3568,14 @@ TargetType = TypeVar("TargetType", int, float, str) # TODO: this behaviour is incorrect, it will be fixed by improving reachability def convert_type(target_type: Type[TargetType]) -> TargetType: if target_type == str: - return str() # E: Incompatible return value type (got "str", expected "int") \ - # E: Incompatible return value type (got "str", expected "float") + return str() if target_type == int: - return int() # E: Incompatible return value type (got "int", expected "str") + return int() if target_type == float: - return float() # E: Incompatible return value type (got "float", expected "int") \ - # E: Incompatible return value type (got "float", expected "str") + return float() # E: Incompatible return value type (got "float", expected "int") raise + + [builtins fixtures/primitives.pyi] diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 6db60275944f3..70e22eb6e679b 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -493,11 +493,11 @@ from typing import Optional def main(x: Optional[str]): if x == 0: - reveal_type(x) # N: Revealed type is "builtins.str | None" + reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "builtins.str | None" if x is 0: - reveal_type(x) # N: Revealed type is "builtins.str | None" + reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "builtins.str | None" [builtins fixtures/ops.pyi] diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 76f60db874127..40491fefc50c2 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -91,7 +91,7 @@ m: A match m: case b.b: - reveal_type(m) # N: Revealed type is "__main__.A" + reveal_type(m) # E: Statement is unreachable [file b.py] class B: ... b: B @@ -104,7 +104,7 @@ m: int match m: case b.b: - reveal_type(m) # N: Revealed type is "builtins.int" + reveal_type(m) # E: Statement is unreachable [file b.py] b: str [builtins fixtures/primitives.pyi] @@ -2849,7 +2849,7 @@ def x() -> tuple[Literal["test"]]: ... match x(): case (x,) if x == "test": # E: Incompatible types in capture pattern (pattern captures type "Literal['test']", variable has type "Callable[[], tuple[Literal['test']]]") - reveal_type(x) # N: Revealed type is "def () -> tuple[Literal['test']]" + reveal_type(x) # E: Statement is unreachable case foo: foo diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 5dcc34027c700..9653d9d037ce6 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1540,7 +1540,7 @@ class B: pass def f1(possibles: Tuple[int, Tuple[A]], x: Optional[Tuple[B]]): if x in possibles: - reveal_type(x) # N: Revealed type is "tuple[__main__.B]" + reveal_type(x) # E: Statement is unreachable else: reveal_type(x) # N: Revealed type is "tuple[__main__.B] | None" diff --git a/test-data/unit/check-unreachable-code.test b/test-data/unit/check-unreachable-code.test index 22c4c80916cbe..f02eb2bd55c47 100644 --- a/test-data/unit/check-unreachable-code.test +++ b/test-data/unit/check-unreachable-code.test @@ -424,24 +424,22 @@ x = 1 [case testCustomSysVersionInfo] # flags: --python-version 3.11 import sys -if sys.version_info == (3, 11): +if sys.version_info >= (3, 11): x = "foo" else: x = 3 reveal_type(x) # N: Revealed type is "builtins.str" [builtins fixtures/ops.pyi] -[out] [case testCustomSysVersionInfo2] # flags: --python-version 3.11 import sys -if sys.version_info == (3, 6): +if sys.version_info >= (3, 12): x = "foo" else: x = 3 reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/ops.pyi] -[out] [case testCustomSysPlatform] # flags: --platform linux @@ -894,42 +892,53 @@ def baz(x: int) -> int: import sys from typing import TYPE_CHECKING -x: int -if TYPE_CHECKING: - reveal_type(x) # N: Revealed type is "builtins.int" -else: - reveal_type(x) +def f1(x: int) -> None: + if TYPE_CHECKING: + reveal_type(x) # N: Revealed type is "builtins.int" + else: + reveal_type(x) -if not TYPE_CHECKING: - reveal_type(x) -else: - reveal_type(x) # N: Revealed type is "builtins.int" +def f2(x: int) -> None: + if not TYPE_CHECKING: + reveal_type(x) + else: + reveal_type(x) # N: Revealed type is "builtins.int" -if sys.platform == 'darwin': - reveal_type(x) -else: - reveal_type(x) # N: Revealed type is "builtins.int" +def f3(x: int) -> None: + if sys.platform == 'darwin': + reveal_type(x) + else: + reveal_type(x) # N: Revealed type is "builtins.int" -if sys.platform == 'win32': - reveal_type(x) # N: Revealed type is "builtins.int" -else: - reveal_type(x) +def f4(x: int) -> None: + if sys.platform == 'win32': + reveal_type(x) # N: Revealed type is "builtins.int" + else: + reveal_type(x) -if sys.version_info == (2, 7): - reveal_type(x) -else: - reveal_type(x) # N: Revealed type is "builtins.int" +def f5(x: int) -> None: + if sys.version_info == (2, 7): + reveal_type(x) + else: + reveal_type(x) # N: Revealed type is "builtins.int" -if sys.version_info == (3, 9): - reveal_type(x) # N: Revealed type is "builtins.int" -else: - reveal_type(x) +def f6(x: int) -> None: + if sys.version_info >= (3, 9): + reveal_type(x) # N: Revealed type is "builtins.int" + else: + reveal_type(x) -FOOBAR = "" -if FOOBAR: - reveal_type(x) -else: - reveal_type(x) # N: Revealed type is "builtins.int" + if sys.version_info >= (3, 10): + reveal_type(x) + else: + reveal_type(x) # N: Revealed type is "builtins.int" + +def f7(x: int) -> None: + FOOBAR = "" + if FOOBAR: + reveal_type(x) + else: + reveal_type(x) # N: Revealed type is "builtins.int" [builtins fixtures/ops.pyi] [typing fixtures/typing-medium.pyi] diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index 1a2e37e665461..0113856a52427 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -368,7 +368,7 @@ python_version = 3.14 # cmd: mypy main.py [file main.py] import sys -if sys.version_info == (3, 9): # Update here when bumping the min Python version! +if sys.version_info < (3, 10): # Update here when bumping the min Python version! reveal_type("good") [file mypy.ini] \[mypy]