Skip to content

Commit e8af2ac

Browse files
authored
More testing related improvements and updates (#20709)
Less trivial follow up to #20707 Several of the skipped match tests now pass or have better behaviour, so unskipping them The two new tests I added both do not have great behaviour, mostly predating my rewrite. I improve these in #20660 but that PR is a little tricky to land, so I'm separating it out. This way it is easier for reviewers to evaluate what the changes are.
1 parent 21eb6a1 commit e8af2ac

File tree

3 files changed

+102
-19
lines changed

3 files changed

+102
-19
lines changed

test-data/unit/check-narrowing.test

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3246,14 +3246,15 @@ class B: ...
32463246

32473247
T = TypeVar("T")
32483248

3249-
def forgets_about_subclasses1(self, obj: T) -> T:
3249+
def forgets_about_subclasses(self, obj: T) -> T:
32503250
if isinstance(obj, A):
32513251
return A() # E: Incompatible return value type (got "A", expected "T")
32523252
elif isinstance(obj, B):
32533253
return B() # E: Incompatible return value type (got "B", expected "T")
32543254
raise
32553255

32563256
def correct1(self, obj: T) -> T:
3257+
# TODO: mypy should not error in this case
32573258
if type(obj) == A:
32583259
return A() # E: Incompatible return value type (got "A", expected "T")
32593260
elif type(obj) == B:
@@ -3262,7 +3263,10 @@ def correct1(self, obj: T) -> T:
32623263

32633264
T_value = TypeVar("T_value", A, B)
32643265

3265-
def forgets_about_subclasses2(self, obj: T_value) -> T_value:
3266+
def forgets_about_multiple_inheritance(self, obj: T_value) -> T_value:
3267+
# Note that it is a little confusing that mypy only errors in the first branch here, but this
3268+
# is a branch that would be taken by a subclass of both A and B.
3269+
# See also https://github.com/python/mypy/issues/10302#issuecomment-3832182574
32663270
if isinstance(obj, A):
32673271
return A() # E: Incompatible return value type (got "A", expected "B")
32683272
elif isinstance(obj, B):
@@ -3479,6 +3483,71 @@ def bar(X: type[T]) -> T:
34793483
raise
34803484
[builtins fixtures/type.pyi]
34813485

3486+
[case testNarrowingConstrainedTypeVarType]
3487+
# flags: --strict-equality --warn-unreachable
3488+
from typing import TypeVar, Any, Type
3489+
3490+
TargetType = TypeVar("TargetType", int, float, str)
3491+
3492+
# TODO: this behaviour is incorrect, it will be fixed by improving reachability
3493+
def convert_type(target_type: Type[TargetType]) -> TargetType:
3494+
if target_type == str:
3495+
return str() # E: Incompatible return value type (got "str", expected "int") \
3496+
# E: Incompatible return value type (got "str", expected "float")
3497+
if target_type == int:
3498+
return int() # E: Incompatible return value type (got "int", expected "str")
3499+
if target_type == float:
3500+
return float() # E: Incompatible return value type (got "float", expected "int") \
3501+
# E: Incompatible return value type (got "float", expected "str")
3502+
raise
3503+
[builtins fixtures/primitives.pyi]
3504+
3505+
3506+
[case testNarrowingEqualityWithPromotions]
3507+
# flags: --strict-equality --warn-unreachable
3508+
from __future__ import annotations
3509+
from typing import Literal
3510+
3511+
# TODO: the behaviour on some of these test cases is incorrect
3512+
def f1(number: float, i: int):
3513+
if number == i:
3514+
reveal_type(number) # N: Revealed type is "builtins.float"
3515+
reveal_type(i) # N: Revealed type is "builtins.int"
3516+
3517+
def f2(number: float, five: Literal[5]):
3518+
if number == five:
3519+
reveal_type(number) # E: Statement is unreachable
3520+
reveal_type(five)
3521+
3522+
def f3(number: float | int, five: Literal[5]):
3523+
if number == five:
3524+
reveal_type(number) # N: Revealed type is "Literal[5]"
3525+
reveal_type(five) # N: Revealed type is "Literal[5]"
3526+
3527+
def f4(number: float | None, i: int):
3528+
if number == i:
3529+
reveal_type(number) # N: Revealed type is "builtins.float | None"
3530+
reveal_type(i) # N: Revealed type is "builtins.int"
3531+
3532+
def f5(number: float | int, i: int):
3533+
if number == i:
3534+
reveal_type(number) # N: Revealed type is "builtins.int"
3535+
reveal_type(i) # N: Revealed type is "builtins.int"
3536+
3537+
def f6(number: float | complex, i: int):
3538+
if number == i:
3539+
reveal_type(number) # N: Revealed type is "builtins.float | builtins.complex"
3540+
reveal_type(i) # N: Revealed type is "builtins.int"
3541+
3542+
class Custom:
3543+
def __eq__(self, other: object) -> bool: return True
3544+
3545+
def f7(number: float, x: Custom | int):
3546+
if number == x:
3547+
reveal_type(number) # N: Revealed type is "builtins.float"
3548+
reveal_type(x) # N: Revealed type is "__main__.Custom"
3549+
[builtins fixtures/primitives.pyi]
3550+
34823551
[case testNarrowingAnyNegativeIntersection-xfail]
34833552
# flags: --strict-equality --warn-unreachable
34843553
# https://github.com/python/mypy/issues/20597

test-data/unit/check-python310.test

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,17 @@ match m:
3636
case other:
3737
reveal_type(other) # N: Revealed type is "Any"
3838

39-
[case testMatchLiteralPatternAlreadyNarrower-skip]
40-
m: bool
39+
[case testMatchLiteralPatternBoolIntAlreadyNarrower]
40+
# flags: --strict-equality --warn-unreachable
4141

42-
match m:
43-
case 1:
44-
reveal_type(m) # This should probably be unreachable, but isn't detected as such.
42+
def foo(m: bool, x: int):
43+
match m:
44+
case 1:
45+
reveal_type(m) # N: Revealed type is "Literal[1]"
46+
47+
match m:
48+
case x:
49+
reveal_type(m) # N: Revealed type is "builtins.bool"
4550
[builtins fixtures/primitives.pyi]
4651

4752
[case testMatchLiteralPatternUnreachable]
@@ -402,7 +407,7 @@ match d.tag:
402407
reveal_type(d) # N: Revealed type is "__main__.B"
403408
reveal_type(d.num) # N: Revealed type is "builtins.int"
404409

405-
[case testMatchSequenceUnion-skip]
410+
[case testMatchSequenceUnion]
406411
from typing import List, Union
407412
m: Union[List[List[str]], str]
408413

@@ -1878,7 +1883,8 @@ match m:
18781883
assert_never(unreachable)
18791884
[builtins fixtures/enum.pyi]
18801885

1881-
[case testMatchLiteralPatternEnumCustomEquals-skip]
1886+
[case testMatchLiteralPatternEnumCustomEquals]
1887+
# flags: --strict-equality --warn-unreachable
18821888
from enum import Enum
18831889
class Medal(Enum):
18841890
gold = 1
@@ -1891,9 +1897,9 @@ m: Medal
18911897

18921898
match m:
18931899
case Medal.gold:
1894-
reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.gold]"
1895-
case _:
18961900
reveal_type(m) # N: Revealed type is "__main__.Medal"
1901+
case _:
1902+
reveal_type(m) # N: Revealed type is "Literal[__main__.Medal.silver] | Literal[__main__.Medal.bronze]"
18971903
[builtins fixtures/enum.pyi]
18981904

