Skip to content

Commit 8104fe7

Browse files
added subpattern support
1 parent 845d78c commit 8104fe7

File tree

3 files changed

+83
-40
lines changed

3 files changed

+83
-40
lines changed

Lib/test/test_patma.py

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import dataclasses
44
import enum
55
import inspect
6-
from re import I
76
import sys
87
import unittest
98

@@ -14,6 +13,13 @@ class Point:
1413
y: int
1514

1615

16+
@dataclasses.dataclass
17+
class Point3D:
18+
x: int
19+
y: int
20+
z: int
21+
22+
1723
class TestCompiler(unittest.TestCase):
1824

1925
def test_refleaks(self):
@@ -2895,6 +2901,60 @@ def test_patma_union_type(self):
28952901
x = 1
28962902
self.assertEqual(x, 1)
28972903

2904+
def test_union_type_positional_subpattern(self):
2905+
IntOrStr = int | str
2906+
x = 1
2907+
w = None
2908+
match x:
2909+
case IntOrStr(y):
2910+
w = y
2911+
self.assertEqual(w, 1)
2912+
2913+
def test_union_type_keyword_subpattern(self):
2914+
EitherPoint = Point | Point3D
2915+
p = Point(x=1, y=2)
2916+
w = None
2917+
match p:
2918+
case EitherPoint(x=1, y=2):
2919+
w = 1
2920+
self.assertEqual(w, 1)
2921+
2922+
def test_patma_union_no_match(self):
2923+
IntOrStr = int | str
2924+
x = None
2925+
match x:
2926+
case IntOrStr():
2927+
x = 1
2928+
self.assertIsNone(x)
2929+
2930+
def test_patma_union_arg(self):
2931+
p = Point(x=1, y=2)
2932+
IntOrStr = int | str
2933+
w = None
2934+
match p:
2935+
case Point(IntOrStr(), IntOrStr()):
2936+
w = 1
2937+
self.assertEqual(w, 1)
2938+
2939+
def test_patma_union_kwarg(self):
2940+
p = Point(x=1, y=2)
2941+
IntOrStr = int | str
2942+
w = None
2943+
match p:
2944+
case Point(x=IntOrStr(), y=IntOrStr()):
2945+
w = 1
2946+
self.assertEqual(w, 1)
2947+
2948+
def test_union_type_match_second_member(self):
2949+
EitherPoint = Point | Point3D
2950+
p = Point3D(x=1, y=2, z=3)
2951+
w = None
2952+
match p:
2953+
case EitherPoint(x=1, y=2, z=3):
2954+
w = 1
2955+
self.assertEqual(w, 1)
2956+
2957+
28982958

28992959
class TestSyntaxErrors(unittest.TestCase):
29002960

@@ -3370,31 +3430,6 @@ class A:
33703430
w = 0
33713431
self.assertIsNone(w)
33723432

3373-
def test_union_type_postional_subpattern(self):
3374-
IntOrStr = int | str
3375-
x = 1
3376-
w = None
3377-
with self.assertRaises(TypeError):
3378-
match x:
3379-
case IntOrStr(x):
3380-
w = 0
3381-
self.assertEqual(x, 1)
3382-
self.assertIsNone(w)
3383-
3384-
def test_union_type_keyword_subpattern(self):
3385-
@dataclasses.dataclass
3386-
class Point2:
3387-
x: int
3388-
y: int
3389-
EitherPoint = Point | Point2
3390-
x = Point(x=1, y=2)
3391-
w = None
3392-
with self.assertRaises(TypeError):
3393-
match x:
3394-
case EitherPoint(x=1, y=2):
3395-
w = 0
3396-
self.assertIsNone(w)
3397-
33983433

33993434
class TestValueErrors(unittest.TestCase):
34003435

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Since Python 3.10, it was possible to use unions as the second argument to ``isinstance``. Now, unions can also be used as match patterns. However, no sub-patterns can be used for unions; only the basic ``isinstance`` function is available.
1+
Since Python 3.10, it was possible to use unions as the second argument to ``isinstance``. Now, unions can also be used as match patterns.

Python/ceval.c

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -457,26 +457,34 @@ PyObject*
457457
_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type,
458458
Py_ssize_t nargs, PyObject *kwargs)
459459
{
460-
if (!PyType_Check(type) && !_PyUnion_Check(type)) {
461-
const char *e = "called match pattern must be a class or a union";
462-
_PyErr_Format(tstate, PyExc_TypeError, e);
460+
// Recurse on unions.
461+
if (_PyUnion_Check(type)) {
462+
// get union members
463+
PyObject *members = _Py_union_args(type);
464+
Py_ssize_t n = PyTuple_GET_SIZE(members);
465+
466+
// iterate over union members and return first match
467+
for (Py_ssize_t i = 0; i < n; i++) {
468+
PyObject *member = PyTuple_GET_ITEM(members, i);
469+
PyObject *attrs = _PyEval_MatchClass(tstate, subject, member, nargs, kwargs);
470+
// match found
471+
if (attrs != NULL) {
472+
return attrs;
473+
}
474+
}
475+
// no match found
476+
return NULL;
477+
}
478+
if (!PyType_Check(type)) {
479+
const char *e = "called match pattern must be a class or a union, (got %s, %s)";
480+
_PyErr_Format(tstate, PyExc_TypeError, e, Py_TYPE(type)->tp_name, subject->ob_type->tp_name);
463481
return NULL;
464482
}
465483
assert(PyTuple_CheckExact(kwargs));
466484
// First, an isinstance check:
467485
if (PyObject_IsInstance(subject, type) <= 0) {
468486
return NULL;
469487
}
470-
// Subpatterns are not supported for union types:
471-
if (_PyUnion_Check(type)) {
472-
// Return error if any positional or keyword arguments are given:
473-
if (nargs || PyTuple_GET_SIZE(kwargs)) {
474-
const char *e = "union types do not support sub-patterns";
475-
_PyErr_Format(tstate, PyExc_TypeError, e);
476-
return NULL;
477-
}
478-
return PyTuple_New(0);
479-
}
480488
// So far so good:
481489
PyObject *seen = PySet_New(NULL);
482490
if (seen == NULL) {

0 commit comments

Comments
 (0)