automatically replace calls to builtin.__contains__() with the corresponding C-API...
authorStefan Behnel <stefan_ml@behnel.de>
Fri, 22 Feb 2013 19:07:09 +0000 (20:07 +0100)
committerStefan Behnel <stefan_ml@behnel.de>
Fri, 22 Feb 2013 19:07:09 +0000 (20:07 +0100)
Cython/Compiler/Builtin.py
Cython/Compiler/ExprNodes.py
Cython/Compiler/PyrexTypes.py
Cython/Compiler/Symtab.py
tests/run/unbound_special_methods.pyx [new file with mode: 0644]

index 9cb1006..ea6dc27 100644 (file)
@@ -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",
index 2d451c3..cadf0df 100755 (executable)
@@ -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")
index 709ec84..dc7ee6d 100755 (executable)
@@ -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
index f42768a..b08fb37 100644 (file)
@@ -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 (file)
index 0000000..e16c7b2
--- /dev/null
@@ -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