Skip to content

Commit

Permalink
Fix checking of match sequence pattern against bounded type variables (
Browse files Browse the repository at this point in the history
…#18091)

Fixes #18089

Adds handling for bounded type variables when checking match sequence
patterns.

Previously, this crashed. Now, this correctly narrows the upper bound of
the type variable:
```python
from typing import TypeVar, Sequence

T = TypeVar("T", bound=Sequence[int | str])

def accept_seq_int(x: Sequence[int]): pass

def f(x: T) -> None:
    match x:
        case [1, 2]:
            accept_seq_int(x)  # ok: upper bound narrowed to Sequence[int]
        case _:
            accept_seq_int(x)  # E: Argument 1 to "accept_seq_int" has incompatible type "T"; expected "Sequence[int]"
```
  • Loading branch information
brianschubert authored Nov 15, 2024
1 parent fa01a07 commit 880eb87
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 6 deletions.
21 changes: 15 additions & 6 deletions mypy/checkpattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
TypedDictType,
TypeOfAny,
TypeVarTupleType,
TypeVarType,
UninhabitedType,
UnionType,
UnpackType,
Expand Down Expand Up @@ -343,13 +344,11 @@ def visit_sequence_pattern(self, o: SequencePattern) -> PatternType:
new_inner_type = UninhabitedType()
for typ in new_inner_types:
new_inner_type = join_types(new_inner_type, typ)
new_type = self.construct_sequence_child(current_type, new_inner_type)
if is_subtype(new_type, current_type):
new_type, _ = self.chk.conditional_types_with_intersection(
current_type, [get_type_range(new_type)], o, default=current_type
)
if isinstance(current_type, TypeVarType):
new_bound = self.narrow_sequence_child(current_type.upper_bound, new_inner_type, o)
new_type = current_type.copy_modified(upper_bound=new_bound)
else:
new_type = current_type
new_type = self.narrow_sequence_child(current_type, new_inner_type, o)
return PatternType(new_type, rest_type, captures)

def get_sequence_type(self, t: Type, context: Context) -> Type | None:
Expand Down Expand Up @@ -448,6 +447,16 @@ def expand_starred_pattern_types(

return new_types

def narrow_sequence_child(self, outer_type: Type, inner_type: Type, ctx: Context) -> Type:
new_type = self.construct_sequence_child(outer_type, inner_type)
if is_subtype(new_type, outer_type):
new_type, _ = self.chk.conditional_types_with_intersection(
outer_type, [get_type_range(new_type)], ctx, default=outer_type
)
else:
new_type = outer_type
return new_type

def visit_starred_pattern(self, o: StarredPattern) -> PatternType:
captures: dict[Expression, Type] = {}
if o.capture is not None:
Expand Down
27 changes: 27 additions & 0 deletions test-data/unit/check-python310.test
Original file line number Diff line number Diff line change
Expand Up @@ -2382,3 +2382,30 @@ def test(xs: Tuple[Unpack[Ts]]) -> None:
reveal_type(b3) # N: Revealed type is "builtins.list[builtins.object]"
reveal_type(c3) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testMatchSequencePatternTypeVarBoundNoCrash]
# This was crashing: https://github.com/python/mypy/issues/18089
from typing import TypeVar, Sequence, Any

T = TypeVar("T", bound=Sequence[Any])

def f(x: T) -> None:
match x:
case [_]:
pass
[builtins fixtures/tuple.pyi]

[case testMatchSequencePatternTypeVarBoundNarrows]
from typing import TypeVar, Sequence

T = TypeVar("T", bound=Sequence[int | str])

def accept_seq_int(x: Sequence[int]): ...

def f(x: T) -> None:
match x:
case [1, 2]:
accept_seq_int(x)
case _:
accept_seq_int(x) # E: Argument 1 to "accept_seq_int" has incompatible type "T"; expected "Sequence[int]"
[builtins fixtures/tuple.pyi]

0 comments on commit 880eb87

Please sign in to comment.