Skip to content

gh-106246: Allow the use of unions as match patterns #118525

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
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
34 changes: 34 additions & 0 deletions Lib/test/test_patma.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import dataclasses
import enum
import inspect
from re import I
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accidental?

import sys
import unittest

Expand Down Expand Up @@ -2886,6 +2887,14 @@ class B(A): ...
h = 1
self.assertEqual(h, 1)

def test_patma_union_type(self):
IntOrStr = int | str
x = 0
match x:
case IntOrStr():
x = 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a test that involves a union that does not match.

self.assertEqual(x, 1)


class TestSyntaxErrors(unittest.TestCase):

Expand Down Expand Up @@ -3361,6 +3370,31 @@ class A:
w = 0
self.assertIsNone(w)

def test_union_type_postional_subpattern(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

english is not my mother tongue, but postional must not to be replaced by positional?

IntOrStr = int | str
x = 1
w = None
with self.assertRaises(TypeError):
match x:
case IntOrStr(x):
w = 0
self.assertEqual(x, 1)
self.assertIsNone(w)

def test_union_type_keyword_subpattern(self):
@dataclasses.dataclass
class Point2:
x: int
y: int
EitherPoint = Point | Point2
x = Point(x=1, y=2)
w = None
with self.assertRaises(TypeError):
match x:
case EitherPoint(x=1, y=2):
w = 0
self.assertIsNone(w)


class TestValueErrors(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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.
15 changes: 13 additions & 2 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "pycore_sysmodule.h" // _PySys_Audit()
#include "pycore_tuple.h" // _PyTuple_ITEMS()
#include "pycore_typeobject.h" // _PySuper_Lookup()
#include "pycore_unionobject.h" // _PyUnion_Check()
#include "pycore_uop_ids.h" // Uops
#include "pycore_pyerrors.h"

Expand Down Expand Up @@ -460,8 +461,8 @@ PyObject*
_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type,
Py_ssize_t nargs, PyObject *kwargs)
{
if (!PyType_Check(type)) {
const char *e = "called match pattern must be a class";
if (!PyType_Check(type) && !_PyUnion_Check(type)) {
const char *e = "called match pattern must be a class or a union";
_PyErr_Format(tstate, PyExc_TypeError, e);
return NULL;
}
Expand All @@ -470,6 +471,16 @@ _PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type,
if (PyObject_IsInstance(subject, type) <= 0) {
return NULL;
}
// Subpatterns are not supported for union types:
if (_PyUnion_Check(type)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the fact that we call _PyUnion_Check(type) twice for no real reason.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we save the result in a variable? Or would it cleaner to handle unions first?

// Return error if any positional or keyword arguments are given:
if (nargs || PyTuple_GET_SIZE(kwargs)) {
const char *e = "union types do not support sub-patterns";
_PyErr_Format(tstate, PyExc_TypeError, e);
return NULL;
}
return PyTuple_New(0);
}
// So far so good:
PyObject *seen = PySet_New(NULL);
if (seen == NULL) {
Expand Down