Fix match statement narrowing for self-matching class patterns with literal arguments (#21918)
Fixes #21780
This was likely because join_types was being used to combine two
possible remaining types (e.g. tuple[int, int] and int), and it would
collapse those into object instead of keeping both, which is why mypy
couldn't narrow down to a bottom type (Never).
This PR replaces join_types with make_simplified_union, and adds a
regression test based on the repro in the issue.
Could be a better fix than this, I would welcome it.
diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py
index 53fa75f..9d02262 100644
--- a/mypy/checkpattern.py
+++ b/mypy/checkpattern.py
@@ -605,7 +605,7 @@
if not is_uninhabited(pattern_type.type):
return PatternType(
pattern_type.type,
- join_types(rest_type, pattern_type.rest_type),
+ make_simplified_union([rest_type, pattern_type.rest_type]),
pattern_type.captures,
)
captures = pattern_type.captures
diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test
index 2f5b2d7..01e490d 100644
--- a/test-data/unit/check-python310.test
+++ b/test-data/unit/check-python310.test
@@ -1905,6 +1905,32 @@
case b:
reveal_type(b) # N: Revealed type is "builtins.int"
+[case testMatchClassPatternLiteralNegativeNarrowing]
+# flags: --strict-equality --warn-unreachable
+# See: https://github.com/python/mypy/issues/21780
+
+from typing import reveal_type, NoReturn
+
+def assert_never(x: NoReturn) -> None: ...
+
+def f(x: int | tuple[int, int] | None) -> float | None:
+ match x:
+ case None:
+ reveal_type(x) # N: Revealed type is "None"
+ return None
+ case int(0):
+ reveal_type(x) # N: Revealed type is "Literal[0]"
+ return 0.0
+ case int(bits):
+ reveal_type(x) # N: Revealed type is "builtins.int"
+ return bits / 8.0
+ case int(bits), int(seconds):
+ reveal_type(x) # N: Revealed type is "tuple[builtins.int, builtins.int]"
+ return bits / 8.0 / seconds
+ case _:
+ assert_never(x) # E: Statement is unreachable
+[builtins fixtures/ops.pyi]
+
[case testMatchExhaustiveReturn]
# flags: --strict-equality --warn-unreachable
def foo(value) -> int: