Skip to content

Fix negative narrowing of tuples in match statement #17817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion mypy/checkpattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
for inner_type, new_inner_type in zip(inner_types, new_inner_types):
(narrowed_inner_type, inner_rest_type) = (
self.chk.conditional_types_with_intersection(
new_inner_type, [get_type_range(inner_type)], o, default=new_inner_type
inner_type, [get_type_range(new_inner_type)], o, default=inner_type
)
)
narrowed_inner_types.append(narrowed_inner_type)
Expand All @@ -320,6 +320,16 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
if all(is_uninhabited(typ) for typ in inner_rest_types):
# All subpatterns always match, so we can apply negative narrowing
rest_type = TupleType(rest_inner_types, current_type.partial_fallback)
elif sum(not is_uninhabited(typ) for typ in inner_rest_types) == 1:
# Exactly one subpattern may conditionally match, the rest always match.
# We can apply negative narrowing to this one position.
rest_type = TupleType(
[
curr if is_uninhabited(rest) else rest
for curr, rest in zip(inner_types, inner_rest_types)
],
current_type.partial_fallback,
)
elif isinstance(current_type, TupleType):
# For variadic tuples it is too tricky to match individual items like for fixed
# tuples, so we instead try to narrow the entire type.
Expand Down
26 changes: 26 additions & 0 deletions test-data/unit/check-python310.test
Original file line number Diff line number Diff line change
Expand Up @@ -1424,6 +1424,7 @@ def f(value: Literal[1] | Literal[2]) -> int:

[case testMatchSequencePatternNegativeNarrowing]
from typing import Union, Sequence, Tuple
from typing_extensions import Literal

m1: Sequence[int | str]

Expand All @@ -1448,6 +1449,31 @@ match m3:
reveal_type(m3) # N: Revealed type is "Tuple[Literal[1]]"
case r2:
reveal_type(m3) # N: Revealed type is "Tuple[Union[builtins.int, builtins.str]]"

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']]]"

[builtins fixtures/tuple.pyi]

[case testMatchEnumSingleChoice]
Expand Down
Loading