18991905
[case testMatchNarrowUsingPatternGuardSpecialCase]
@@ -2035,7 +2041,8 @@ match c:
20352041
assert_never(c) # E: Argument 1 to "assert_never" has incompatible type "Literal['red']"; expected "Never"
20362042
[typing fixtures/typing-typeddict.pyi]
20372043

2038-
[case testMatchAsPatternIntersection-skip]
2044+
[case testMatchAsPatternIntersection]
2045+
# flags: --strict-equality --warn-unreachable
20392046
class A: pass
20402047
class B: pass
20412048
class C: pass
@@ -2046,7 +2053,7 @@ def f(x: A) -> None:
20462053
reveal_type(y) # N: Revealed type is "__main__.<subclass of "__main__.A" and "__main__.B">"
20472054
case C() as y:
20482055
reveal_type(y) # N: Revealed type is "__main__.<subclass of "__main__.A" and "__main__.C">"
2049-
reveal_type(y) # N: Revealed type is "Union[__main__.<subclass of "__main__.A" and "__main__.B">, __main__.<subclass of "__main__.A" and "__main__.C">]"
2056+
reveal_type(y) # N: Revealed type is "__main__.<subclass of "__main__.A" and "__main__.B"> | __main__.<subclass of "__main__.A" and "__main__.C">"
20502057

20512058
[case testMatchWithBreakAndContinue]
20522059
def f(x: int | str | None) -> None:

test-data/unit/check-tuples.test

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1533,17 +1533,24 @@ reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int]"
15331533
[builtins fixtures/tuple.pyi]
15341534

15351535
[case testTupleOverlapDifferentTuples]
1536+
# flags: --warn-unreachable
15361537
from typing import Optional, Tuple
15371538
class A: pass
15381539
class B: pass
15391540

1540-
possibles: Tuple[int, Tuple[A]]
1541-
x: Optional[Tuple[B]]
1541+
def f1(possibles: Tuple[int, Tuple[A]], x: Optional[Tuple[B]]):
1542+
if x in possibles:
1543+
reveal_type(x) # N: Revealed type is "tuple[__main__.B]"
1544+
else:
1545+
reveal_type(x) # N: Revealed type is "tuple[__main__.B] | None"
1546+
1547+
class AA(A): pass
15421548

1543-
if x in possibles:
1544-
reveal_type(x) # N: Revealed type is "tuple[__main__.B]"
1545-
else:
1546-
reveal_type(x) # N: Revealed type is "tuple[__main__.B] | None"
1549+
def f2(possibles: Tuple[int, Tuple[A]], x: Optional[Tuple[AA]]):
1550+
if x in possibles:
1551+
reveal_type(x) # N: Revealed type is "tuple[__main__.AA]"
1552+
else:
1553+
reveal_type(x) # N: Revealed type is "tuple[__main__.AA] | None"
15471554

15481555
[builtins fixtures/tuple.pyi]
15491556

0 commit comments

Comments
 (0)