summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorda-woods <dw-git@d-woods.co.uk>2022-09-17 09:57:53 +0100
committerda-woods <dw-git@d-woods.co.uk>2022-09-17 09:57:53 +0100
commite1f145e5b9c787083a347200a18a5401ad227540 (patch)
treee8e1e63464b5360fd0efa8af6bedb154e09ff03d
parent119d7679be2609fd7cdb8385c914ebb437046a01 (diff)
parenta96578193f3fa9a5e4d2b083d13f5fa8ddcc40a2 (diff)
downloadcython-e1f145e5b9c787083a347200a18a5401ad227540.tar.gz
Merge branch 'match-class' into match-or
-rw-r--r--Cython/Compiler/MatchCaseNodes.py294
-rw-r--r--Cython/Utility/MatchCase.c456
-rw-r--r--tests/run/extra_patma.pyx24
-rw-r--r--tests/run/extra_patma_py.py35
4 files changed, 460 insertions, 349 deletions
diff --git a/Cython/Compiler/MatchCaseNodes.py b/Cython/Compiler/MatchCaseNodes.py
index 833520642..3d0d6890b 100644
--- a/Cython/Compiler/MatchCaseNodes.py
+++ b/Cython/Compiler/MatchCaseNodes.py
@@ -1060,30 +1060,32 @@ class MatchMappingPatternNode(PatternNode):
],
exception_value="-1",
)
+ # lie about the types of keys for simplicity
Pyx_mapping_check_duplicates_type = PyrexTypes.CFuncType(
PyrexTypes.c_int_type,
[
- PyrexTypes.CFuncTypeArg("fixed_keys", PyrexTypes.py_object_type, None),
- PyrexTypes.CFuncTypeArg("var_keys", PyrexTypes.py_object_type, None),
+ PyrexTypes.CFuncTypeArg("keys", PyrexTypes.c_void_ptr_type, None),
+ PyrexTypes.CFuncTypeArg("nKeys", PyrexTypes.c_py_ssize_t_type, None),
],
exception_value="-1",
)
+ # lie about the types of keys and subjects for simplicity
Pyx_mapping_extract_subjects_type = PyrexTypes.CFuncType(
PyrexTypes.c_bint_type,
[
- PyrexTypes.CFuncTypeArg("map", PyrexTypes.py_object_type, None),
- PyrexTypes.CFuncTypeArg("fixed_keys", PyrexTypes.py_object_type, None),
- PyrexTypes.CFuncTypeArg("var_keys", PyrexTypes.py_object_type, None),
+ PyrexTypes.CFuncTypeArg("mapping", PyrexTypes.py_object_type, None),
+ PyrexTypes.CFuncTypeArg("keys", PyrexTypes.c_void_ptr_type, None),
+ PyrexTypes.CFuncTypeArg("nKeys", PyrexTypes.c_py_ssize_t_type, None),
+ PyrexTypes.CFuncTypeArg("subjects", PyrexTypes.c_void_ptr_ptr_type, None),
],
exception_value="-1",
- has_varargs=True,
)
Pyx_mapping_doublestar_type = PyrexTypes.CFuncType(
Builtin.dict_type,
[
- PyrexTypes.CFuncTypeArg("map", PyrexTypes.py_object_type, None),
- PyrexTypes.CFuncTypeArg("fixed_keys", PyrexTypes.py_object_type, None),
- PyrexTypes.CFuncTypeArg("var_keys", PyrexTypes.py_object_type, None),
+ PyrexTypes.CFuncTypeArg("mapping", PyrexTypes.py_object_type, None),
+ PyrexTypes.CFuncTypeArg("keys", PyrexTypes.c_void_ptr_type, None),
+ PyrexTypes.CFuncTypeArg("nKeys", PyrexTypes.c_py_ssize_t_type, None),
],
)
@@ -1203,8 +1205,10 @@ class MatchMappingPatternNode(PatternNode):
self.pos, arg=subject_node, fallback=call, check=self.is_dict_type_check
)
- def make_duplicate_keys_check(self, static_keys_tuple, var_keys_tuple):
+ def make_duplicate_keys_check(self, n_fixed_keys):
utility_code = UtilityCode.load_cached("MappingKeyCheck", "MatchCase.c")
+ if n_fixed_keys == len(self.keys):
+ return None # nothing to check
return Nodes.ExprStatNode(
self.pos,
@@ -1213,11 +1217,15 @@ class MatchMappingPatternNode(PatternNode):
"__Pyx_MatchCase_CheckMappingDuplicateKeys",
self.Pyx_mapping_check_duplicates_type,
utility_code=utility_code,
- args=[static_keys_tuple.clone_node(), var_keys_tuple],
+ args=[
+ MappingOrClassComparisonNode.make_keys_node(self.pos),
+ ExprNodes.IntNode(self.pos, value=str(n_fixed_keys)),
+ ExprNodes.IntNode(self.pos, value=str(len(self.keys)))
+ ],
),
)
- def check_all_keys(self, subject_node, const_keys_tuple, var_keys_tuple):
+ def check_all_keys(self, subject_node):
# It's debatable here whether to go for individual unpacking or a function.
# Current implementation is a function that's loosely copied from CPython.
# For small numbers of keys it might be better to generate the code instead.
@@ -1243,24 +1251,23 @@ class MatchMappingPatternNode(PatternNode):
util_code = UtilityCode.load_cached("ExtractGeneric", "MatchCase.c")
func_name = "__Pyx_MatchCase_Mapping_Extract"
- subject_derefs = [
- ExprNodes.NullNode(self.pos)
- if t is None
- else AddressOfPyObjectNode(self.pos, obj=t)
- for t in self.subject_temps
- ]
return ExprNodes.PythonCapiCallNode(
self.pos,
func_name,
self.Pyx_mapping_extract_subjects_type,
utility_code=util_code,
- args=[subject_node, const_keys_tuple.clone_node(), var_keys_tuple]
- + subject_derefs,
+ args=[
+ subject_node,
+ MappingOrClassComparisonNode.make_keys_node(self.pos),
+ ExprNodes.IntNode(
+ self.pos,
+ value=str(len(self.keys))
+ ),
+ MappingOrClassComparisonNode.make_subjects_node(self.pos),
+ ],
)
- def make_double_star_capture(
- self, subject_node, const_tuple, var_tuple, test_result
- ):
+ def make_double_star_capture(self, subject_node, test_result):
# test_result being the variable that holds "case check passed until now"
is_dict = self.is_dict_type_check(subject_node.type)
if is_dict:
@@ -1277,7 +1284,11 @@ class MatchMappingPatternNode(PatternNode):
"__Pyx_MatchCase_DoubleStarCapture" + tag,
self.Pyx_mapping_doublestar_type,
utility_code=utility_code,
- args=[subject_node, const_tuple, var_tuple],
+ args=[
+ subject_node,
+ MappingOrClassComparisonNode.make_keys_node(self.pos),
+ ExprNodes.IntNode(self.pos, value=str(len(self.keys)))
+ ],
)
assignment = Nodes.SingleAssignmentNode(
self.double_star_capture_target.pos, lhs=self.double_star_temp, rhs=func
@@ -1294,22 +1305,17 @@ class MatchMappingPatternNode(PatternNode):
def get_comparison_node(self, subject_node, sequence_mapping_temp=None):
from . import UtilNodes
- const_keys = []
var_keys = []
+ n_literal_keys = 0
for k in self.keys:
- if not k.arg.is_literal:
- k = UtilNodes.ResultRefNode(k, is_temp=False)
+ if not k.is_literal:
var_keys.append(k)
else:
- const_keys.append(k.arg.clone_node())
- const_keys_tuple = ExprNodes.TupleNode(self.pos, args=const_keys)
- var_keys_tuple = ExprNodes.TupleNode(self.pos, args=var_keys)
- if var_keys:
- var_keys_tuple = UtilNodes.ResultRefNode(var_keys_tuple, is_temp=True)
+ n_literal_keys += 1
all_tests = []
all_tests.append(self.make_mapping_check(subject_node, sequence_mapping_temp))
- all_tests.append(self.check_all_keys(subject_node, const_keys_tuple, var_keys_tuple))
+ all_tests.append(self.check_all_keys(subject_node))
if any(isinstance(test, ExprNodes.BoolNode) and not test.value for test in all_tests):
# identify automatic-failure
@@ -1324,10 +1330,10 @@ class MatchMappingPatternNode(PatternNode):
all_tests = generate_binop_tree_from_list(self.pos, "and", all_tests)
test_result = UtilNodes.ResultRefNode(pos=self.pos, type=PyrexTypes.c_bint_type)
+ duplicate_check = self.make_duplicate_keys_check(n_literal_keys)
body = Nodes.StatListNode(
self.pos,
- stats=[
- self.make_duplicate_keys_check(const_keys_tuple, var_keys_tuple),
+ stats=([duplicate_check] if duplicate_check else []) + [
Nodes.SingleAssignmentNode(self.pos, lhs=test_result, rhs=all_tests),
],
)
@@ -1335,21 +1341,21 @@ class MatchMappingPatternNode(PatternNode):
assert self.double_star_temp
body.stats.append(
# make_double_star_capture wraps itself in an if
- self.make_double_star_capture(
- subject_node, const_keys_tuple, var_keys_tuple, test_result
- )
+ self.make_double_star_capture(subject_node, test_result)
)
- if var_keys or self.double_star_capture_target:
+ if duplicate_check or self.double_star_capture_target:
body = UtilNodes.TempResultFromStatNode(test_result, body)
- if var_keys:
- body = UtilNodes.EvalWithTempExprNode(var_keys_tuple, body)
- for k in var_keys:
- if isinstance(k, UtilNodes.ResultRefNode):
- body = UtilNodes.EvalWithTempExprNode(k, body)
- return LazyCoerceToBool(body.pos, arg=body)
else:
- return LazyCoerceToBool(all_tests.pos, arg=all_tests)
+ body = all_tests
+ if self.keys or self.double_star_capture_target:
+ body = MappingOrClassComparisonNode(
+ body.pos,
+ arg=LazyCoerceToBool(body.pos, arg=body),
+ keys_array=self.keys,
+ subjects_array=self.subject_temps
+ )
+ return LazyCoerceToBool(body.pos, arg=body)
def analyse_pattern_expressions(self, env, sequence_mapping_temp):
def to_temp_or_literal(node):
@@ -1359,7 +1365,7 @@ class MatchMappingPatternNode(PatternNode):
return node.coerce_to_temp(env)
self.keys = [
- ExprNodes.ProxyNode(to_temp_or_literal(k.analyse_expressions(env)))
+ to_temp_or_literal(k.analyse_expressions(env))
for k in self.keys
]
@@ -1410,16 +1416,19 @@ class ClassPatternNode(PatternNode):
keyword_pattern_names = []
keyword_pattern_patterns = []
+ # as with the mapping functions, lie a little about some of the types for
+ # ease of declaration
Pyx_positional_type = PyrexTypes.CFuncType(
PyrexTypes.c_bint_type,
[
PyrexTypes.CFuncTypeArg("subject", PyrexTypes.py_object_type, None),
PyrexTypes.CFuncTypeArg("type", Builtin.type_type, None),
- PyrexTypes.CFuncTypeArg("keysnames_tuple", PyrexTypes.py_object_type, None),
+ PyrexTypes.CFuncTypeArg("fixed_names", PyrexTypes.c_void_ptr_type, None),
+ PyrexTypes.CFuncTypeArg("n_fixed", PyrexTypes.c_py_ssize_t_type, None),
PyrexTypes.CFuncTypeArg("match_self", PyrexTypes.c_int_type, None),
- PyrexTypes.CFuncTypeArg("num_args", PyrexTypes.c_int_type, None),
+ PyrexTypes.CFuncTypeArg("subjects", PyrexTypes.c_void_ptr_ptr_type, None),
+ PyrexTypes.CFuncTypeArg("n_subjects", PyrexTypes.c_int_type, None),
],
- has_varargs=True,
exception_value="-1",
)
@@ -1589,13 +1598,10 @@ class ClassPatternNode(PatternNode):
def make_positional_args_call(self, subject_node, class_node):
assert self.positional_patterns
util_code = UtilityCode.load_cached("ClassPositionalPatterns", "MatchCase.c")
- keynames = ExprNodes.TupleNode(
- self.pos,
- args=[
- ExprNodes.StringNode(n.pos, value=n.name)
- for n in self.keyword_pattern_names
- ],
- )
+ keynames = [
+ ExprNodes.StringNode(n.pos, value=n.name)
+ for n in self.keyword_pattern_names
+ ]
# -1 is "unknown"
match_self = (
-1
@@ -1628,21 +1634,28 @@ class ClassPatternNode(PatternNode):
match_self = 0 # I think... Relies on knowing the bases
match_self = ExprNodes.IntNode(self.pos, value=str(match_self))
- len_ = ExprNodes.IntNode(self.pos, value=str(len(self.positional_patterns)))
- subject_derefs = [
- ExprNodes.NullNode(self.pos)
- if t is None
- else AddressOfPyObjectNode(self.pos, obj=t)
- for t in self.positional_subject_temps
- ]
- return ExprNodes.PythonCapiCallNode(
+ n_subjects = ExprNodes.IntNode(self.pos, value=str(len(self.positional_patterns)))
+ return MappingOrClassComparisonNode(
self.pos,
- "__Pyx_MatchCase_ClassPositional",
- self.Pyx_positional_type,
- utility_code=util_code,
- args=[subject_node, class_node, keynames, match_self, len_]
- + subject_derefs,
+ arg=ExprNodes.PythonCapiCallNode(
+ self.pos,
+ "__Pyx_MatchCase_ClassPositional",
+ self.Pyx_positional_type,
+ utility_code=util_code,
+ args=[
+ subject_node,
+ class_node,
+ MappingOrClassComparisonNode.make_keys_node(self.pos),
+ ExprNodes.IntNode(self.pos, value=str(len(keynames))),
+ match_self,
+ MappingOrClassComparisonNode.make_subjects_node(self.pos),
+ n_subjects,
+ ]
+ ),
+ subjects_array=self.positional_subject_temps,
+ keys_array=keynames,
)
+ return
def make_subpattern_checks(self):
patterns = self.keyword_pattern_patterns + self.positional_patterns
@@ -2057,27 +2070,6 @@ class CompilerDirectivesExprNode(ExprNodes.ProxyNode):
self.arg.annotate(code)
-class AddressOfPyObjectNode(ExprNodes.ExprNode):
- """
- obj - some temp node
- """
-
- type = PyrexTypes.c_void_ptr_ptr_type
- is_temp = False
- subexprs = ["obj"]
-
- def analyse_types(self, env):
- self.obj = self.obj.analyse_types(env)
- assert self.obj.type.is_pyobject, repr(self.obj.type)
- return self
-
- def generate_result_code(self, code):
- self.obj.generate_result_code(code)
-
- def calculate_result_code(self):
- return "&%s" % self.obj.result()
-
-
class LazyCoerceToPyObject(ExprNodes.ExprNode):
"""
Just calls "self.arg.coerce_to_pyobject" when it's analysed,
@@ -2127,4 +2119,122 @@ def generate_binop_tree_from_list(pos, operator, list_of_tests):
operator=operator,
operand1=operand1,
operand2=operand2
- ) \ No newline at end of file
+ )
+
+
+class MappingOrClassComparisonNode(ExprNodes.ExprNode):
+ """
+ Combined with MappingOrClassComparisonNodeInner this is responsible
+ for setting up up the arrays of subjects and keys that are used in
+ the function calls that handle these types of patterns
+
+ Note that self.keys_array is owned by this but used by
+ MappingOrClassComparisonNodeInner - that's mainly to ensure that
+ it gets evaluated in the correct order
+ """
+ subexprs = ["keys_array", "inner"]
+
+ keys_array_cname = "__pyx_match_mapping_keys"
+ subjects_array_cname = "__pyx_match_mapping_subjects"
+
+ @property
+ def type(self):
+ return self.inner.type
+
+ @classmethod
+ def make_keys_node(cls, pos):
+ return ExprNodes.RawCNameExprNode(
+ pos,
+ type=PyrexTypes.c_void_ptr_type,
+ cname=cls.keys_array_cname
+ )
+
+ @classmethod
+ def make_subjects_node(cls, pos):
+ return ExprNodes.RawCNameExprNode(
+ pos,
+ type=PyrexTypes.c_void_ptr_ptr_type,
+ cname=cls.subjects_array_cname
+ )
+
+ def __init__(self, pos, arg, subjects_array, **kwds):
+ super(MappingOrClassComparisonNode, self).__init__(pos, **kwds)
+ self.inner = MappingOrClassComparisonNodeInner(
+ pos,
+ arg=arg,
+ keys_array = self.keys_array,
+ subjects_array = subjects_array
+ )
+
+ def analyse_types(self, env):
+ self.inner = self.inner.analyse_types(env)
+ self.keys_array = [
+ key.analyse_types(env).coerce_to_simple(env) for key in self.keys_array
+ ]
+ return self
+
+ def generate_result_code(self, code):
+ pass
+
+ def calculate_result_code(self):
+ return self.inner.calculate_result_code()
+
+
+class MappingOrClassComparisonNodeInner(ExprNodes.ExprNode):
+ """
+ Sets up the arrays of subjects and keys
+
+ Created by the constructor of MappingComparisonNode
+ (no need to create directly)
+
+ has attributes:
+ * arg - the main comparison node
+ * keys_array - list of ExprNodes representing keys
+ * subjects_array - list of ExprNodes representing subjects
+ """
+ subexprs = ['arg']
+
+ @property
+ def type(self):
+ return self.arg.type
+
+ def analyse_types(self, env):
+ self.arg = self.arg.analyse_types(env)
+ for n in range(len(self.keys_array)):
+ key = self.keys_array[n].analyse_types(env)
+ key = key.coerce_to_pyobject(env)
+ self.keys_array[n] = key
+ assert self.arg.type is PyrexTypes.c_bint_type
+ return self
+
+ def generate_evaluation_code(self, code):
+ code.putln("{")
+ keys_str = ", ".join(k.result() for k in self.keys_array)
+ if not keys_str:
+ # GCC gets worried about overflow if we pass
+ # a genuinely empty array
+ keys_str = "NULL"
+ code.putln("PyObject *%s[] = {%s};" % (
+ MappingOrClassComparisonNode.keys_array_cname,
+ keys_str,
+ ))
+ subjects_str = ", ".join(
+ "&"+subject.result() if subject is not None else "NULL" for subject in self.subjects_array
+ )
+ if not subjects_str:
+ # GCC gets worried about overflow if we pass
+ # a genuinely empty array
+ subjects_str = "NULL"
+ code.putln("PyObject **%s[] = {%s};" % (
+ MappingOrClassComparisonNode.subjects_array_cname,
+ subjects_str
+ ))
+ super(MappingOrClassComparisonNodeInner, self).generate_evaluation_code(code)
+
+ code.putln("}")
+
+ def generate_result_code(self, code):
+ pass
+
+ def calculate_result_code(self):
+ return self.arg.result() \ No newline at end of file
diff --git a/Cython/Utility/MatchCase.c b/Cython/Utility/MatchCase.c
index fea08e0c1..85f8236eb 100644
--- a/Cython/Utility/MatchCase.c
+++ b/Cython/Utility/MatchCase.c
@@ -1,28 +1,14 @@
///////////////////////////// ABCCheck //////////////////////////////
#if PY_VERSION_HEX < 0x030A0000
-static int __Pyx_MatchCase_IsExactSequence(PyObject *o) {
+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)) {
+ 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;
}
- if (PyRange_Check(o) || PyMemoryView_Check(o)) {
- // Exact check isn't possible so do exact check in another way
- PyObject *mro = PyObject_GetAttrString((PyObject*)Py_TYPE(o), "__mro__");
- if (mro) {
- Py_ssize_t len = PyObject_Length(mro);
- Py_DECREF(mro);
- if (len < 0) {
- PyErr_Clear(); // doesn't really matter, just proceed with other checks
- } else if (len == 2) {
- return 1; // the type and "object" and no other bases
- }
- } else {
- PyErr_Clear(); // doesn't really matter, just proceed with other checks
- }
- }
return 0;
}
@@ -34,10 +20,13 @@ static CYTHON_INLINE int __Pyx_MatchCase_IsExactMapping(PyObject *o) {
}
static int __Pyx_MatchCase_IsExactNeitherSequenceNorMapping(PyObject *o) {
- if (PyUnicode_Check(o) || PyBytes_Check(o) || PyByteArray_Check(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.
- // They're therefore "inexact" checks
+ // 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;
@@ -73,6 +62,16 @@ static int __Pyx_MatchCase_IsExactNeitherSequenceNorMapping(PyObject *o) {
#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) {
@@ -101,12 +100,7 @@ static unsigned int __Pyx_MatchCase_ABCCheck(PyObject *o, int sequence_first, in
result = __PYX_DEFINITELY_SEQUENCE_FLAG;
goto end;
}
- sequence_type = PyObject_GetAttr(abc_module, PYIDENT("Sequence"));
- if (!sequence_type) {
- result = __PYX_SEQUENCE_MAPPING_ERROR;
- goto end;
- }
- sequence_result = PyObject_IsInstance(o, sequence_type);
+ sequence_result = __Pyx_MatchCase_InitAndIsInstanceAbc(o, abc_module, &sequence_type, PYIDENT("Sequence"));
if (sequence_result < 0) {
result = __PYX_SEQUENCE_MAPPING_ERROR;
goto end;
@@ -114,41 +108,32 @@ static unsigned int __Pyx_MatchCase_ABCCheck(PyObject *o, int sequence_first, in
result |= __PYX_DEFINITELY_NOT_SEQUENCE_FLAG;
goto end;
}
- // else wait to see what mapping is
+ // else wait to see what mapping is
}
if (!definitely_not_mapping) {
- mapping_type = PyObject_GetAttr(abc_module, PYIDENT("Mapping"));
- if (!mapping_type) {
+ mapping_result = __Pyx_MatchCase_InitAndIsInstanceAbc(o, abc_module, &mapping_type, PYIDENT("Mapping"));
+ if (mapping_result < 0) {
+ result = __PYX_SEQUENCE_MAPPING_ERROR;
goto end;
- }
- mapping_result = PyObject_IsInstance(o, mapping_type);
- }
- 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;
+ } 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_type = PyObject_GetAttr(abc_module, PYIDENT("Sequence"));
- if (!sequence_type) {
- result = __PYX_SEQUENCE_MAPPING_ERROR;
- goto end;
- }
- sequence_result = PyObject_IsInstance(o, sequence_type);
+ sequence_result = __Pyx_MatchCase_InitAndIsInstanceAbc(o, abc_module, &sequence_type, PYIDENT("Sequence"));
}
if (sequence_result < 0) {
result = __PYX_SEQUENCE_MAPPING_ERROR;
@@ -167,7 +152,7 @@ static unsigned int __Pyx_MatchCase_ABCCheck(PyObject *o, int sequence_first, in
if (!mro) {
PyErr_Clear();
goto end;
- }
+ }
if (!PyTuple_Check(mro)) {
Py_DECREF(mro);
goto end;
@@ -322,7 +307,7 @@ static PyObject *__Pyx_MatchCase_OtherSequenceSliceToList(PyObject *x, Py_ssize_
PyObject *list;
ssizeargfunc slot;
PyTypeObject *type = Py_TYPE(x);
-
+
list = PyList_New(total);
if (!list) {
return NULL;
@@ -454,17 +439,15 @@ static int __Pyx_MatchCase_IsMapping(PyObject *o, unsigned int *sequence_mapping
#endif
}
-//////////////////////// DuplicateKeyCheck.proto ///////////////////////
+//////////////////////// MappingKeyCheck.proto /////////////////////////
-// Returns an new reference to any duplicate key.
-// NULL can indicate no duplicate keys or an error (so use PyErr_Occurred)
-static PyObject* __Pyx_MatchCase_CheckDuplicateKeys(PyObject *fixed_keys, PyObject *var_keys, Py_ssize_t n_var_keys); /*proto*/
+static int __Pyx_MatchCase_CheckMappingDuplicateKeys(PyObject *keys[], Py_ssize_t nFixedKeys, Py_ssize_t nKeys);
-//////////////////////// DuplicateKeyCheck /////////////////////////////
+//////////////////////// MappingKeyCheck ///////////////////////////////
-static PyObject* __Pyx_MatchCase_CheckDuplicateKeys(PyObject *fixed_keys, PyObject *var_keys, Py_ssize_t n_var_keys) {
- // Inputs are tuples, and typically fairly small. It may be more efficient to
- // loop over the tuple than create a set.
+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
@@ -472,71 +455,55 @@ static PyObject* __Pyx_MatchCase_CheckDuplicateKeys(PyObject *fixed_keys, PyObje
// this step completely.
PyObject *var_keys_set;
- PyObject *key = NULL;
+ PyObject *key;
Py_ssize_t n;
int contains;
var_keys_set = PySet_New(NULL);
- if (!var_keys_set) return NULL;
+ if (!var_keys_set) return -1;
- n_var_keys = (n_var_keys < 0) ? PyTuple_GET_SIZE(var_keys) : n_var_keys;
- for (n=0; n < n_var_keys; ++n) {
- key = PyTuple_GET_ITEM(var_keys, n);
+ for (n=nFixedKeys; n < nKeys; ++n) {
+ key = keys[n];
contains = PySet_Contains(var_keys_set, key);
if (contains < 0) {
- key = NULL;
- goto end;
+ goto bad;
} else if (contains == 1) {
- Py_INCREF(key);
- goto end;
+ goto raise_error;
} else {
if (PySet_Add(var_keys_set, key)) {
- key = NULL;
- goto end;
+ goto bad;
}
}
}
- for (n=0; n < PyTuple_GET_SIZE(fixed_keys); ++n) {
- key = PyTuple_GET_ITEM(fixed_keys, n);
+ for (n=0; n < nFixedKeys; ++n) {
+ key = keys[n];
contains = PySet_Contains(var_keys_set, key);
if (contains < 0) {
- key = NULL;
- goto end;
+ goto bad;
} else if (contains == 1) {
- Py_INCREF(key);
- goto end;
+ goto raise_error;
}
}
- key = NULL;
- end:
Py_DECREF(var_keys_set);
- return key;
-}
-
-//////////////////////// MappingKeyCheck.proto /////////////////////////
-
-static int __Pyx_MatchCase_CheckMappingDuplicateKeys(PyObject *fixed_keys, PyObject *var_keys); /* proto */
-
-//////////////////////// MappingKeyCheck ///////////////////////////////
-//@requires: DuplicateKeyCheck
-
-static int __Pyx_MatchCase_CheckMappingDuplicateKeys(PyObject *fixed_keys, PyObject *var_keys) {
- PyObject *key = __Pyx_MatchCase_CheckDuplicateKeys(fixed_keys, var_keys, -1);
- if (key) {
- PyErr_Format(PyExc_ValueError, "mapping pattern checks duplicate key (%R)", key);
- Py_DECREF(key);
- return -1;
- } else if (PyErr_Occurred()) {
- return -1;
- } else {
- return 0;
- }
+ 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 ////////////////
-#include <stdarg.h>
-
// the variadic arguments are a list of PyObject** to subjects to be filled. They may be NULL
// in which case they're ignored.
//
@@ -544,48 +511,31 @@ static int __Pyx_MatchCase_CheckMappingDuplicateKeys(PyObject *fixed_keys, PyObj
#if CYTHON_REFNANNY
#define __Pyx_MatchCase_Mapping_ExtractDict(...) __Pyx__MatchCase_Mapping_ExtractDict(__pyx_refnanny, __VA_ARGS__)
-#define __Pyx_MatchCase_Mapping_ExtractDictV(...) __Pyx__MatchCase_Mapping_ExtractDictV(__pyx_refnanny, __VA_ARGS__)
#else
#define __Pyx_MatchCase_Mapping_ExtractDict(...) __Pyx__MatchCase_Mapping_ExtractDict(NULL, __VA_ARGS__)
-#define __Pyx_MatchCase_Mapping_ExtractDictV(...) __Pyx__MatchCase_Mapping_ExtractDictV(NULL, __VA_ARGS__)
#endif
-static CYTHON_INLINE int __Pyx__MatchCase_Mapping_ExtractDict(void *__pyx_refnanny, PyObject *dict, PyObject *fixed_keys, PyObject *var_keys, ...); /* proto */
-static int __Pyx__MatchCase_Mapping_ExtractDictV(void *__pyx_refnanny, PyObject *dict, PyObject *fixed_keys, PyObject *var_keys, va_list subjects); /* proto */
+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 *fixed_keys, PyObject *var_keys, ...) {
- int result;
- va_list subjects;
-
- va_start(subjects, var_keys);
- result = __Pyx_MatchCase_Mapping_ExtractDictV(dict, fixed_keys, var_keys, subjects);
- va_end(subjects);
- return result;
-}
+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;
-static int __Pyx__MatchCase_Mapping_ExtractDictV(void *__pyx_refnanny, PyObject *dict, PyObject *fixed_keys, PyObject *var_keys, va_list subjects) {
- PyObject *keys[] = {fixed_keys, var_keys};
- Py_ssize_t i, j;
-
- for (i=0; i<2; ++i) {
- PyObject *tuple = keys[i];
- for (j=0; j<PyTuple_GET_SIZE(tuple); ++j) {
- PyObject *key = PyTuple_GET_ITEM(tuple, j);
- PyObject **subject = va_arg(subjects, PyObject**);
- 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!
+ 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
@@ -598,74 +548,55 @@ static int __Pyx__MatchCase_Mapping_ExtractDictV(void *__pyx_refnanny, PyObject
//
// This is a specialized version for the rarer case when the type isn't an exact dict.
-#include <stdarg.h>
-
#if CYTHON_REFNANNY
#define __Pyx_MatchCase_Mapping_ExtractNonDict(...) __Pyx__MatchCase_Mapping_ExtractNonDict(__pyx_refnanny, __VA_ARGS__)
-#define __Pyx_MatchCase_Mapping_ExtractNonDictV(...) __Pyx__MatchCase_Mapping_ExtractNonDictV(__pyx_refnanny, __VA_ARGS__)
#else
#define __Pyx_MatchCase_Mapping_ExtractNonDict(...) __Pyx__MatchCase_Mapping_ExtractNonDict(NULL, __VA_ARGS__)
-#define __Pyx_MatchCase_Mapping_ExtractNonDictV(...) __Pyx__MatchCase_Mapping_ExtractNonDictV(NULL, __VA_ARGS__)
#endif
-static CYTHON_INLINE int __Pyx__MatchCase_Mapping_ExtractNonDict(void *__pyx_refnanny, PyObject *mapping, PyObject *fixed_keys, PyObject *var_keys, ...); /* proto */
-static int __Pyx__MatchCase_Mapping_ExtractNonDictV(void *__pyx_refnanny, PyObject *mapping, PyObject *fixed_keys, PyObject *var_keys, va_list subjects); /* proto */
+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 CYTHON_INLINE int __Pyx__MatchCase_Mapping_ExtractNonDict(void *__pyx_refnanny, PyObject *map, PyObject *fixed_keys, PyObject *var_keys, ...) {
- int result;
- va_list subjects;
-
- va_start(subjects, var_keys);
- result = __Pyx_MatchCase_Mapping_ExtractNonDictV(map, fixed_keys, var_keys, subjects);
- va_end(subjects);
- return result;
-}
-
-static int __Pyx__MatchCase_Mapping_ExtractNonDictV(void *__pyx_refnanny, PyObject *map, PyObject *fixed_keys, PyObject *var_keys, va_list subjects) {
+static int __Pyx__MatchCase_Mapping_ExtractNonDict(void *__pyx_refnanny, PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys, PyObject **subjects[]) {
PyObject *dummy=NULL, *get=NULL;
- PyObject *keys[] = {fixed_keys, var_keys};
- Py_ssize_t i, j;
+ Py_ssize_t i;
int result = 0;
dummy = PyObject_CallObject((PyObject *)&PyBaseObject_Type, NULL);
if (!dummy) {
return -1;
}
- get = PyObject_GetAttrString(map, "get");
+ get = PyObject_GetAttrString(mapping, "get");
if (!get) {
result = -1;
goto end;
}
- for (i=0; i<2; ++i) {
- PyObject *tuple = keys[i];
- for (j=0; j<PyTuple_GET_SIZE(tuple); ++j) {
- PyObject **subject;
- PyObject *value = NULL;
- PyObject *key = PyTuple_GET_ITEM(tuple, j);
+ 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"
- value = __Pyx_PyObject_Call2Args(get, key, dummy);
- if (!value) {
- result = -1;
- goto end;
- } else if (value == dummy) {
- Py_DECREF(value);
- goto end; // failed
+ // 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"
+ 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 {
- subject = va_arg(subjects, PyObject**);
- if (subject) {
- __Pyx_XDECREF_SET(*subject, value);
- __Pyx_GOTREF(*subject);
- } else {
- Py_DECREF(value);
- }
+ Py_DECREF(value);
}
}
}
@@ -679,36 +610,28 @@ static int __Pyx__MatchCase_Mapping_ExtractNonDictV(void *__pyx_refnanny, PyObje
///////////////////////// ExtractGeneric.proto ////////////////////////////////
-#include <stdarg.h>
-
#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 *map, PyObject *fixed_keys, PyObject *var_keys, ...); /* proto */
+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 *map, PyObject *fixed_keys, PyObject *var_keys, ...) {
- va_list subjects;
- int result;
-
- va_start(subjects, var_keys);
- if (PyDict_CheckExact(map)) {
- result = __Pyx_MatchCase_Mapping_ExtractDictV(map, fixed_keys, var_keys, subjects);
+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 {
- result = __Pyx_MatchCase_Mapping_ExtractNonDictV(map, fixed_keys, var_keys, subjects);
+ return __Pyx_MatchCase_Mapping_ExtractNonDict(mapping, keys, nKeys, subjects);
}
- va_end(subjects);
- return result;
}
///////////////////////////// DoubleStarCapture.proto //////////////////////
-static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *map, PyObject *const_temps, PyObject *var_temps); /* proto */
+static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys); /* proto */
//////////////////////////// DoubleStarCapture //////////////////////////////
@@ -717,31 +640,30 @@ static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *map, PyObjec
// 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 loop over separate tuples for constant and runtime keys
-// 2. We add a shortcut for when there will be no left over keys (because I'm guess it's pretty common)
+// 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 *map, PyObject *const_temps, PyObject *var_temps) {
+static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *mapping, PyObject *keys[], Py_ssize_t nKeys) {
PyObject *dict_out;
- PyObject *tuples[] = { const_temps, var_temps };
- Py_ssize_t i, j;
+ Py_ssize_t i;
{{if tag != "NonDict"}}
// shortcut for when there are no left-over keys
- if ({{if tag=="ExactDict"}}(1){{else}}PyDict_CheckExact(map){{endif}}) {
- Py_ssize_t s = PyDict_Size(map);
+ if ({{if tag=="ExactDict"}}(1){{else}}PyDict_CheckExact(mapping){{endif}}) {
+ Py_ssize_t s = PyDict_Size(mapping);
if (s == -1) {
return NULL;
}
- if (s == (PyTuple_GET_SIZE(const_temps) + PyTuple_GET_SIZE(var_temps))) {
+ if (s == nKeys) {
return PyDict_New();
}
}
{{endif}}
{{if tag=="ExactDict"}}
- dict_out = PyDict_Copy(map);
+ dict_out = PyDict_Copy(mapping);
{{else}}
dict_out = PyDict_New();
{{endif}}
@@ -749,19 +671,16 @@ static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *map, PyObjec
return NULL;
}
{{if tag!="ExactDict"}}
- if (PyDict_Update(dict_out, map)) {
+ if (PyDict_Update(dict_out, mapping)) {
Py_DECREF(dict_out);
return NULL;
}
{{endif}}
- for (i=0; i<2; ++i) {
- PyObject *keys = tuples[i];
- for (j=0; j<PyTuple_GET_SIZE(keys); ++j) {
- if (PyDict_DelItem(dict_out, PyTuple_GET_ITEM(keys, j))) {
- Py_DECREF(dict_out);
- return NULL;
- }
+ for (i=0; i<nKeys; ++i) {
+ if (PyDict_DelItem(dict_out, keys[i])) {
+ Py_DECREF(dict_out);
+ return NULL;
}
}
return dict_out;
@@ -769,30 +688,81 @@ static PyObject* __Pyx_MatchCase_DoubleStarCapture{{tag}}(PyObject *map, PyObjec
////////////////////////////// ClassPositionalPatterns.proto ////////////////////////
-#include <stdarg.h>
-
#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 *keysnames_tuple, int match_self, int num_args, ...); /* proto */
+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 //////////////////////////////
-//@requires: DuplicateKeyCheck
+
+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
-
-static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subject, PyTypeObject *type, PyObject *keysnames_tuple, int match_self, int num_args, ...)
+// 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, *dup_key;
+ PyObject *match_args;
Py_ssize_t allowed, i;
int result;
- va_list subjects;
match_args = PyObject_GetAttrString((PyObject*)type, "__match_args__");
if (!match_args) {
@@ -805,19 +775,17 @@ static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subj
_Py_TPFLAGS_MATCH_SELF);
#else
// probably an earlier version of Python. Go off the known list in the specification
- match_self = (PyType_IsSubtype(type, &PyByteArray_Type) ||
- PyType_IsSubtype(type, &PyBytes_Type) ||
- PyType_IsSubtype(type, &PyDict_Type) ||
+ 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) ||
- PyType_IsSubtype(type, &PyLong_Type) || // This should capture bool too
- #if PY_MAJOR_VERSION < 3
- PyType_IsSubtype(type, &PyInt_Type) ||
- #endif
- PyType_IsSubtype(type, &PyList_Type) ||
- PyType_IsSubtype(type, &PySet_Type) ||
- PyType_IsSubtype(type, &PyUnicode_Type) ||
- PyType_IsSubtype(type, &PyTuple_Type)
);
#endif
}
@@ -838,18 +806,17 @@ static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subj
allowed = match_self ?
1 : (match_args ? PyTuple_GET_SIZE(match_args) : 0);
- if (allowed < num_args) {
+ 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, num_args);
+ allowed, plural, n_subjects);
Py_XDECREF(match_args);
return -1;
}
- va_start(subjects, num_args);
if (match_self) {
- PyObject **self_subject = va_arg(subjects, PyObject**);
+ PyObject **self_subject = subjects[0];
if (self_subject) {
// Easy. Copy the subject itself, and move on to kwargs.
__Pyx_XDECREF_SET(*self_subject, subject);
@@ -858,20 +825,13 @@ static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subj
result = 1;
goto end_match_self;
}
- // next stage is to check for duplicate keys. Reuse code from mapping
- dup_key = __Pyx_MatchCase_CheckDuplicateKeys(keysnames_tuple, match_args, num_args);
- if (dup_key) {
- PyErr_Format(PyExc_TypeError, "%s() got multiple sub-patterns for attribute %R",
- type->tp_name, dup_key);
- Py_DECREF(dup_key);
- result = -1;
- goto end;
- } else if (PyErr_Occurred()) {
+ // 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 < num_args; i++) {
+ for (i = 0; i < n_subjects; i++) {
PyObject *attr;
PyObject **subject_i;
PyObject *name = PyTuple_GET_ITEM(match_args, i);
@@ -889,7 +849,7 @@ static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subj
result = 0;
goto end;
}
- subject_i = va_arg(subjects, PyObject**);
+ subject_i = subjects[i];
if (subject_i) {
__Pyx_XDECREF_SET(*subject_i, attr);
__Pyx_GOTREF(attr);
@@ -902,7 +862,6 @@ static int __Pyx__MatchCase_ClassPositional(void *__pyx_refnanny, PyObject *subj
end:
Py_DECREF(match_args);
end_match_self: // because match_args isn't set
- va_end(subjects);
return result;
}
@@ -913,6 +872,13 @@ 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;
diff --git a/tests/run/extra_patma.pyx b/tests/run/extra_patma.pyx
index 3bded3426..613f2daa0 100644
--- a/tests/run/extra_patma.pyx
+++ b/tests/run/extra_patma.pyx
@@ -5,6 +5,21 @@
cimport cython
import array
+import sys
+
+__doc__ = ""
+
+if sys.version_info[0] > 2:
+ __doc__ += """
+ array.array doesn't have the buffer protocol in Py2 and
+ this doesn't really feel worth working around to test
+ >>> print(test_memoryview(array.array('i', [0, 1, 2])))
+ a 1
+ >>> print(test_memoryview(array.array('i', [])))
+ b
+ >>> print(test_memoryview(array.array('i', [5])))
+ c [5]
+ """
# goes via .shape instead
@cython.test_fail_if_path_exists("//CallNode//NameNode[@name = 'len']")
@@ -12,14 +27,8 @@ import array
@cython.test_fail_if_path_exists("//PythonCapiCallNode//PythonCapiFunctionNode[@cname = '__Pyx_MatchCase_IsSequence']")
def test_memoryview(int[:] x):
"""
- >>> print(test_memoryview(array.array('i', [0, 1, 2])))
- a 1
- >>> print(test_memoryview(array.array('i', [])))
- b
>>> print(test_memoryview(None))
no!
- >>> print(test_memoryview(array.array('i', [5])))
- c [5]
"""
match x:
case [0, y, 2]:
@@ -89,7 +98,7 @@ def class_attr_lookup(x):
assert cython.typeof(y) == "double", cython.typeof(y)
return y
-class PyClass:
+class PyClass(object):
pass
@cython.test_assert_path_exists("//PythonCapiFunctionNode[@cname='__Pyx_TypeCheck']")
@@ -110,6 +119,7 @@ def class_typecheck_exists(x):
case _:
return False
+
@cython.test_fail_if_path_exists("//NameNode[@name='isinstance']")
@cython.test_fail_if_path_exists("//PythonCapiFunctionNode[@cname='__Pyx_TypeCheck']")
def class_typecheck_doesnt_exist(C x):
diff --git a/tests/run/extra_patma_py.py b/tests/run/extra_patma_py.py
index e4b2aef84..a5046b997 100644
--- a/tests/run/extra_patma_py.py
+++ b/tests/run/extra_patma_py.py
@@ -4,6 +4,9 @@
from __future__ import print_function
import array
+import sys
+
+__doc__ = ""
def test_type_inference(x):
"""
@@ -63,10 +66,15 @@ def test_duplicate_keys(key1, key2):
>>> test_duplicate_keys("a", "b")
True
- >>> test_duplicate_keys("a", "a")
- Traceback (most recent call last):
- ...
- ValueError: mapping pattern checks duplicate key ('a')
+
+ Slightly awkward doctest to work around Py2 incompatibility
+ >>> try:
+ ... test_duplicate_keys("a", "a")
+ ... except ValueError as e:
+ ... if sys.version_info[0] > 2:
+ ... assert e.args[0] == "mapping pattern checks duplicate key ('a')", e.args[0]
+ ... else:
+ ... assert e.args[0] == "mapping pattern checks duplicate key"
"""
class Keys:
KEY_1 = key1
@@ -79,7 +87,7 @@ def test_duplicate_keys(key1, key2):
return False
-class PyClass:
+class PyClass(object):
pass
@@ -99,3 +107,20 @@ class PrivateAttrLookupOuter:
match x:
case PyClass(__something=y):
return y
+
+
+if sys.version_info[0] < 3:
+ class OldStyleClass:
+ pass
+
+ def test_oldstyle_class_failure(x):
+ match x:
+ case OldStyleClass():
+ return True
+
+ __doc__ += """
+ >>> test_oldstyle_class_failure(1)
+ Traceback (most recent call last):
+ ...
+ TypeError: called match pattern must be a new-style class.
+ """