From b6a9784d8b61b4167290eb015693a13f7f8830c3 Mon Sep 17 00:00:00 2001 From: Thomas M Kehrenberg Date: Thu, 2 May 2024 22:10:45 +0200 Subject: [PATCH 1/2] Allow the use of unions as match patterns --- Lib/test/test_patma.py | 34 ++++++++++++++++++++++++++++++++++ Python/ceval.c | 15 +++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_patma.py b/Lib/test/test_patma.py index 1bdab125dc6ef0..ece0f2847545fa 100644 --- a/Lib/test/test_patma.py +++ b/Lib/test/test_patma.py @@ -3,6 +3,7 @@ import dataclasses import enum import inspect +from re import I import sys import unittest @@ -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 + self.assertEqual(x, 1) + class TestSyntaxErrors(unittest.TestCase): @@ -3361,6 +3370,31 @@ class A: w = 0 self.assertIsNone(w) + def test_union_type_postional_subpattern(self): + 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): diff --git a/Python/ceval.c b/Python/ceval.c index 59498bc826e941..a33a3798f0ca80 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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" @@ -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; } @@ -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)) { + // 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) { From 47071f5719d74f4a3312db6c6a90f09722eb1f76 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 20:16:13 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2024-05-02-20-16-12.gh-issue-118524.B4rIYi.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-05-02-20-16-12.gh-issue-118524.B4rIYi.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-05-02-20-16-12.gh-issue-118524.B4rIYi.rst b/Misc/NEWS.d/next/Core and Builtins/2024-05-02-20-16-12.gh-issue-118524.B4rIYi.rst new file mode 100644 index 00000000000000..0986d8c3120440 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-05-02-20-16-12.gh-issue-118524.B4rIYi.rst @@ -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.