diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 3c51c4106909..215a6dce6098 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -14,7 +14,7 @@ from mypy.maptype import map_instance_to_supertype from mypy.meet import narrow_declared_type from mypy.messages import MessageBuilder -from mypy.nodes import ARG_POS, Context, Expression, NameExpr, TypeAlias, Var +from mypy.nodes import ARG_POS, Context, Expression, NameExpr, Node, TypeAlias, Var from mypy.options import Options from mypy.patterns import ( AsPattern, @@ -104,6 +104,8 @@ class PatternChecker(PatternVisitor[PatternType]): subject_type: Type # Type of the subject to check the (sub)pattern against type_context: list[Type] + # Pattern node currently being processed + node_context: list[Node] # Types that match against self instead of their __match_args__ if used as a class pattern # Filled in from self_match_type_names self_match_types: list[Type] @@ -121,6 +123,7 @@ def __init__( self.plugin = plugin self.type_context = [] + self.node_context = [] self.self_match_types = self.generate_types_from_names(self_match_type_names) self.non_sequence_match_types = self.generate_types_from_names( non_sequence_match_type_names @@ -129,8 +132,10 @@ def __init__( def accept(self, o: Pattern, type_context: Type) -> PatternType: self.type_context.append(type_context) + self.node_context.append(o) result = o.accept(self) self.type_context.pop() + self.node_context.pop() return result @@ -140,7 +145,12 @@ def visit_as_pattern(self, o: AsPattern) -> PatternType: pattern_type = self.accept(o.pattern, current_type) typ, rest_type, type_map = pattern_type else: - typ, rest_type, type_map = current_type, UninhabitedType(), {} + typ, type_map = current_type, {} + if len(self.node_context) >= 2 and isinstance(self.node_context[-2], SequencePattern): + # Don't narrow rest type to Never if parent node is a sequence pattern + rest_type = current_type + else: + rest_type = UninhabitedType() if not is_uninhabited(typ) and o.name is not None: typ, _ = self.chk.conditional_types_with_intersection( diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 3a9f12e8a550..052931f406f4 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -1681,6 +1681,7 @@ def f(value: Literal[1] | Literal[2]) -> int: [typing fixtures/typing-medium.pyi] [case testMatchSequencePatternNegativeNarrowing] +# flags: --warn-unreachable from typing import Literal, Union, Sequence, Tuple m1: Sequence[int | str] @@ -1733,7 +1734,42 @@ match m6: case _: reveal_type(m6) # N: Revealed type is "tuple[Union[Literal[1], Literal[2]], Union[Literal['a'], Literal['b']]]" -[builtins fixtures/tuple.pyi] +m7: dict[str, str] + +match (m7, m7): + case ({"a": "1"}, _): + reveal_type(m7) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + case (_, {"a": "2"}): + reveal_type(m7) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + +match (m7, m7): + case (dict(), _): + reveal_type(m7) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + case (_, dict()): + reveal_type(m7) # E: Statement is unreachable + case (_, _): + reveal_type(m7) # E: Statement is unreachable + +match (m7, 4): + case ({"a": "1"}, _): + reveal_type(m7) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + case r7: + reveal_type(r7) # N: Revealed type is "tuple[builtins.dict[builtins.str, builtins.str], Literal[4]?]" + +m8: list[int] + +match (m8, m8): + case ([1], _): + reveal_type(m8) # N: Revealed type is "builtins.list[builtins.int]" + case (_, [2]): + reveal_type(m8) # N: Revealed type is "builtins.list[builtins.int]" + +match (m8, m8): + case (list(), _): + reveal_type(m8) # N: Revealed type is "builtins.list[builtins.int]" + case (_, [2]): + reveal_type(m8) # E: Statement is unreachable +[builtins fixtures/dict.pyi] [case testMatchEnumSingleChoice] from enum import Enum