Skip to content

Commit

Permalink
gh-110686: Fix pattern matching with runtime_checkable protocols (#290
Browse files Browse the repository at this point in the history
)
  • Loading branch information
sobolevn authored Oct 31, 2023
1 parent 9de9fd6 commit fc9acbd
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
signature of `typing.NewType.__call__`. Patch by Alex Waygood.
- `typing.deprecated` now gives a better error message if you pass a non-`str`
argument to the `msg` parameter. Patch by Alex Waygood.
- Exclude `__match_args__` from `Protocol` members,
this is a backport of https://github.com/python/cpython/pull/110683

# Release 4.8.0 (September 17, 2023)

Expand Down
37 changes: 35 additions & 2 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2521,6 +2521,39 @@ class Bad: pass
self.assertNotIsInstance(Other(), Concrete)
self.assertIsInstance(NT(1, 2), Position)

def test_runtime_checkable_with_match_args(self):
@runtime_checkable
class P_regular(Protocol):
x: int
y: int

@runtime_checkable
class P_match(Protocol):
__match_args__ = ("x", "y")
x: int
y: int

class Regular:
def __init__(self, x: int, y: int):
self.x = x
self.y = y

class WithMatch:
__match_args__ = ("x", "y", "z")
def __init__(self, x: int, y: int, z: int):
self.x = x
self.y = y
self.z = z

class Nope: ...

self.assertIsInstance(Regular(1, 2), P_regular)
self.assertIsInstance(Regular(1, 2), P_match)
self.assertIsInstance(WithMatch(1, 2, 3), P_regular)
self.assertIsInstance(WithMatch(1, 2, 3), P_match)
self.assertNotIsInstance(Nope(), P_regular)
self.assertNotIsInstance(Nope(), P_match)

def test_protocols_isinstance_init(self):
T = TypeVar('T')
@runtime_checkable
Expand Down Expand Up @@ -5062,12 +5095,12 @@ def test_typing_extensions_defers_when_possible(self):
exclude |= {'final', 'Any', 'NewType'}
if sys.version_info < (3, 12):
exclude |= {
'Protocol', 'SupportsAbs', 'SupportsBytes',
'SupportsAbs', 'SupportsBytes',
'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt',
'SupportsRound', 'Unpack',
}
if sys.version_info < (3, 13):
exclude |= {'NamedTuple', 'TypedDict', 'is_typeddict'}
exclude |= {'NamedTuple', 'Protocol', 'TypedDict', 'is_typeddict'}
for item in typing_extensions.__all__:
if item not in exclude and hasattr(typing, item):
self.assertIs(
Expand Down
7 changes: 4 additions & 3 deletions src/typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ def clear_overloads():
"__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__",
"__subclasshook__", "__orig_class__", "__init__", "__new__",
"__protocol_attrs__", "__callable_proto_members_only__",
"__match_args__",
}

if sys.version_info >= (3, 9):
Expand Down Expand Up @@ -503,9 +504,9 @@ def _caller(depth=2):
return None


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

0 comments on commit fc9acbd

Please sign in to comment.