Skip to content

Commit 688121a

Browse files
authored
Better match narrowing for irrefutable sequence patterns (#20782)
The changes around narrow_sequence_child: - implement my suggestion from here: #18091 (review) - remove an is_subtype check I don't understand Fixes #13955 Helps with things in #19081
1 parent e74d4aa commit 688121a

File tree

2 files changed

+39
-16
lines changed

2 files changed

+39
-16
lines changed

mypy/checkpattern.py

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from mypy.maptype import map_instance_to_supertype
1515
from mypy.meet import narrow_declared_type
1616
from mypy.messages import MessageBuilder
17-
from mypy.nodes import ARG_POS, Context, Expression, NameExpr, TempNode, TypeAlias, Var
17+
from mypy.nodes import ARG_POS, Expression, NameExpr, TempNode, TypeAlias, Var
1818
from mypy.options import Options
1919
from mypy.patterns import (
2020
AsPattern,
@@ -394,11 +394,14 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
394394
new_inner_type = UninhabitedType()
395395
for typ in new_inner_types:
396396
new_inner_type = join_types(new_inner_type, typ)
397-
if isinstance(current_type, TypeVarType):
398-
new_bound = self.narrow_sequence_child(current_type.upper_bound, new_inner_type, o)
399-
new_type = current_type.copy_modified(upper_bound=new_bound)
400-
else:
401-
new_type = self.narrow_sequence_child(current_type, new_inner_type, o)
397+
new_type = self.construct_sequence_child(current_type, new_inner_type)
398+
new_type, possible_rest_type = self.chk.conditional_types_with_intersection(
399+
current_type, [get_type_range(new_type)], o, default=current_type
400+
)
401+
if star_position is not None and len(o.patterns) == 1:
402+
# Match cannot be refuted, so narrow the remaining type
403+
rest_type = possible_rest_type
404+
402405
return PatternType(new_type, rest_type, captures)
403406

404407
def contract_starred_pattern_types(
@@ -478,16 +481,6 @@ def expand_starred_pattern_types(
478481

479482
return new_types
480483

481-
def narrow_sequence_child(self, outer_type: Type, inner_type: Type, ctx: Context) -> Type:
482-
new_type = self.construct_sequence_child(outer_type, inner_type)
483-
if is_subtype(new_type, outer_type):
484-
new_type, _ = self.chk.conditional_types_with_intersection(
485-
outer_type, [get_type_range(new_type)], ctx, default=outer_type
486-
)
487-
else:
488-
new_type = outer_type
489-
return new_type
490-
491484
def visit_starred_pattern(self, o: StarredPattern) -> PatternType:
492485
captures: dict[Expression, Type] = {}
493486
if o.capture is not None:
@@ -796,6 +789,9 @@ def construct_sequence_child(self, outer_type: Type, inner_type: Type) -> Type:
796789
or class T(Sequence[Tuple[T, T]]), there is no way any of those can map to Sequence[str].
797790
"""
798791
proper_type = get_proper_type(outer_type)
792+
if isinstance(proper_type, TypeVarType):
793+
new_bound = self.construct_sequence_child(proper_type.upper_bound, inner_type)
794+
return proper_type.copy_modified(upper_bound=new_bound)
799795
if isinstance(proper_type, AnyType):
800796
return outer_type
801797
if isinstance(proper_type, UnionType):

test-data/unit/check-python310.test

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,33 @@ match m6:
17461746

17471747
[builtins fixtures/tuple.pyi]
17481748

1749+
[case testMatchSequenceWildcardRefutability]
1750+
# flags: --strict-equality --warn-unreachable
1751+
def f1(x: int | list[int]):
1752+
match x:
1753+
case [*foo]:
1754+
reveal_type(x) # N: Revealed type is "builtins.list[builtins.int]"
1755+
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.int]"
1756+
case _:
1757+
reveal_type(x) # N: Revealed type is "builtins.int"
1758+
1759+
def f2(x: object):
1760+
match x:
1761+
case [*foo]:
1762+
reveal_type(x) # N: Revealed type is "typing.Sequence[builtins.object]"
1763+
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.object]"
1764+
case _:
1765+
reveal_type(x) # N: Revealed type is "builtins.object"
1766+
1767+
def f3(x: int | list[int]):
1768+
match x:
1769+
case [1, *foo]:
1770+
reveal_type(x) # N: Revealed type is "builtins.list[builtins.int]"
1771+
reveal_type(foo) # N: Revealed type is "builtins.list[builtins.int]"
1772+
case _:
1773+
reveal_type(x) # N: Revealed type is "builtins.int | builtins.list[builtins.int]"
1774+
[builtins fixtures/tuple.pyi]
1775+
17491776
[case testMatchTupleUnions]
17501777
from typing_extensions import Unpack
17511778

0 commit comments

Comments
 (0)