diff options
author | da-woods <dw-git@d-woods.co.uk> | 2022-09-17 09:57:53 +0100 |
---|---|---|
committer | da-woods <dw-git@d-woods.co.uk> | 2022-09-17 09:57:53 +0100 |
commit | e1f145e5b9c787083a347200a18a5401ad227540 (patch) | |
tree | e8e1e63464b5360fd0efa8af6bedb154e09ff03d | |
parent | 119d7679be2609fd7cdb8385c914ebb437046a01 (diff) | |
parent | a96578193f3fa9a5e4d2b083d13f5fa8ddcc40a2 (diff) | |
download | cython-e1f145e5b9c787083a347200a18a5401ad227540.tar.gz |
Merge branch 'match-class' into match-or
-rw-r--r-- | Cython/Compiler/MatchCaseNodes.py | 294 | ||||
-rw-r--r-- | Cython/Utility/MatchCase.c | 456 | ||||
-rw-r--r-- | tests/run/extra_patma.pyx | 24 | ||||
-rw-r--r-- | tests/run/extra_patma_py.py | 35 |
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. + """ |