Skip to content

Fix handling of named tuples in class match pattern #18663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions mypy/checkpattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
get_proper_type,
split_with_prefix_and_suffix,
)
from mypy.typevars import fill_typevars
from mypy.typevars import fill_typevars, fill_typevars_with_any
from mypy.visitor import PatternVisitor

self_match_type_names: Final = [
Expand Down Expand Up @@ -544,16 +544,7 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:
self.msg.fail(message_registry.CLASS_PATTERN_GENERIC_TYPE_ALIAS, o)
return self.early_non_match()
if isinstance(type_info, TypeInfo):
any_type = AnyType(TypeOfAny.implementation_artifact)
args: list[Type] = []
for tv in type_info.defn.type_vars:
if isinstance(tv, TypeVarTupleType):
args.append(
UnpackType(self.chk.named_generic_type("builtins.tuple", [any_type]))
)
else:
args.append(any_type)
typ: Type = Instance(type_info, args)
typ: Type = fill_typevars_with_any(type_info)
elif isinstance(type_info, TypeAlias):
typ = type_info.target
elif (
Expand Down Expand Up @@ -703,6 +694,8 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:

def should_self_match(self, typ: Type) -> bool:
typ = get_proper_type(typ)
if isinstance(typ, TupleType):
typ = typ.partial_fallback
if isinstance(typ, Instance) and typ.type.get("__match_args__") is not None:
# Named tuples and other subtypes of builtins that define __match_args__
# should not self match.
Expand Down
30 changes: 30 additions & 0 deletions test-data/unit/check-python310.test
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,21 @@ match m:
reveal_type(j) # N: Revealed type is "builtins.int"
[builtins fixtures/tuple.pyi]

[case testMatchSequencePatternCaptureNamedTuple]
from typing import NamedTuple

class N(NamedTuple):
x: int
y: str

a = N(1, "a")

match a:
case [x, y]:
reveal_type(x) # N: Revealed type is "builtins.int"
reveal_type(y) # N: Revealed type is "builtins.str"
[builtins fixtures/tuple.pyi]

[case testMatchClassPatternCaptureGeneric]
from typing import Generic, TypeVar

Expand Down Expand Up @@ -2522,3 +2537,18 @@ def fn2(x: Some | int | str) -> None:
case Some(value): # E: Incompatible types in capture pattern (pattern captures type "Union[int, str]", variable has type "Callable[[], str]")
pass
[builtins fixtures/dict.pyi]

[case testMatchNamedTupleSequence]
from typing import Any, NamedTuple

class T(NamedTuple):
t: list[Any]

class K(NamedTuple):
k: int

def f(t: T) -> None:
match t:
case T([K() as k]):
reveal_type(k) # N: Revealed type is "Tuple[builtins.int, fallback=__main__.K]"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is a test case like this, which would cover structural matching of named tuples:

from typing import NamedTuple

class N(NamedTuple):
    x: int
    y: str

a = N(1, "a")

match a:
    case [x, y]:
        reveal_type(x)
        reveal_type(y)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't see such test, will add one.

[builtins fixtures/tuple.pyi]