Skip to content

Commit 50fc847

Browse files
authored
Omit errors for class pattern matches against object (#19709)
Since the class pattern matches any subclass, it can also be used to check whether the matched object has a specific attribute. Mypy should not emit an error for it. ```py match m: case object(foo=_): m.foo ``` Using `object` for it is recommended in [PEP 635](https://peps.python.org/pep-0635/#history-and-context) and more prominently in the precursor [PEP 622](https://peps.python.org/pep-0622/#class-patterns).
1 parent 881a35a commit 50fc847

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

mypy/checker_shared.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,10 @@ def checking_await_set(self) -> Iterator[None]:
272272
def get_precise_awaitable_type(self, typ: Type, local_errors: ErrorWatcher) -> Type | None:
273273
raise NotImplementedError
274274

275+
@abstractmethod
276+
def add_any_attribute_to_type(self, typ: Type, name: str) -> Type:
277+
raise NotImplementedError
278+
275279
@abstractmethod
276280
def is_defined_in_stub(self, typ: Instance, /) -> bool:
277281
raise NotImplementedError

mypy/checkpattern.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -671,12 +671,15 @@ def visit_class_pattern(self, o: ClassPattern) -> PatternType:
671671
has_local_errors = local_errors.has_new_errors()
672672
if has_local_errors or key_type is None:
673673
key_type = AnyType(TypeOfAny.from_error)
674-
self.msg.fail(
675-
message_registry.CLASS_PATTERN_UNKNOWN_KEYWORD.format(
676-
typ.str_with_options(self.options), keyword
677-
),
678-
pattern,
679-
)
674+
if not (type_info and type_info.fullname == "builtins.object"):
675+
self.msg.fail(
676+
message_registry.CLASS_PATTERN_UNKNOWN_KEYWORD.format(
677+
typ.str_with_options(self.options), keyword
678+
),
679+
pattern,
680+
)
681+
elif keyword is not None:
682+
new_type = self.chk.add_any_attribute_to_type(new_type, keyword)
680683

681684
inner_type, inner_rest_type, inner_captures = self.accept(pattern, key_type)
682685
if is_uninhabited(inner_type):

test-data/unit/check-python310.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,15 +1003,24 @@ match m:
10031003
[builtins fixtures/tuple.pyi]
10041004

10051005
[case testMatchClassPatternNonexistentKeyword]
1006+
from typing import Any
10061007
class A: ...
10071008

10081009
m: object
1010+
n: Any
10091011

10101012
match m:
10111013
case A(a=j): # E: Class "__main__.A" has no attribute "a"
10121014
reveal_type(m) # N: Revealed type is "__main__.A"
10131015
reveal_type(j) # N: Revealed type is "Any"
10141016

1017+
match n:
1018+
# Matching against object should not emit an error for non-existing keys
1019+
case object(a=k):
1020+
reveal_type(n) # N: Revealed type is "builtins.object"
1021+
reveal_type(n.a) # N: Revealed type is "Any"
1022+
reveal_type(k) # N: Revealed type is "Any"
1023+
10151024
[case testMatchClassPatternDuplicateKeyword]
10161025
class A:
10171026
a: str

0 commit comments

Comments
 (0)