Skip to content

Commit 1c01858

Browse files
Fix negative narrowing of tuples in match statement (#17817)
Fixes #17328 ### Before Lines marked with `!!!` denote incorrect behavior. ([Playground link](https://mypy-play.net/?mypy=1.11.2&python=3.12&flags=strict%2Cwarn-unreachable&gist=7a7081c5fbc2fac9987f24e02421f24f)) ```python from typing import Literal m4: tuple[Literal[1], int] match m4: case (1, 5): reveal_type(m4) # N: Revealed type is "tuple[Literal[1], Literal[5]]" case (1, 6): reveal_type(m4) # !!! E: Statement is unreachable [unreachable] case _: reveal_type(m4) # !!! N: Revealed type is "tuple[Never, builtins.int]" m5: tuple[Literal[1, 2], Literal["a", "b"]] match m5: case (1, str()): reveal_type(m5) # N: Revealed type is "tuple[Literal[1], Union[Literal['a'], Literal['b']]]" case _: reveal_type(m5) # !!! N: Revealed type is "tuple[Literal[2], Never]" match m5: case (1, "a"): reveal_type(m5) # N: Revealed type is "tuple[Literal[1], Literal['a']]" case _: reveal_type(m5) # !!! N: Revealed type is "tuple[Literal[2], Literal['b']]" ``` ### After ```python from typing import Literal m4: tuple[Literal[1], int] match m4: case (1, 5): reveal_type(m4) # N: Revealed type is "tuple[Literal[1], Literal[5]]" case (1, 6): reveal_type(m4) # N: Revealed type is "tuple[Literal[1], Literal[6]]" case _: reveal_type(m4) # N: Revealed type is "tuple[Literal[1], builtins.int]" m5: tuple[Literal[1, 2], Literal["a", "b"]] match m5: case (1, str()): reveal_type(m5) # N: Revealed type is "tuple[Literal[1], Union[Literal['a'], Literal['b']]]" case _: reveal_type(m5) # N: Revealed type is "tuple[Literal[2], Union[Literal['a'], Literal['b']]]" match m5: case (1, "a"): reveal_type(m5) # N: Revealed type is "tuple[Literal[1], Literal['a']]" case _: reveal_type(m5) # N: Revealed type is "tuple[Union[Literal[1], Literal[2]], Union[Literal['a'], Literal['b']]]" ```
1 parent 7237d55 commit 1c01858

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

mypy/checkpattern.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
307307
for inner_type, new_inner_type in zip(inner_types, new_inner_types):
308308
(narrowed_inner_type, inner_rest_type) = (
309309
self.chk.conditional_types_with_intersection(
310-
new_inner_type, [get_type_range(inner_type)], o, default=new_inner_type
310+
inner_type, [get_type_range(new_inner_type)], o, default=inner_type
311311
)
312312
)
313313
narrowed_inner_types.append(narrowed_inner_type)
@@ -320,6 +320,16 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
320320
if all(is_uninhabited(typ) for typ in inner_rest_types):
321321
# All subpatterns always match, so we can apply negative narrowing
322322
rest_type = TupleType(rest_inner_types, current_type.partial_fallback)
323+
elif sum(not is_uninhabited(typ) for typ in inner_rest_types) == 1:
324+
# Exactly one subpattern may conditionally match, the rest always match.
325+
# We can apply negative narrowing to this one position.
326+
rest_type = TupleType(
327+
[
328+
curr if is_uninhabited(rest) else rest
329+
for curr, rest in zip(inner_types, inner_rest_types)
330+
],
331+
current_type.partial_fallback,
332+
)
323333
elif isinstance(current_type, TupleType):
324334
# For variadic tuples it is too tricky to match individual items like for fixed
325335
# tuples, so we instead try to narrow the entire type.

test-data/unit/check-python310.test

+26
Original file line numberDiff line numberDiff line change
@@ -1424,6 +1424,7 @@ def f(value: Literal[1] | Literal[2]) -> int:
14241424

14251425
[case testMatchSequencePatternNegativeNarrowing]
14261426
from typing import Union, Sequence, Tuple
1427+
from typing_extensions import Literal
14271428

14281429
m1: Sequence[int | str]
14291430

@@ -1448,6 +1449,31 @@ match m3:
14481449
reveal_type(m3) # N: Revealed type is "Tuple[Literal[1]]"
14491450
case r2:
14501451
reveal_type(m3) # N: Revealed type is "Tuple[Union[builtins.int, builtins.str]]"
1452+
1453+
m4: Tuple[Literal[1], int]
1454+
1455+
match m4:
1456+
case (1, 5):
1457+
reveal_type(m4) # N: Revealed type is "Tuple[Literal[1], Literal[5]]"
1458+
case (1, 6):
1459+
reveal_type(m4) # N: Revealed type is "Tuple[Literal[1], Literal[6]]"
1460+
case _:
1461+
reveal_type(m4) # N: Revealed type is "Tuple[Literal[1], builtins.int]"
1462+
1463+
m5: Tuple[Literal[1, 2], Literal["a", "b"]]
1464+
1465+
match m5:
1466+
case (1, str()):
1467+
reveal_type(m5) # N: Revealed type is "Tuple[Literal[1], Union[Literal['a'], Literal['b']]]"
1468+
case _:
1469+
reveal_type(m5) # N: Revealed type is "Tuple[Literal[2], Union[Literal['a'], Literal['b']]]"
1470+
1471+
match m5:
1472+
case (1, "a"):
1473+
reveal_type(m5) # N: Revealed type is "Tuple[Literal[1], Literal['a']]"
1474+
case _:
1475+
reveal_type(m5) # N: Revealed type is "Tuple[Union[Literal[1], Literal[2]], Union[Literal['a'], Literal['b']]]"
1476+
14511477
[builtins fixtures/tuple.pyi]
14521478

14531479
[case testMatchEnumSingleChoice]

0 commit comments

Comments
 (0)