From 74bf2cc524a77115e6bf002a29065740a70e5c95 Mon Sep 17 00:00:00 2001 From: Brian Schubert Date: Sun, 22 Sep 2024 14:46:34 -0400 Subject: [PATCH] Fix negative narrowing of tuples in match statement --- mypy/checkpattern.py | 12 +++++++++++- test-data/unit/check-python310.test | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index a23be464b825..cb3577ce2f6e 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -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) @@ -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. diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 5ecc69dc7c32..e7028a027e25 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -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] @@ -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]