From 18933f25663401999c10722833251725ca2134c6 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Fri, 22 Feb 2013 20:07:09 +0100 Subject: [PATCH] automatically replace calls to builtin.__contains__() with the corresponding C-API function and enable optimisation of unbound builtin methods for subtypes --- Cython/Compiler/Builtin.py | 21 +++++++---- Cython/Compiler/ExprNodes.py | 31 ++++++++++++---- Cython/Compiler/PyrexTypes.py | 4 +- Cython/Compiler/Symtab.py | 2 +- tests/run/unbound_special_methods.pyx | 69 +++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 18 deletions(-) create mode 100644 tests/run/unbound_special_methods.pyx diff --git a/Cython/Compiler/Builtin.py b/Cython/Compiler/Builtin.py index 9cb1006..ea6dc27 100644 --- a/Cython/Compiler/Builtin.py +++ b/Cython/Compiler/Builtin.py @@ -268,20 +268,26 @@ builtin_types_table = [ BuiltinAttribute('imag', 'cval.imag', field_type = PyrexTypes.c_double_type), ]), - ("bytes", "PyBytes_Type", []), - ("str", "PyString_Type", []), - ("unicode", "PyUnicode_Type", [BuiltinMethod("join", "TO", "T", "PyUnicode_Join"), + ("bytes", "PyBytes_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"), + ]), + ("str", "PyString_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"), + ]), + ("unicode", "PyUnicode_Type", [BuiltinMethod("__contains__", "TO", "b", "PyUnicode_Contains"), + BuiltinMethod("join", "TO", "T", "PyUnicode_Join"), ]), - ("tuple", "PyTuple_Type", []), + ("tuple", "PyTuple_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"), + ]), - ("list", "PyList_Type", [BuiltinMethod("insert", "TzO", "r", "PyList_Insert"), + ("list", "PyList_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"), + BuiltinMethod("insert", "TzO", "r", "PyList_Insert"), BuiltinMethod("reverse", "T", "r", "PyList_Reverse"), BuiltinMethod("append", "TO", "r", "__Pyx_PyList_Append", utility_code=UtilityCode.load("ListAppend", "Optimize.c")), ]), - ("dict", "PyDict_Type", [BuiltinMethod("items", "T", "O", "__Pyx_PyDict_Items", + ("dict", "PyDict_Type", [BuiltinMethod("__contains__", "TO", "b", "PyDict_Contains"), + BuiltinMethod("items", "T", "O", "__Pyx_PyDict_Items", utility_code=UtilityCode.load("py_dict_items", "Builtins.c")), BuiltinMethod("keys", "T", "O", "__Pyx_PyDict_Keys", utility_code=UtilityCode.load("py_dict_keys", "Builtins.c")), @@ -309,7 +315,8 @@ builtin_types_table = [ ]), # ("file", "PyFile_Type", []), # not in Py3 - ("set", "PySet_Type", [BuiltinMethod("clear", "T", "r", "PySet_Clear", + ("set", "PySet_Type", [BuiltinMethod("__contains__", "TO", "b", "PySequence_Contains"), + BuiltinMethod("clear", "T", "r", "PySet_Clear", utility_code = py_set_utility_code), # discard() and remove() have a special treatment for unhashable values # BuiltinMethod("discard", "TO", "r", "PySet_Discard", diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 2d451c3..cadf0df 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -1494,7 +1494,7 @@ class NameNode(AtomicExprNode): if not entry: entry = env.lookup(self.name) if entry and entry.is_type: - if entry.type.is_extension_type: # or entry.type.is_builtin_type: + if entry.type.is_extension_type or entry.type.is_builtin_type: return entry.type return None @@ -4001,6 +4001,11 @@ class SimpleCallNode(CallNode): else: arg = CloneNode(self.self) arg = self.coerced_self = arg.coerce_to(formal_arg.type, env) + elif formal_arg.type.is_builtin_type: + # special case: unbound methods of builtins accept subtypes + arg = arg.coerce_to(formal_arg.type, env) + if arg.type.is_builtin_type and isinstance(arg, PyTypeTestNode): + arg.exact_builtin_type = False args[0] = arg # Coerce arguments @@ -4763,6 +4768,9 @@ class AttributeNode(ExprNode): entry = type.scope.lookup_here(self.attribute) if entry and entry.is_cmethod: if type.is_builtin_type: + if not self.is_called: + # must handle this as Python object + return None ubcm_entry = entry else: # Create a temporary entry describing the C method @@ -4793,7 +4801,7 @@ class AttributeNode(ExprNode): if module_scope: entry = module_scope.lookup_here(self.attribute) if entry and entry.is_type: - if entry.type.is_extension_type: # or entry.type.is_builtin_type: + if entry.type.is_extension_type or entry.type.is_builtin_type: return entry.type return None @@ -9719,6 +9727,8 @@ class PyTypeTestNode(CoercionNode): # object is an instance of a particular extension type. # This node borrows the result of its argument node. + exact_builtin_type = True + def __init__(self, arg, dst_type, env, notnone=False): # The arg is know to be a Python object, and # the dst_type is known to be an extension type. @@ -9760,12 +9770,17 @@ class PyTypeTestNode(CoercionNode): def generate_result_code(self, code): if self.type.typeobj_is_available(): - if not self.type.is_builtin_type: - code.globalstate.use_utility_code(UtilityCode.load_cached("ExtTypeTest", "ObjectHandling.c")) - code.putln( - "if (!(%s)) %s" % ( - self.type.type_test_code(self.arg.py_result(), self.notnone), - code.error_goto(self.pos))) + if self.type.is_builtin_type: + type_test = self.type.type_test_code( + self.arg.py_result(), + self.notnone, exact=self.exact_builtin_type) + else: + type_test = self.type.type_test_code( + self.arg.py_result(), self.notnone) + code.globalstate.use_utility_code( + UtilityCode.load_cached("ExtTypeTest", "ObjectHandling.c")) + code.putln("if (!(%s)) %s" % ( + type_test, code.error_goto(self.pos))) else: error(self.pos, "Cannot test type of extern C class " "without type object name specification") diff --git a/Cython/Compiler/PyrexTypes.py b/Cython/Compiler/PyrexTypes.py index 709ec84..dc7ee6d 100755 --- a/Cython/Compiler/PyrexTypes.py +++ b/Cython/Compiler/PyrexTypes.py @@ -984,8 +984,8 @@ class BuiltinObjectType(PyObjectType): def isinstance_code(self, arg): return '%s(%s)' % (self.type_check_function(exact=False), arg) - def type_test_code(self, arg, notnone=False): - type_check = self.type_check_function(exact=True) + def type_test_code(self, arg, notnone=False, exact=True): + type_check = self.type_check_function(exact=exact) check = 'likely(%s(%s))' % (type_check, arg) if not notnone: check += '||((%s) == Py_None)' % arg diff --git a/Cython/Compiler/Symtab.py b/Cython/Compiler/Symtab.py index f42768a..b08fb37 100644 --- a/Cython/Compiler/Symtab.py +++ b/Cython/Compiler/Symtab.py @@ -1891,7 +1891,7 @@ class CClassScope(ClassScope): def declare_cfunction(self, name, type, pos, cname = None, visibility = 'private', api = 0, in_pxd = 0, defining = 0, modifiers = (), utility_code = None): - if get_special_method_signature(name): + if get_special_method_signature(name) and not self.parent_type.is_builtin_type: error(pos, "Special methods must be declared with 'def', not 'cdef'") args = type.args if not args: diff --git a/tests/run/unbound_special_methods.pyx b/tests/run/unbound_special_methods.pyx new file mode 100644 index 0000000..e16c7b2 --- /dev/null +++ b/tests/run/unbound_special_methods.pyx @@ -0,0 +1,69 @@ +# mode: run +# tag: special_method + +cimport cython + +text = u'ab jd sdflk as sa sadas asdas fsdf ' + + +@cython.test_fail_if_path_exists( + "//CoerceFromPyTypeNode") +@cython.test_assert_path_exists( + "//CoerceToPyTypeNode", + "//AttributeNode", + "//AttributeNode[@entry.cname = 'PyUnicode_Contains']") +def unicode_contains(unicode s, substring): + """ + >>> unicode_contains(text, 'fl') + True + >>> unicode_contains(text, 'XYZ') + False + >>> unicode_contains(None, 'XYZ') + Traceback (most recent call last): + AttributeError: 'NoneType' object has no attribute '__contains__' + """ + return s.__contains__(substring) + + +@cython.test_fail_if_path_exists( + "//CoerceFromPyTypeNode") +@cython.test_assert_path_exists( +# "//CoerceToPyTypeNode", + "//NameNode[@entry.cname = 'PyUnicode_Contains']") +def unicode_contains_unbound(unicode s, substring): + """ + >>> unicode_contains_unbound(text, 'fl') + True + >>> unicode_contains_unbound(text, 'XYZ') + False + >>> unicode_contains_unbound(None, 'XYZ') # doctest: +ELLIPSIS + Traceback (most recent call last): + TypeError: descriptor '__contains__' requires a '...' object but received a 'NoneType' + """ + return unicode.__contains__(s, substring) + + +cdef class UnicodeSubclass(unicode): + """ + >>> u = UnicodeSubclass(text) + >>> 'fl' in u + False + >>> 'XYZ' in u + True + >>> u.method('fl') + False + >>> u.method('XYZ') + True + >>> u.operator('fl') + False + >>> u.operator('XYZ') + True + """ + def __contains__(self, substring): + return substring not in (self + u'x') + + def method(self, other): + return self.__contains__(other) + + def operator(self, other): + return other in self -- 2.7.4