Skip to content

Commit e413220

Browse files
authored
Better match narrowing for irrefutable mapping patterns (#20906)
Fixes #18950 Fixes this comment #19081 (comment) (previously improved on by #20744 and #20782 )
1 parent ce7669e commit e413220

2 files changed

Lines changed: 66 additions & 2 deletions

File tree

mypy/checkpattern.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,12 +508,24 @@ def visit_mapping_pattern(self, o: MappingPattern) -> PatternType:
508508

509509
captures[o.rest] = rest_type
510510

511+
else_type = current_type
511512
if can_match:
512513
# We can't narrow the type here, as Mapping key is invariant.
513514
new_type = self.type_context[-1]
515+
if not o.keys:
516+
# Match cannot be refuted, so narrow the remaining type
517+
mapping = self.chk.named_type("typing.Mapping")
518+
if_type, else_type = self.chk.conditional_types_with_intersection(
519+
current_type,
520+
[TypeRange(mapping, is_upper_bound=False)],
521+
o,
522+
default=current_type,
523+
)
524+
if not isinstance(current_type, AnyType):
525+
new_type = if_type
514526
else:
515527
new_type = UninhabitedType()
516-
return PatternType(new_type, current_type, captures)
528+
return PatternType(new_type, else_type, captures)
517529

518530
def get_mapping_item_type(
519531
self, pattern: MappingPattern, mapping_type: Type, key: Expression

test-data/unit/check-python310.test

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,59 @@ match m:
620620
reveal_type(r) # N: Revealed type is "builtins.dict[builtins.str, builtins.int]"
621621
[builtins fixtures/dict.pyi]
622622

623-
-- Mapping patterns currently do not narrow --
623+
[case testMatchMappingPatternNarrowing]
624+
from typing import Mapping, Sequence
625+
626+
def f1(x: dict[str, str] | list[str] | str) -> None:
627+
match x:
628+
case {}:
629+
reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]"
630+
case [*_]:
631+
reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]"
632+
case _:
633+
reveal_type(x) # N: Revealed type is "builtins.str"
634+
635+
def f1_rest(x: dict[str, str] | list[str] | str) -> None:
636+
match x:
637+
case {**rest}:
638+
reveal_type(x) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]"
639+
case [*_]:
640+
reveal_type(x) # N: Revealed type is "builtins.list[builtins.str]"
641+
case _:
642+
reveal_type(x) # N: Revealed type is "builtins.str"
643+
644+
def f2(x: Mapping[str, str] | Sequence[str] | str) -> None:
645+
match x:
646+
case {}:
647+
reveal_type(x) # N: Revealed type is "typing.Mapping[builtins.str, builtins.str]"
648+
case [*_]:
649+
reveal_type(x) # N: Revealed type is "typing.Sequence[builtins.str]"
650+
case _:
651+
reveal_type(x) # N: Revealed type is "builtins.str"
652+
653+
def f2_rest(x: Mapping[str, str] | Sequence[str] | str) -> None:
654+
match x:
655+
case {**rest}:
656+
reveal_type(x) # N: Revealed type is "typing.Mapping[builtins.str, builtins.str]"
657+
case [*_]:
658+
reveal_type(x) # N: Revealed type is "typing.Sequence[builtins.str]"
659+
case _:
660+
reveal_type(x) # N: Revealed type is "builtins.str"
661+
[builtins fixtures/dict.pyi]
662+
663+
[case testMatchMappingPatternNarrowingAny]
664+
from typing import Any
665+
666+
def f1(x: Any) -> None:
667+
match x:
668+
case {}:
669+
reveal_type(x) # N: Revealed type is "Any"
670+
671+
def f2(x: Any) -> None:
672+
match x:
673+
case {"x": "y"}:
674+
reveal_type(x) # N: Revealed type is "Any"
675+
[builtins fixtures/dict.pyi]
624676

625677
-- Class Pattern --
626678

0 commit comments

Comments
 (0)