Skip to content

Commit 232697e

Browse files
authored
Avoid narrowing to unreachable at module level (#21144)
Helps with confusing symptoms in #21132 It is unfortunate that it makes the logic a little more ad hoc / might lead to minimal repros being a little more confusing. But I think this is still a better and more helpful state to be in than before #20660 (relevant PR from the narrowing rewrite). Hopefully we can add the ability to check unreachable code, which will fix this and other issues.
1 parent 554fb8c commit 232697e

File tree

4 files changed

+42
-44
lines changed

4 files changed

+42
-44
lines changed

mypy/checker.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6803,11 +6803,14 @@ def narrow_type_by_identity_equality(
68036803
# It is correct to always narrow here. It improves behaviour on tests and
68046804
# detects many inaccurate type annotations on primer.
68056805
# However, because mypy does not currently check unreachable code, it feels
6806-
# risky to narrow to unreachable without --warn-unreachable.
6806+
# risky to narrow to unreachable without --warn-unreachable or not
6807+
# at module level
68076808
# See also this specific primer comment, where I force primer to run with
68086809
# --warn-unreachable to see what code we would stop checking:
68096810
# https://github.com/python/mypy/pull/20660#issuecomment-3865794148
6810-
if self.options.warn_unreachable or not is_unreachable_map(if_map):
6811+
if (
6812+
self.options.warn_unreachable and len(self.scope.stack) != 1
6813+
) or not is_unreachable_map(if_map):
68116814
all_if_maps.append(if_map)
68126815

68136816
# Handle narrowing for operands with custom __eq__ methods specially

test-data/unit/check-isinstance.test

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,27 +2134,22 @@ else:
21342134
# flags: --warn-unreachable
21352135
from typing import List, Optional
21362136

2137-
x: List[str]
2138-
y: Optional[int]
2139-
2140-
if y in x:
2141-
reveal_type(y) # E: Statement is unreachable
2142-
else:
2143-
reveal_type(y) # N: Revealed type is "builtins.int | None"
2137+
def f(x: List[str], y: Optional[int]) -> None:
2138+
if y in x:
2139+
reveal_type(y) # E: Statement is unreachable
2140+
else:
2141+
reveal_type(y) # N: Revealed type is "builtins.int | None"
21442142
[builtins fixtures/list.pyi]
21452143

21462144
[case testNarrowTypeAfterInListNested]
21472145
# flags: --warn-unreachable
21482146
from typing import List, Optional, Any
21492147

2150-
x: Optional[int]
2151-
lst: Optional[List[int]]
2152-
nested_any: List[List[Any]]
2153-
2154-
if lst in nested_any:
2155-
reveal_type(lst) # N: Revealed type is "builtins.list[builtins.int]"
2156-
if x in nested_any:
2157-
reveal_type(x) # E: Statement is unreachable
2148+
def f(x: Optional[int], lst: Optional[List[int]], nested_any: List[List[Any]]) -> None:
2149+
if lst in nested_any:
2150+
reveal_type(lst) # N: Revealed type is "builtins.list[builtins.int]"
2151+
if x in nested_any:
2152+
reveal_type(x) # E: Statement is unreachable
21582153
[builtins fixtures/list.pyi]
21592154

21602155
[case testNarrowTypeAfterInTuple]

test-data/unit/check-narrowing.test

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2802,13 +2802,13 @@ while x is not None and b():
28022802
[case testAvoidFalseNonOverlappingEqualityCheckInLoop1]
28032803
# flags: --allow-redefinition-new --local-partial-types --strict-equality --warn-unreachable
28042804

2805-
x = 1
2806-
while True:
2807-
if x == str():
2808-
break
2809-
x = str()
2810-
if x == int(): # E: Non-overlapping equality check (left operand type: "str", right operand type: "int")
2811-
break # E: Statement is unreachable
2805+
def f(x: int) -> None:
2806+
while True:
2807+
if x == str():
2808+
break
2809+
x = str()
2810+
if x == int(): # E: Non-overlapping equality check (left operand type: "str", right operand type: "int")
2811+
break # E: Statement is unreachable
28122812
[builtins fixtures/primitives.pyi]
28132813

28142814
[case testAvoidFalseNonOverlappingEqualityCheckInLoop2]
@@ -2818,11 +2818,11 @@ class A: ...
28182818
class B: ...
28192819
class C: ...
28202820

2821-
x = A()
2822-
while True:
2823-
if x == C(): # E: Non-overlapping equality check (left operand type: "A | B", right operand type: "C")
2824-
break # E: Statement is unreachable
2825-
x = B()
2821+
def f(x: A) -> None:
2822+
while True:
2823+
if x == C(): # E: Non-overlapping equality check (left operand type: "A | B", right operand type: "C")
2824+
break # E: Statement is unreachable
2825+
x = B()
28262826
[builtins fixtures/primitives.pyi]
28272827

28282828
[case testAvoidFalseNonOverlappingEqualityCheckInLoop3]

test-data/unit/check-python310.test

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ b: int
8787
import b
8888

8989
class A: ...
90-
m: A
9190

92-
match m:
93-
case b.b:
94-
reveal_type(m) # E: Statement is unreachable
91+
def f(m: A) -> None:
92+
match m:
93+
case b.b:
94+
reveal_type(m) # E: Statement is unreachable
9595
[file b.py]
9696
class B: ...
9797
b: B
@@ -100,11 +100,10 @@ b: B
100100
# flags: --strict-equality --warn-unreachable
101101
import b
102102

103-
m: int
104-
105-
match m:
106-
case b.b:
107-
reveal_type(m) # E: Statement is unreachable
103+
def f(m: int) -> None:
104+
match m:
105+
case b.b:
106+
reveal_type(m) # E: Statement is unreachable
108107
[file b.py]
109108
b: str
110109
[builtins fixtures/primitives.pyi]
@@ -3151,13 +3150,14 @@ def nested_in_dict(d: dict[str, Any]) -> int:
31513150
# flags: --warn-unreachable
31523151
from typing import Literal
31533152

3154-
def x() -> tuple[Literal["test"]]: ...
3153+
def f() -> None:
3154+
def x() -> tuple[Literal["test"]]: ...
31553155

3156-
match x():
3157-
case (x,) if x == "test": # E: Incompatible types in capture pattern (pattern captures type "Literal['test']", variable has type "Callable[[], tuple[Literal['test']]]")
3158-
reveal_type(x) # E: Statement is unreachable
3159-
case foo:
3160-
foo
3156+
match x():
3157+
case (x,) if x == "test": # E: Incompatible types in capture pattern (pattern captures type "Literal['test']", variable has type "Callable[[], tuple[Literal['test']]]")
3158+
reveal_type(x) # E: Statement is unreachable
3159+
case foo:
3160+
foo
31613161

31623162
[builtins fixtures/dict.pyi]
31633163

0 commit comments

Comments
 (0)