summaryrefslogtreecommitdiff
path: root/Cython/Utility/MatchCase.c
diff options
context:
space:
mode:
Diffstat (limited to 'Cython/Utility/MatchCase.c')
-rw-r--r--Cython/Utility/MatchCase.c907
1 files changed, 907 insertions, 0 deletions
diff --git a/Cython/Utility/MatchCase.c b/Cython/Utility/MatchCase.c
new file mode 100644
index 000000000..55c8b99b4
--- /dev/null
+++ b/Cython/Utility/MatchCase.c
@@ -0,0 +1,907 @@
+///////////////////////////// ABCCheck //////////////////////////////
+
+#if PY_VERSION_HEX < 0x030A0000
+static CYTHON_INLINE int __Pyx_MatchCase_IsExactSequence(PyObject *o) {
+ // is one of the small list of builtin types known to be a sequence
+ if (PyList_CheckExact(o) || PyTuple_CheckExact(o) ||
+ PyType_CheckExact(o, PyRange_Type) || PyType_CheckExact(o, PyMemoryView_Type)) {
+ // Use exact type match for these checks. I in the event of inheritence we need to make sure
+ // that it isn't a mapping too
+ return 1;
+ }
+ return 0;
+}
+
+static CYTHON_INLINE int __Pyx_MatchCase_IsExactMapping(PyObject *o) {
+ // Py_Dict is the only regularly used mapping type
+ // "types.MappingProxyType" also exists but is correctly covered by
+ // the isinstance(o, Mapping) check
+ return PyDict_CheckExact(o);
+}
+
+static int __Pyx_MatchCase_IsExactNeitherSequenceNorMapping(PyObject *o) {
+ if (PyType_GetFlags(Py_TYPE(o)) & (Py_TPFLAGS_BYTES_SUBCLASS | Py_TPFLAGS_UNICODE_SUBCLASS)) ||
+ PyByteArray_Check(o)) {
+ return 1; // these types are deliberately excluded from the sequence test
+ // even though they look like sequences for most other purposes.
+ // Leave them as inexact checks since they do pass
+ // "isinstance(o, collections.abc.Sequence)" so it's very hard to
+ // reason about their subclasses
+ }
+ if (o == Py_None || PyLong_CheckExact(o) || PyFloat_CheckExact(o)) {
+ return 1;
+ }
+ #if PY_MAJOR_VERSION < 3
+ if (PyInt_CheckExact(o)) {
+ return 1;
+ }
+ #endif
+
+ return 0;
+}
+
+// sequence_mapping_temp: For Python 3.10 testing sequences and mappings are
+// really quick and this is ignored. For lower versions of Python they're
+// slow, especially in the "fail" case.
+// Therefore, we store an int temp to avoid duplicating tests.
+// The bits of it in order are:
+// 0. definitely a sequence
+// 1. definitely a mapping
+// - note that both of the above and be true when
+// the type is registered with both abc types (not via inheritance)
+// and in this case we return true for both IsSequence or IsMapping
+// (which seems like the best handling of an ambiguous situation)
+// 2. definitely not a sequence
+// 3. definitely not a mapping
+
+#if PY_VERSION_HEX < 0x030A0000
+#define __PYX_DEFINITELY_SEQUENCE_FLAG 1U
+#define __PYX_DEFINITELY_MAPPING_FLAG (1U<<1)
+#define __PYX_DEFINITELY_NOT_SEQUENCE_FLAG (1U<<2)
+#define __PYX_DEFINITELY_NOT_MAPPING_FLAG (1U<<3)
+#define __PYX_SEQUENCE_MAPPING_ERROR (1U<<4) // only used by the ABCCheck function
+#endif
+
+static int __Pyx_MatchCase_InitAndIsInstanceAbc(PyObject *o, PyObject *abc_module,
+ PyObject **abc_type, PyObject *name) {
+ assert(!abc_type);
+ abc_type = PyObject_GetAttr(abc_module, name);
+ if (!abc_type) {
+ return -1;
+ }
+ return PyObject_IsInstance(o, abc_type);
+}
+
+// the result is defined using the specification for sequence_mapping_temp
+// (detailed in "is_sequence")
+static unsigned int __Pyx_MatchCase_ABCCheck(PyObject *o, int sequence_first, int definitely_not_sequence, int definitely_not_mapping) {
+ // in Python 3.10 objects can have their sequence bit set or their mapping bit set
+ // but not both. Practically this translates to "which type is registered first".
+ // In Python < 3.10 we can only determine this if they're direct bases (by looking
+ // at the MRO order). If they're registered manually then we can't tell
+
+ PyObject *abc_module=NULL, *sequence_type=NULL, *mapping_type=NULL;
+ PyObject *mro;
+ int sequence_result=0, mapping_result=0;
+ unsigned int result = 0;
+
+ abc_module = PyImport_ImportModule(
+#if PY_VERSION_HEX > 0x03030000
+ "collections.abc"
+#else
+ "collections"
+#endif
+ );
+ if (!abc_module) {
+ return __PYX_SEQUENCE_MAPPING_ERROR;
+ }
+ if (sequence_first) {
+ if (definitely_not_sequence) {
+ result = __PYX_DEFINITELY_SEQUENCE_FLAG;
+ goto end;
+ }
+ sequence_result = __Pyx_MatchCase_InitAndIsInstanceAbc(o, abc_module, &sequence_type, PYIDENT("Sequence"));
+ if (sequence_result < 0) {
+ result = __PYX_SEQUENCE_MAPPING_ERROR;
+ goto end;
+ } else if (sequence_result == 0) {
+ result |= __PYX_DEFINITELY_NOT_SEQUENCE_FLAG;
+ goto end;
+ }
+ // else wait to see what mapping is
+ }
+ if (!definitely_not_mapping) {
+ mapping_result = __Pyx_MatchCase_InitAndIsInstanceAbc(o, abc_module, &mapping_type, PYIDENT("Mapping"));
+ if (mapping_result < 0) {
+ result = __PYX_SEQUENCE_MAPPING_ERROR;
+ goto end;
+ } else if (mapping_result == 0) {
+ result |= __PYX_DEFINITELY_NOT_MAPPING_FLAG;
+ if (sequence_first) {
+ assert(sequence_result);
+ result |= __PYX_DEFINITELY_SEQUENCE_FLAG;
+ }
+ goto end;
+ } else /* mapping_result == 1 */ {
+ if (sequence_first && !sequence_result) {
+ result |= __PYX_DEFINITELY_MAPPING_FLAG;
+ goto end;
+ }
+ }
+ }
+ if (!sequence_first) {
+ // here we know mapping_result is true because we'd have returned otherwise
+ assert(mapping_result);
+ if (!definitely_not_sequence) {
+ sequence_result = __Pyx_MatchCase_InitAndIsInstanceAbc(o, abc_module, &sequence_type, PYIDENT("Sequence"));
+ }
+ if (sequence_result < 0) {
+ result = __PYX_SEQUENCE_MAPPING_ERROR;
+ goto end;
+ } else if (sequence_result == 0) {
+ result |= (__PYX_DEFINITELY_NOT_SEQUENCE_FLAG | __PYX_DEFINITELY_MAPPING_FLAG);
+ goto end;
+ } /* else sequence_result == 1, continue to check both */
+ }
+
+ // It's an instance of both types. Look up the MRO order.
+ // In event of failure treat it as "could be either"
+ result = __PYX_DEFINITELY_SEQUENCE_FLAG | __PYX_DEFINITELY_MAPPING_FLAG;
+ mro = PyObject_GetAttrString((PyObject*)Py_TYPE(o), "__mro__");
+ Py_ssize_t i;
+ if (!mro) {
+ PyErr_Clear();
+ goto end;
+ }
+ if (!PyTuple_Check(mro)) {
+ Py_DECREF(mro);
+ goto end;
+ }
+ for (i=1; i < PyTuple_GET_SIZE(mro); ++i) {
+ int is_subclass_sequence, is_subclass_mapping;
+ PyObject *mro_item = PyTuple_GET_ITEM(mro, i);
+ is_subclass_sequence = PyObject_IsSubclass(mro_item, sequence_type);
+ if (is_subclass_sequence < 0) goto loop_error;
+ is_subclass_mapping = PyObject_IsSubclass(mro_item, mapping_type);
+ if (is_subclass_mapping < 0) goto loop_error;
+ if (is_subclass_sequence && !is_subclass_mapping) {
+ result = (__PYX_DEFINITELY_SEQUENCE_FLAG | __PYX_DEFINITELY_NOT_MAPPING_FLAG);
+ break;
+ } else if (is_subclass_mapping && !is_subclass_sequence) {
+ result = (__PYX_DEFINITELY_NOT_SEQUENCE_FLAG | __PYX_DEFINITELY_MAPPING_FLAG);
+ break;
+ }
+ }
+ // If we get to the end of the loop without breaking then neither type is in
+ // the MRO, so they've both been registered manually. We don't know which was
+ // registered first so accept the object as either as a compromise
+ if (0) {
+ loop_error:
+ PyErr_Clear();
+ }
+ Py_DECREF(mro);
+
+ end:
+ Py_XDECREF(abc_module);
+ Py_XDECREF(sequence_type);
+ Py_XDECREF(mapping_type);
+ return result;
+}
+#endif
+
+///////////////////////////// IsSequence.proto //////////////////////
+
+static int __Pyx_MatchCase_IsSequence(PyObject *o, unsigned int *sequence_mapping_temp); /* proto */
+
+//////////////////////////// IsSequence /////////////////////////
+//@requires: ABCCheck
+
+static int __Pyx_MatchCase_IsSequence(PyObject *o, unsigned int *sequence_mapping_temp) {
+#if PY_VERSION_HEX >= 0x030A0000
+ return __Pyx_PyType_HasFeature(Py_TYPE(o), Py_TPFLAGS_SEQUENCE);
+#else
+ // Py_TPFLAGS_SEQUENCE doesn't exit.
+ PyObject *o_module_name;
+ unsigned int abc_result, dummy=0;
+
+ if (sequence_mapping_temp) {
+ // maybe we already know the answer
+ if (*sequence_mapping_temp & __PYX_DEFINITELY_SEQUENCE_FLAG) {
+ return 1;
+ }
+ if (*sequence_mapping_temp & __PYX_DEFINITELY_NOT_SEQUENCE_FLAG) {
+ return 0;
+ }
+ } else {
+ // Probably quicker to just assign it and not check from here
+ sequence_mapping_temp = &dummy;
+ }
+
+ // Start by check a known list of types
+ if (__Pyx_MatchCase_IsExactSequence(o)) {
+ *sequence_mapping_temp |= (__PYX_DEFINITELY_SEQUENCE_FLAG | __PYX_DEFINITELY_NOT_MAPPING_FLAG);
+ return 1;
+ }
+ if (__Pyx_MatchCase_IsExactMapping(o)) {
+ *sequence_mapping_temp |= (__PYX_DEFINITELY_MAPPING_FLAG | __PYX_DEFINITELY_NOT_SEQUENCE_FLAG);
+ return 0;
+ }
+ if (__Pyx_MatchCase_IsExactNeitherSequenceNorMapping(o)) {
+ *sequence_mapping_temp |= (__PYX_DEFINITELY_NOT_SEQUENCE_FLAG | __PYX_DEFINITELY_NOT_MAPPING_FLAG);
+ return 0;
+ }
+
+ abc_result = __Pyx_MatchCase_ABCCheck(
+ o, 1,
+ *sequence_mapping_temp & __PYX_DEFINITELY_NOT_SEQUENCE_FLAG,
+ *sequence_mapping_temp & __PYX_DEFINITELY_NOT_MAPPING_FLAG
+ );
+ if (abc_result & __PYX_SEQUENCE_MAPPING_ERROR) {
+ return -1;
+ }
+ *sequence_mapping_temp = abc_result;
+ if (*sequence_mapping_temp & __PYX_DEFINITELY_SEQUENCE_FLAG) {
+ return 1;
+ }
+
+ // array.array is a more complicated check (and unfortunately isn't covered by
+ // collections.abc.Sequence on Python <3.10).
+ // Do the test by checking the module name, and then importing/testing the class
+ // It also doesn't give perfect results for classes that inherit from both array.array
+ // and a mapping
+ o_module_name = PyObject_GetAttrString((PyObject*)Py_TYPE(o), "__module__");
+ if (!o_module_name) {
+ return -1;
+ }
+#if PY_MAJOR_VERSION >= 3
+ if (PyUnicode_Check(o_module_name) && PyUnicode_CompareWithASCIIString(o_module_name, "array") == 0)
+#else
+ if (PyBytes_Check(o_module_name) && PyBytes_AS_STRING(o_module_name)[0] == 'a' &&
+ PyBytes_AS_STRING(o_module_name)[1] == 'r' && PyBytes_AS_STRING(o_module_name)[2] == 'r' &&
+ PyBytes_AS_STRING(o_module_name)[3] == 'a' && PyBytes_AS_STRING(o_module_name)[4] == 'y' &&
+ PyBytes_AS_STRING(o_module_name)[5] == '\0')
+#endif
+ {
+ int is_array;
+ PyObject *array_module, *array_object;
+ Py_DECREF(o_module_name);
+ array_module = PyImport_ImportModule("array");
+ if (!array_module) {
+ PyErr_Clear();
+ return 0; // treat these tests as "soft" and don't cause an exception
+ }
+ array_object = PyObject_GetAttrString(array_module, "array");
+ Py_DECREF(array_module);
+ if (!array_object) {
+ PyErr_Clear();
+ return 0;
+ }
+ is_array = PyObject_IsInstance(o, array_object);
+ Py_DECREF(array_object);
+ if (is_array) {
+ *sequence_mapping_temp |= __PYX_DEFINITELY_SEQUENCE_FLAG;
+ return 1;
+ }
+ PyErr_Clear();
+ } else {
+ Py_DECREF(o_module_name);
+ }
+ *sequence_mapping_temp |= __PYX_DEFINITELY_NOT_SEQUENCE_FLAG;
+ return 0;
+#endif
+}
+
+////////////////////// OtherSequenceSliceToList.proto //////////////////////
+
+static PyObject *__Pyx_MatchCase_OtherSequenceSliceToList(PyObject *x, Py_ssize_t start, Py_ssize_t end); /* proto */
+
+////////////////////// OtherSequenceSliceToList //////////////////////////
+
+// This is substantially based off ceval unpack_iterable.
+// It's also pretty similar to itertools.islice
+// Indices must be postive - there's no wraparound or boundschecking
+
+static PyObject *__Pyx_MatchCase_OtherSequenceSliceToList(PyObject *x, Py_ssize_t start, Py_ssize_t end) {
+ int total = end-start;
+ int i;
+ PyObject *list;
+ ssizeargfunc slot;
+ PyTypeObject *type = Py_TYPE(x);
+
+ list = PyList_New(total);
+ if (!list) {
+ return NULL;
+ }
+
+#if CYTHON_USE_TYPE_SLOTS || PY_MAJOR_VERSION < 3 || CYTHON_COMPILING_IN_PYPY
+ slot = type->tp_as_sequence ? type->tp_as_sequence->sq_item : NULL;
+#else
+ if ((PY_VERSION_HEX >= 0x030A0000) || __Pyx_PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
+ // PyType_GetSlot only works on heap types in Python <3.10
+ slot = (ssizeargfunc) PyType_GetSlot(type, Py_sq_item);
+ }
+#endif
+ if (!slot) {
+ #if !defined(Py_LIMITED_API) && !defined(PySequence_ITEM)
+ // PyPy (and maybe others?) implements PySequence_ITEM as a function. In this case
+ // it's slightly more efficient than using PySequence_GetItem since it skips negative indices
+ slot = PySequence_ITEM;
+ #else
+ slot = PySequence_GetItem;
+ #endif
+ }
+
+ for (i=start; i<end; ++i) {
+ PyObject *obj = slot(x, i);
+ if (!obj) {
+ Py_DECREF(list);
+ return NULL;
+ }
+ PyList_SET_ITEM(list, i-start, obj);
+ }
+ return list;
+}
+
+////////////////////// TupleSliceToList.proto //////////////////////
+
+static PyObject *__Pyx_MatchCase_TupleSliceToList(PyObject *x, Py_ssize_t start, Py_ssize_t end); /* proto */
+
+////////////////////// TupleSliceToList //////////////////////////
+//@requires: OtherSequenceSliceToList
+//@requires: ObjectHandling.c::TupleAndListFromArray
+
+// Note that this should also work fine on lists (if needed)
+// Indices must be postive - there's no wraparound or boundschecking
+
+static PyObject *__Pyx_MatchCase_TupleSliceToList(PyObject *x, Py_ssize_t start, Py_ssize_t end) {
+#if !CYTHON_COMPILING_IN_CPYTHON
+ return __Pyx_MatchCase_OtherSequenceSliceToList(x, start, end);
+#else
+ PyObject **array;
+
+ (void)__Pyx_MatchCase_OtherSequenceSliceToList; // clear unused warning
+
+ array = PySequence_Fast_ITEMS(x);
+ return __Pyx_PyList_FromArray(array+start, end-start);
+#endif
+}
+
+////////////////////////// UnknownTypeSliceToList.proto //////////////////////
+
+static PyObject *__Pyx_MatchCase_UnknownTypeSliceToList(PyObject *x, Py_ssize_t start, Py_ssize_t end); /* proto */
+
+////////////////////////// UnknownTypeSliceToList.proto //////////////////////
+//@requires: TupleSliceToList
+//@requires: OtherSequenceSliceToList
+
+static PyObject *__Pyx_MatchCase_UnknownTypeSliceToList(PyObject *x, Py_ssize_t start, Py_ssize_t end) {
+ if (PyList_CheckExact(x)) {
+ return PyList_GetSlice(x, start, end);
+ }
+#if !CYTHON_COMPILING_IN_CPYTHON
+ // since __Pyx_MatchCase_TupleToList only does anything special in CPython, skip the check otherwise
+ if (PyTuple_CheckExact(x)) {
+ return __Pyx_MatchCase_TupleSliceToList(x, start, end);
+ }
+#else
+ (void)__Pyx_MatchCase_TupleSliceToList;
+#endif
+ return __Pyx_MatchCase_OtherSequenceSliceToList(x, start, end);
+}
+
+///////////////////////////// IsMapping.proto //////////////////////
+
+static int __Pyx_MatchCase_IsMapping(PyObject *o, unsigned int *sequence_mapping_temp); /* proto */
+
+//////////////////////////// IsMapping /////////////////////////
+//@requires: ABCCheck
+
+static int __Pyx_MatchCase_IsMapping(PyObject *o, unsigned int *sequence_mapping_temp) {
+#if PY_VERSION_HEX >= 0x030A0000
+ return __Pyx_PyType_HasFeature(Py_TYPE(o), Py_TPFLAGS_MAPPING);
+#else
+ unsigned int abc_result, dummy=0;
+ if (sequence_mapping_temp) {
+ // do we already know the answer?
+ if (*sequence_mapping_temp & __PYX_DEFINITELY_MAPPING_FLAG) {
+ return 1;
+ } else if (*sequence_mapping_temp & __PYX_DEFINITELY_NOT_MAPPING_FLAG) {
+ return 0;
+ }
+ } else {
+ sequence_mapping_temp = &dummy; // just so we can assign freely without checking
+ }
+
+ if (__Pyx_MatchCase_IsExactMapping(o)) {
+ *sequence_mapping_temp |= (__PYX_DEFINITELY_MAPPING_FLAG | __PYX_DEFINITELY_NOT_SEQUENCE_FLAG);
+ return 1;
+ }
+ if (__Pyx_MatchCase_IsExactSequence(o)) {
+ *sequence_mapping_temp |= (__PYX_DEFINITELY_SEQUENCE_FLAG | __PYX_DEFINITELY_NOT_MAPPING_FLAG);
+ return 0;
+ }
+ if (__Pyx_MatchCase_IsExactNeitherSequenceNorMapping(o)) {
+ *sequence_mapping_temp |= (__PYX_DEFINITELY_NOT_SEQUENCE_FLAG | __PYX_DEFINITELY_NOT_MAPPING_FLAG);
+ return 0;
+ }
+
+ // otherwise check against collections.abc.Mapping
+ abc_result = __Pyx_MatchCase_ABCCheck(
+ o, 0,
+ *sequence_mapping_temp & __PYX_DEFINITELY_NOT_SEQUENCE_FLAG,
+ *sequence_mapping_temp & __PYX_DEFINITELY_NOT_MAPPING_FLAG
+ );
+ if (abc_result & __PYX_SEQUENCE_MAPPING_ERROR) {
+ return -1;
+ }
+ *sequence_mapping_temp = abc_result;
+ return *sequence_mapping_temp & __PYX_DEFINITELY_MAPPING_FLAG;
+#endif
+}
+
+//////////////////////// MappingKeyCheck.proto /////////////////////////
+
+static int __Pyx_MatchCase_CheckMappingDuplicateKeys(PyObject *keys[], Py_ssize_t nFixedKeys, Py_ssize_t nKeys);
+
+//////////////////////// MappingKeyCheck ///////////////////////////////
+
+static int __Pyx_MatchCase_CheckMappingDuplicateKeys(PyObject *keys[], Py_ssize_t nFixedKeys, Py_ssize_t nKeys) {
+ // Inputs are arrays, and typically fairly small. It may be more efficient to
+ // loop over the array than create a set.
+
+ // The CPython implementation (match_keys in ceval.c) does this concurrently with
+ // taking the keys out of the dictionary. I'm choosing to do it separately since the
+ // majority of the time the keys will be known at compile-time so Cython can skip
+ // this step completely.
+
+ PyObject *var_keys_set;
+ PyObject *key;
+ Py_ssize_t n;
+ int contains;
+
+ var_keys_set = PySet_New(NULL);
+ if (!var_keys_set) return -1;
+
+ for (n=nFixedKeys; n < nKeys; ++n) {
+ key = keys[n];
+ contains = PySet_Contains(var_keys_set, key);
+ if (contains < 0) {
+ goto bad;
+ } else if (contains == 1) {
+ goto raise_error;
+ } else {
+ if (PySet_Add(var_keys_set, key)) {
+ goto bad;
+ }
+ }
+ }
+ for (n=0; n < nFixedKeys; ++n) {
+ key = keys[n];
+ contains = PySet_Contains(var_keys_set, key);
+ if (contains < 0) {
+ goto bad;
+ } else if (contains == 1) {
+ goto raise_error;
+ }
+ }
+ Py_DECREF(var_keys_set);
+ return 0;
+
+ raise_error:
+ #if PY_MAJOR_VERSION > 2
+ PyErr_Format(PyExc_ValueError,
+ "mapping pattern checks duplicate key (%R)", key);
+ #else
+ // DW really can't be bothered working around features that don't exist in
+ // Python 2, so just provide less information!
+ PyErr_SetString(PyExc_ValueError,
+ "mapping pattern checks duplicate key");
+ #endif
+ bad:
+ Py_DECREF(var_keys_set);
+ return -1;
+}
+
+/////////////////////////// ExtractExactDict.proto ////////////////
+
+// the variadic arguments are a list of PyObject** to subjects to be filled. They may be NULL
+// in which case they're ignored.
+//
+// This is a specialized version for when we have an exact dict (which is likely to be pretty common)
+
+#if CYTHON_REFNANNY
+#define __Pyx_MatchCase_Mapping_ExtractDict(...) __Pyx__MatchCase_Mapping_ExtractDict(__pyx_refnanny, __VA_ARGS__)
+#else
+#define __Pyx_MatchCase_Mapping_ExtractDict(...) __Pyx__MatchCase_Mapping_ExtractDict(NULL, __VA_ARGS__)
+#endif
+static CYTHON_INLINE int __Pyx__MatchCase_Mapping_ExtractDict(void *__pyx_refnanny, PyObject *dict, PyObject *keys[], Py_ssize_t nKeys, PyObject **subjects[]); /* proto */
+
+/////////////////////////// ExtractExactDict ////////////////
+
+static CYTHON_INLINE int __Pyx__MatchCase_Mapping_ExtractDict(void *__pyx_refnanny, PyObject *dict, PyObject *keys[], Py_ssize_t nKeys, PyObject **subjects[]) {
+ Py_ssize_t i;
+
+ for (i=0; i<nKeys; ++i) {
+ PyObject *key = keys[i];
+ PyObject **subject = subjects[i];
+ if (!subject) {
+ int contains = PyDict_Contains(dict, key);
+ if (contains <= 0) {
+ return -1; // any subjects that were already set will be cleaned up externally
+ }
+ } else {
+ PyObject *value = __Pyx_PyDict_GetItemStrWithError(dict, key);
+ if (!value) {
+ return (PyErr_Occurred()) ? -1 : 0; // any subjects that were already set will be cleaned up externally
+ }
+ __Pyx_XDECREF_SET(*subject, value);
+ __Pyx_INCREF(*subject); // capture this incref with refnanny!
+ }
+ }
+ return 1; // success
+}
+
+///////////////////////// ExtractNonDict.proto ////////////////////////////////
+
+// the variadic arguments are a list of PyObject** to subjects to be filled. They may be NULL
+// in which case they're ignored.
+//
+// This is a specialized version for the rarer case when the type isn't an exact dict.
+
+#if CYTHON_REFNANNY
+#define __Pyx_MatchCase_Mapping_ExtractNonDict(...) __Pyx__MatchCase_Mapping_ExtractNonDict(__pyx_refnanny, __VA_ARGS__)
+#else
+#define __Pyx_MatchCase_Mapping_ExtractNonDict(...) __Pyx__MatchCase_Mapping_ExtractNonDict(NULL, __VA_ARGS__)
+#endif
+static CYTHON_INLINE int __Pyx__MatchCase_Mapping_ExtractNonDict(void *__pyx_refnanny, PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys, PyObject **subjects[]); /* proto */
+
+///////////////////////// ExtractNonDict //////////////////////////////////////
+//@requires: ObjectHandling.c::PyObjectCall2Args
+
+// largely adapted from match_keys in CPython ceval.c
+
+static int __Pyx__MatchCase_Mapping_ExtractNonDict(void *__pyx_refnanny, PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys, PyObject **subjects[]) {
+ PyObject *dummy=NULL, *get=NULL;
+ Py_ssize_t i;
+ int result = 0;
+#if CYTHON_UNPACK_METHODS && CYTHON_VECTORCALL
+ PyObject *get_method = NULL, *get_self = NULL;
+#endif
+
+ dummy = PyObject_CallObject((PyObject *)&PyBaseObject_Type, NULL);
+ if (!dummy) {
+ return -1;
+ }
+ get = PyObject_GetAttrString(mapping, "get");
+ if (!get) {
+ result = -1;
+ goto end;
+ }
+#if CYTHON_UNPACK_METHODS && CYTHON_VECTORCALL
+ if (likely(PyMethod_Check(get))) {
+ // both of these are borrowed
+ get_method = PyMethod_GET_FUNCTION(get);
+ get_self = PyMethod_GET_SELF(get);
+ }
+#endif
+
+ for (i=0; i<nKeys; ++i) {
+ PyObject **subject;
+ PyObject *value = NULL;
+ PyObject *key = keys[i];
+
+ // TODO - there's an optimization here (although it deviates from the strict definition of pattern matching).
+ // If we don't need the values then we can call PyObject_Contains instead of "get". If we don't need *any*
+ // of the values then we can skip initialization "get" and "dummy"
+#if CYTHON_UNPACK_METHODS && CYTHON_VECTORCALL
+ if (likely(get_method)) {
+ PyObject *args[] = { get_self, key, dummy };
+ value = _PyObject_Vectorcall(get_method, args, 3, NULL);
+ }
+ else
+#endif
+ {
+ value = __Pyx_PyObject_Call2Args(get, key, dummy);
+ }
+ if (!value) {
+ result = -1;
+ goto end;
+ } else if (value == dummy) {
+ Py_DECREF(value);
+ goto end; // failed
+ } else {
+ subject = subjects[i];
+ if (subject) {
+ __Pyx_XDECREF_SET(*subject, value);
+ __Pyx_GOTREF(*subject);
+ } else {
+ Py_DECREF(value);
+ }
+ }
+ }
+ result = 1;
+
+ end:
+ Py_XDECREF(dummy);
+ Py_XDECREF(get);
+ return result;
+}
+
+///////////////////////// ExtractGeneric.proto ////////////////////////////////
+
+#if CYTHON_REFNANNY
+#define __Pyx_MatchCase_Mapping_Extract(...) __Pyx__MatchCase_Mapping_Extract(__pyx_refnanny, __VA_ARGS__)
+#else
+#define __Pyx_MatchCase_Mapping_Extract(...) __Pyx__MatchCase_Mapping_Extract(NULL, __VA_ARGS__)
+#endif
+static CYTHON_INLINE int __Pyx__MatchCase_Mapping_Extract(void *__pyx_refnanny, PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys, PyObject **subjects[]); /* proto */
+
+////////////////////// ExtractGeneric //////////////////////////////////////
+//@requires: ExtractExactDict
+//@requires: ExtractNonDict
+
+static CYTHON_INLINE int __Pyx__MatchCase_Mapping_Extract(void *__pyx_refnanny, PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys, PyObject **subjects[]) {
+ if (PyDict_CheckExact(mapping)) {
+ return __Pyx_MatchCase_Mapping_ExtractDict(mapping, keys, nKeys, subjects);
+ } else {
+ return __Pyx_MatchCase_Mapping_ExtractNonDict(mapping, keys, nKeys, subjects);
+ }
+}
+
+///////////////////////////// DoubleStarCapture.proto //////////////////////
+
+static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys); /* proto */
+
+//////////////////////////// DoubleStarCapture //////////////////////////////
+
+// The implementation is largely copied from the original COPY_DICT_WITHOUT_KEYS opcode
+// implementation of CPython
+// https://github.com/python/cpython/blob/145bf269df3530176f6ebeab1324890ef7070bf8/Python/ceval.c#L3977
+// (now removed in favour of building the same thing from a combination of opcodes)
+// The differences are:
+// 1. We use an array of keys rather than a tuple of keys
+// 2. We add a shortcut for when there will be no left over keys (because I guess it's pretty common)
+//
+// Tempita variable 'tag' can be "NonDict", "ExactDict" or empty
+
+static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys) {
+ PyObject *dict_out;
+ Py_ssize_t i;
+
+ {{if tag != "NonDict"}}
+ // shortcut for when there are no left-over keys
+ if ({{if tag=="ExactDict"}}(1){{else}}PyDict_CheckExact(mapping){{endif}}) {
+ Py_ssize_t s = PyDict_Size(mapping);
+ if (s == -1) {
+ return NULL;
+ }
+ if (s == nKeys) {
+ return PyDict_New();
+ }
+ }
+ {{endif}}
+
+ {{if tag=="ExactDict"}}
+ dict_out = PyDict_Copy(mapping);
+ {{else}}
+ dict_out = PyDict_New();
+ {{endif}}
+ if (!dict_out) {
+ return NULL;
+ }
+ {{if tag!="ExactDict"}}
+ if (PyDict_Update(dict_out, mapping)) {
+ Py_DECREF(dict_out);
+ return NULL;
+ }
+ {{endif}}
+
+ for (i=0; i<nKeys; ++i) {
+ if (PyDict_DelItem(dict_out, keys[i])) {
+ Py_DECREF(dict_out);
+ return NULL;
+ }
+ }
+ return dict_out;
+}
+
+////////////////////////////// ClassPositionalPatterns.proto ////////////////////////
+
+#if CYTHON_REFNANNY
+#define __Pyx_MatchCase_ClassPositional(...) __Pyx__MatchCase_ClassPositional(__pyx_refnanny, __VA_ARGS__)
+#else
+#define __Pyx_MatchCase_ClassPositional(...) __Pyx__MatchCase_ClassPositional(NULL, __VA_ARGS__)
+#endif
+static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subject, PyTypeObject *type, PyObject *fixed_names[], Py_ssize_t n_fixed, int match_self, PyObject **subjects[], Py_ssize_t n_subjects); /* proto */
+
+/////////////////////////////// ClassPositionalPatterns //////////////////////////////
+
+static int __Pyx_MatchCase_ClassCheckDuplicateAttrs(const char *tp_name, PyObject *fixed_names[], Py_ssize_t n_fixed, PyObject *match_args, Py_ssize_t num_args) {
+ // a lot of the basic logic of this is shared with __Pyx_MatchCase_CheckMappingDuplicateKeys
+ // but they take different input types so it isn't easy to actually share the code.
+
+ // Inputs are tuples, and typically fairly small. It may be more efficient to
+ // loop over the tuple than create a set.
+
+ PyObject *attrs_set;
+ PyObject *attr = NULL;
+ Py_ssize_t n;
+ int contains;
+
+ attrs_set = PySet_New(NULL);
+ if (!attrs_set) return -1;
+
+ num_args = PyTuple_GET_SIZE(match_args) < num_args ? PyTuple_GET_SIZE(match_args) : num_args;
+ for (n=0; n < num_args; ++n) {
+ attr = PyTuple_GET_ITEM(match_args, n);
+ contains = PySet_Contains(attrs_set, attr);
+ if (contains < 0) {
+ goto bad;
+ } else if (contains == 1) {
+ goto raise_error;
+ } else {
+ if (PySet_Add(attrs_set, attr)) {
+ goto bad;
+ }
+ }
+ }
+ for (n=0; n < n_fixed; ++n) {
+ attr = fixed_names[n];
+ contains = PySet_Contains(attrs_set, attr);
+ if (contains < 0) {
+ goto bad;
+ } else if (contains == 1) {
+ goto raise_error;
+ }
+ }
+ Py_DECREF(attrs_set);
+ return 0;
+
+ raise_error:
+ #if PY_MAJOR_VERSION > 2
+ PyErr_Format(PyExc_TypeError, "%s() got multiple sub-patterns for attribute %R",
+ tp_name, attr);
+ #else
+ // DW has no interest in working around the lack of %R in Python 2.7
+ PyErr_Format(PyExc_TypeError, "%s() got multiple sub-patterns for attribute",
+ tp_name);
+ #endif
+ bad:
+ Py_DECREF(attrs_set);
+ return -1;
+}
+
+// Adapted from ceval.c "match_class" in CPython
+//
+// The argument match_self can equal 1 for "known to be true"
+// 0 for "known to be false"
+// -1 for "unknown", runtime test
+// nargs is >= 0 otherwise this function will be skipped
+static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subject, PyTypeObject *type, PyObject *fixed_names[], Py_ssize_t n_fixed, int match_self, PyObject **subjects[], Py_ssize_t n_subjects)
+{
+ PyObject *match_args;
+ Py_ssize_t allowed, i;
+ int result;
+
+ match_args = PyObject_GetAttrString((PyObject*)type, "__match_args__");
+ if (!match_args) {
+ if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ PyErr_Clear();
+
+ if (match_self == -1) {
+ #if defined(_Py_TPFLAGS_MATCH_SELF)
+ match_self = PyType_HasFeature(type,
+ _Py_TPFLAGS_MATCH_SELF);
+ #else
+ // probably an earlier version of Python. Go off the known list in the specification
+ match_self = ((PyType_GetFlags(type) &
+ // long should capture bool too
+ (Py_TPFLAGS_LONG_SUBCLASS | Py_TPFLAGS_LIST_SUBCLASS | Py_TPFLAGS_TUPLE_SUBCLASS |
+ Py_TPFLAGS_BYTES_SUBCLASS | Py_TPFLAGS_UNICODE_SUBCLASS | Py_TPFLAGS_DICT_SUBCLASS
+ #if PY_MAJOR_VERSION < 3
+ | Py_TPFLAGS_IN_SUBCLASS
+ #endif
+ )) ||
+ PyType_IsSubtype(type, &PyByteArray_Type) ||
+ PyType_IsSubtype(type, &PyFloat_Type) ||
+ PyType_IsSubtype(type, &PyFrozenSet_Type) ||
+ );
+ #endif
+ }
+ } else {
+ return -1;
+ }
+ } else {
+ match_self = 0;
+ if (!PyTuple_CheckExact(match_args)) {
+ PyErr_Format(PyExc_TypeError, "%s.__match_args__ must be a tuple (got %s)",
+ type->tp_name,
+ Py_TYPE(match_args)->tp_name
+ );
+ Py_DECREF(match_args);
+ return -1;
+ }
+ }
+
+ allowed = match_self ?
+ 1 : (match_args ? PyTuple_GET_SIZE(match_args) : 0);
+ if (allowed < n_subjects) {
+ const char *plural = (allowed == 1) ? "" : "s";
+ PyErr_Format(PyExc_TypeError,
+ "%s() accepts %d positional sub-pattern%s (%d given)",
+ type->tp_name,
+ allowed, plural, n_subjects);
+ Py_XDECREF(match_args);
+ return -1;
+ }
+ if (match_self) {
+ PyObject **self_subject = subjects[0];
+ if (self_subject) {
+ // Easy. Copy the subject itself, and move on to kwargs.
+ __Pyx_XDECREF_SET(*self_subject, subject);
+ __Pyx_INCREF(*self_subject);
+ }
+ result = 1;
+ goto end_match_self;
+ }
+ // next stage is to check for duplicate attributes.
+ if (__Pyx_MatchCase_ClassCheckDuplicateAttrs(type->tp_name, fixed_names, n_fixed, match_args, n_subjects)) {
+ result = -1;
+ goto end;
+ }
+
+ for (i = 0; i < n_subjects; i++) {
+ PyObject *attr;
+ PyObject **subject_i;
+ PyObject *name = PyTuple_GET_ITEM(match_args, i);
+ if (!PyUnicode_CheckExact(name)) {
+ PyErr_Format(PyExc_TypeError,
+ "__match_args__ elements must be strings "
+ "(got %s)", Py_TYPE(name)->tp_name);
+ result = -1;
+ goto end;
+ }
+
+ attr = PyObject_GetAttr(subject, name);
+ if (attr == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ PyErr_Clear();
+ result = 0;
+ goto end;
+ }
+ subject_i = subjects[i];
+ if (subject_i) {
+ __Pyx_XDECREF_SET(*subject_i, attr);
+ __Pyx_GOTREF(attr);
+ } else {
+ Py_DECREF(attr);
+ }
+ }
+ result = 1;
+
+ end:
+ Py_DECREF(match_args);
+ end_match_self: // because match_args isn't set
+ return result;
+}
+
+//////////////////////// MatchClassIsType.proto /////////////////////////////
+
+static PyTypeObject* __Pyx_MatchCase_IsType(PyObject* type); /* proto */
+
+//////////////////////// MatchClassIsType /////////////////////////////
+
+static PyTypeObject* __Pyx_MatchCase_IsType(PyObject* type) {
+ #if PY_MAJOR_VERSION < 3
+ if (PyClass_Check(type)) {
+ // I don't really think it's worth the effort getting this to work!
+ PyErr_Format(PyExc_TypeError, "called match pattern must be a new-style class.");
+ return NULL;
+ }
+ #endif
+ if (!PyType_Check(type)) {
+ PyErr_Format(PyExc_TypeError, "called match pattern must be a type");
+ return NULL;
+ }
+ Py_INCREF(type);
+ return (PyTypeObject*)type;
+}