Skip to content

Commit 778d088

Browse files
committed
pythongh-110686: Test pattern matching with runtime_checkable protocols
1 parent 5c6e854 commit 778d088

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

Lib/test/test_patma.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2760,6 +2760,120 @@ def test_patma_255(self):
27602760
self.assertEqual(y, 1)
27612761
self.assertIs(z, x)
27622762

2763+
def test_patma_256(self):
2764+
# Runtime-checkable protocol
2765+
from typing import Protocol, runtime_checkable
2766+
2767+
@runtime_checkable
2768+
class P(Protocol):
2769+
x: int
2770+
y: int
2771+
2772+
class A:
2773+
def __init__(self, x: int, y: int):
2774+
self.x = x
2775+
self.y = y
2776+
2777+
class B(A): ...
2778+
2779+
for cls in (A, B):
2780+
with self.subTest(cls=cls):
2781+
inst = cls(1, 2)
2782+
w = 0
2783+
match inst:
2784+
case P() as p:
2785+
self.assertIsInstance(p, cls)
2786+
self.assertEqual(p.x, 1)
2787+
self.assertEqual(p.y, 2)
2788+
w = 1
2789+
self.assertEqual(w, 1)
2790+
2791+
q = 0
2792+
match inst:
2793+
case P(x=x, y=y):
2794+
self.assertEqual(x, 1)
2795+
self.assertEqual(y, 2)
2796+
q = 1
2797+
self.assertEqual(q, 1)
2798+
2799+
2800+
def test_patma_257(self):
2801+
# Runtime-checkable generic protocol
2802+
from typing import Generic, TypeVar, Protocol, runtime_checkable
2803+
2804+
T = TypeVar('T') # not using PEP695 to be able to backport changes
2805+
2806+
@runtime_checkable
2807+
class P(Protocol[T]):
2808+
a: T
2809+
b: T
2810+
2811+
class A:
2812+
def __init__(self, x: int, y: int):
2813+
self.x = x
2814+
self.y = y
2815+
2816+
class G(Generic[T]):
2817+
def __init__(self, x: T, y: T):
2818+
self.x = x
2819+
self.y = y
2820+
2821+
for cls in (A, G):
2822+
with self.subTest(cls=cls):
2823+
inst = cls(1, 2)
2824+
w = 0
2825+
match inst:
2826+
case P():
2827+
w = 1
2828+
self.assertEqual(w, 0)
2829+
2830+
def test_patma_258(self):
2831+
# Runtime-checkable protocol with `__match_args__`
2832+
from typing import Protocol, runtime_checkable
2833+
2834+
# Used to fail before
2835+
# https://github.com/python/cpython/issues/110682
2836+
@runtime_checkable
2837+
class P(Protocol):
2838+
__match_args__ = ('x', 'y')
2839+
x: int
2840+
y: int
2841+
2842+
class A:
2843+
def __init__(self, x: int, y: int):
2844+
self.x = x
2845+
self.y = y
2846+
2847+
class B(A): ...
2848+
2849+
for cls in (A, B):
2850+
with self.subTest(cls=cls):
2851+
inst = cls(1, 2)
2852+
w = 0
2853+
match inst:
2854+
case P() as p:
2855+
self.assertIsInstance(p, cls)
2856+
self.assertEqual(p.x, 1)
2857+
self.assertEqual(p.y, 2)
2858+
w = 1
2859+
self.assertEqual(w, 1)
2860+
2861+
q = 0
2862+
match inst:
2863+
case P(x=x, y=y):
2864+
self.assertEqual(x, 1)
2865+
self.assertEqual(y, 2)
2866+
q = 1
2867+
self.assertEqual(q, 1)
2868+
2869+
g = 0
2870+
match inst:
2871+
case P(x, y):
2872+
self.assertEqual(x, 1)
2873+
self.assertEqual(y, 2)
2874+
g = 1
2875+
self.assertEqual(g, 1)
2876+
27632877

27642878
class TestSyntaxErrors(unittest.TestCase):
27652879

@@ -3198,6 +3312,35 @@ def test_class_pattern_not_type(self):
31983312
w = 0
31993313
self.assertIsNone(w)
32003314

3315+
def test_regular_protocol(self):
3316+
from typing import Protocol
3317+
class P(Protocol): ...
3318+
msg = (
3319+
'Instance and class checks can only be used '
3320+
'with @runtime_checkable protocols'
3321+
)
3322+
w = None
3323+
with self.assertRaisesRegex(TypeError, msg):
3324+
match 1:
3325+
case P():
3326+
w = 0
3327+
self.assertIsNone(w)
3328+
3329+
def test_positional_patterns_with_regular_protocol(self):
3330+
from typing import Protocol
3331+
class P(Protocol):
3332+
x: int # no `__match_args__`
3333+
y: int
3334+
class A:
3335+
x = 1
3336+
y = 2
3337+
w = None
3338+
with self.assertRaises(TypeError):
3339+
match A():
3340+
case P(x, y):
3341+
w = 0
3342+
self.assertIsNone(w)
3343+
32013344

32023345
class TestValueErrors(unittest.TestCase):
32033346

0 commit comments

Comments
 (0)