Skip to content

gh-110686: Test pattern matching with runtime_checkable protocols #110687

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 4 commits into from
Dec 10, 2023
Merged
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
155 changes: 155 additions & 0 deletions Lib/test/test_patma.py
Original file line number Diff line number Diff line change
Expand Up @@ -2760,6 +2760,132 @@ def test_patma_255(self):
self.assertEqual(y, 1)
self.assertIs(z, x)

def test_patma_runtime_checkable_protocol(self):
# Runtime-checkable protocol
from typing import Protocol, runtime_checkable

@runtime_checkable
class P(Protocol):
x: int
y: int

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

class B(A): ...

for cls in (A, B):
with self.subTest(cls=cls.__name__):
inst = cls(1, 2)
w = 0
match inst:
case P() as p:
self.assertIsInstance(p, cls)
self.assertEqual(p.x, 1)
self.assertEqual(p.y, 2)
w = 1
self.assertEqual(w, 1)

q = 0
match inst:
case P(x=x, y=y):
self.assertEqual(x, 1)
self.assertEqual(y, 2)
q = 1
self.assertEqual(q, 1)


def test_patma_generic_protocol(self):
# Runtime-checkable generic protocol
from typing import Generic, TypeVar, Protocol, runtime_checkable

T = TypeVar('T') # not using PEP695 to be able to backport changes

@runtime_checkable
class P(Protocol[T]):
a: T
b: T

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

class G(Generic[T]):
def __init__(self, x: T, y: T):
self.x = x
self.y = y

for cls in (A, G):
with self.subTest(cls=cls.__name__):
inst = cls(1, 2)
w = 0
match inst:
case P():
w = 1
self.assertEqual(w, 0)

def test_patma_protocol_with_match_args(self):
# Runtime-checkable protocol with `__match_args__`
from typing import Protocol, runtime_checkable

# Used to fail before
# https://github.com/python/cpython/issues/110682
@runtime_checkable
class P(Protocol):
__match_args__ = ('x', 'y')
x: int
y: int

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

class B(A): ...

for cls in (A, B):
with self.subTest(cls=cls.__name__):
inst = cls(1, 2)
w = 0
match inst:
case P() as p:
self.assertIsInstance(p, cls)
self.assertEqual(p.x, 1)
self.assertEqual(p.y, 2)
w = 1
self.assertEqual(w, 1)

q = 0
match inst:
case P(x=x, y=y):
self.assertEqual(x, 1)
self.assertEqual(y, 2)
q = 1
self.assertEqual(q, 1)

j = 0
match inst:
case P(x=1, y=2):
j = 1
self.assertEqual(j, 1)

g = 0
match inst:
case P(x, y):
self.assertEqual(x, 1)
self.assertEqual(y, 2)
g = 1
self.assertEqual(g, 1)

h = 0
match inst:
case P(1, 2):
h = 1
self.assertEqual(h, 1)


class TestSyntaxErrors(unittest.TestCase):

Expand Down Expand Up @@ -3198,6 +3324,35 @@ def test_class_pattern_not_type(self):
w = 0
self.assertIsNone(w)

def test_regular_protocol(self):
from typing import Protocol
class P(Protocol): ...
msg = (
'Instance and class checks can only be used '
'with @runtime_checkable protocols'
)
w = None
with self.assertRaisesRegex(TypeError, msg):
match 1:
case P():
w = 0
self.assertIsNone(w)

def test_positional_patterns_with_regular_protocol(self):
from typing import Protocol
class P(Protocol):
x: int # no `__match_args__`
y: int
class A:
x = 1
y = 2
w = None
with self.assertRaises(TypeError):
match A():
case P(x, y):
w = 0
self.assertIsNone(w)


class TestValueErrors(unittest.TestCase):

Expand Down