diff --git a/mypy/checkpattern.py b/mypy/checkpattern.py index 4fca0ce159c1..8a61453e5e72 100644 --- a/mypy/checkpattern.py +++ b/mypy/checkpattern.py @@ -508,12 +508,24 @@ def visit_mapping_pattern(self, o: MappingPattern) -> PatternType: captures[o.rest] = rest_type + else_type = current_type if can_match: # We can't narrow the type here, as Mapping key is invariant. new_type = self.type_context[-1] + if not o.keys: + # Match cannot be refuted, so narrow the remaining type + mapping = self.chk.named_type("typing.Mapping") + if_type, else_type = self.chk.conditional_types_with_intersection( + current_type, + [TypeRange(mapping, is_upper_bound=False)], + o, + default=current_type, + ) + if not isinstance(current_type, AnyType): + new_type = if_type else: new_type = UninhabitedType() - return PatternType(new_type, current_type, captures) + return PatternType(new_type, else_type, captures) def get_mapping_item_type( self, pattern: MappingPattern, mapping_type: Type, key: Expression diff --git a/test-data/unit/check-python310.test b/test-data/unit/check-python310.test index 59527bfff792..9861f0868e03 100644 --- a/test-data/unit/check-python310.test +++ b/test-data/unit/check-python310.test @@ -620,7 +620,59 @@ match m: reveal_type(r) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]" [builtins fixtures/dict.pyi] --- Mapping patterns currently do not narrow -- +[case testMatchMappingPatternNarrowing] +from typing import Mapping, Sequence + +def f1(x: dict[str, str] | list[str] | str) -> None: + match x: + case {}: + reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + case [*_]: + reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]" + case _: + reveal_type(x) # N: Revealed type is "builtins.str" + +def f1_rest(x: dict[str, str] | list[str] | str) -> None: + match x: + case {**rest}: + reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + case [*_]: + reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]" + case _: + reveal_type(x) # N: Revealed type is "builtins.str" + +def f2(x: Mapping[str, str] | Sequence[str] | str) -> None: + match x: + case {}: + reveal_type(x) # N: Revealed type is "typing.Mapping[builtins.str, builtins.str]" + case [*_]: + reveal_type(x) # N: Revealed type is "typing.Sequence[builtins.str]" + case _: + reveal_type(x) # N: Revealed type is "builtins.str" + +def f2_rest(x: Mapping[str, str] | Sequence[str] | str) -> None: + match x: + case {**rest}: + reveal_type(x) # N: Revealed type is "typing.Mapping[builtins.str, builtins.str]" + case [*_]: + reveal_type(x) # N: Revealed type is "typing.Sequence[builtins.str]" + case _: + reveal_type(x) # N: Revealed type is "builtins.str" +[builtins fixtures/dict.pyi] + +[case testMatchMappingPatternNarrowingAny] +from typing import Any + +def f1(x: Any) -> None: + match x: + case {}: + reveal_type(x) # N: Revealed type is "Any" + +def f2(x: Any) -> None: + match x: + case {"x": "y"}: + reveal_type(x) # N: Revealed type is "Any" +[builtins fixtures/dict.pyi] -- Class Pattern --