Skip to content

Commit fc9acbd

Browse files
authored
gh-110686: Fix pattern matching with runtime_checkable protocols (#290)
1 parent 9de9fd6 commit fc9acbd

File tree

3 files changed

+41
-5
lines changed

3 files changed

+41
-5
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
signature of `typing.NewType.__call__`. Patch by Alex Waygood.
66
- `typing.deprecated` now gives a better error message if you pass a non-`str`
77
argument to the `msg` parameter. Patch by Alex Waygood.
8+
- Exclude `__match_args__` from `Protocol` members,
9+
this is a backport of https://github.com/python/cpython/pull/110683
810

911
# Release 4.8.0 (September 17, 2023)
1012

src/test_typing_extensions.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2521,6 +2521,39 @@ class Bad: pass
25212521
self.assertNotIsInstance(Other(), Concrete)
25222522
self.assertIsInstance(NT(1, 2), Position)
25232523

2524+
def test_runtime_checkable_with_match_args(self):
2525+
@runtime_checkable
2526+
class P_regular(Protocol):
2527+
x: int
2528+
y: int
2529+
2530+
@runtime_checkable
2531+
class P_match(Protocol):
2532+
__match_args__ = ("x", "y")
2533+
x: int
2534+
y: int
2535+
2536+
class Regular:
2537+
def __init__(self, x: int, y: int):
2538+
self.x = x
2539+
self.y = y
2540+
2541+
class WithMatch:
2542+
__match_args__ = ("x", "y", "z")
2543+
def __init__(self, x: int, y: int, z: int):
2544+
self.x = x
2545+
self.y = y
2546+
self.z = z
2547+
2548+
class Nope: ...
2549+
2550+
self.assertIsInstance(Regular(1, 2), P_regular)
2551+
self.assertIsInstance(Regular(1, 2), P_match)
2552+
self.assertIsInstance(WithMatch(1, 2, 3), P_regular)
2553+
self.assertIsInstance(WithMatch(1, 2, 3), P_match)
2554+
self.assertNotIsInstance(Nope(), P_regular)
2555+
self.assertNotIsInstance(Nope(), P_match)
2556+
25242557
def test_protocols_isinstance_init(self):
25252558
T = TypeVar('T')
25262559
@runtime_checkable
@@ -5062,12 +5095,12 @@ def test_typing_extensions_defers_when_possible(self):
50625095
exclude |= {'final', 'Any', 'NewType'}
50635096
if sys.version_info < (3, 12):
50645097
exclude |= {
5065-
'Protocol', 'SupportsAbs', 'SupportsBytes',
5098+
'SupportsAbs', 'SupportsBytes',
50665099
'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt',
50675100
'SupportsRound', 'Unpack',
50685101
}
50695102
if sys.version_info < (3, 13):
5070-
exclude |= {'NamedTuple', 'TypedDict', 'is_typeddict'}
5103+
exclude |= {'NamedTuple', 'Protocol', 'TypedDict', 'is_typeddict'}
50715104
for item in typing_extensions.__all__:
50725105
if item not in exclude and hasattr(typing, item):
50735106
self.assertIs(

src/typing_extensions.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ def clear_overloads():
473473
"__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__",
474474
"__subclasshook__", "__orig_class__", "__init__", "__new__",
475475
"__protocol_attrs__", "__callable_proto_members_only__",
476+
"__match_args__",
476477
}
477478

478479
if sys.version_info >= (3, 9):
@@ -503,9 +504,9 @@ def _caller(depth=2):
503504
return None
504505

505506

506-
# The performance of runtime-checkable protocols is significantly improved on Python 3.12,
507-
# so we backport the 3.12 version of Protocol to Python <=3.11
508-
if sys.version_info >= (3, 12):
507+
# `__match_args__` attribute was removed from protocol members in 3.13,
508+
# we want to backport this change to older Python versions.
509+
if sys.version_info >= (3, 13):
509510
Protocol = typing.Protocol
510511
else:
511512
def _allow_reckless_class_checks(depth=3):

0 commit comments

Comments
 (0)