optimise MyType.__new__(MyType) into a tp_new() slot call
authorStefan Behnel <scoder@users.berlios.de>
Sat, 31 Oct 2009 13:01:34 +0000 (14:01 +0100)
committerStefan Behnel <scoder@users.berlios.de>
Sat, 31 Oct 2009 13:01:34 +0000 (14:01 +0100)
Cython/Compiler/Optimize.py
tests/errors/tp_new_errors.pyx [new file with mode: 0644]
tests/run/tp_new.pyx [new file with mode: 0644]

index 0ac80d1208edbcbf064c809823636932c4a4d99a..c26a32a588c451742c978c62f1799e38dcc45474 100644 (file)
@@ -7,6 +7,7 @@ import UtilNodes
 import TypeSlots
 import Symtab
 import Options
+import Naming
 
 from Code import UtilityCode
 from StringEncoding import EncodedString, BytesLiteral
@@ -851,7 +852,10 @@ class OptimizeBuiltinCalls(Visitor.EnvTransform):
             method_handler = self._find_handler(
                 "method_%s_%s" % (type_name, function.attribute), kwargs)
             if method_handler is None:
-                return node
+                method_handler = self._find_handler(
+                    "methodany_%s" % function.attribute, kwargs)
+                if method_handler is None:
+                    return node
             if self_arg is not None:
                 arg_list = [self_arg] + list(arg_list)
             if kwargs:
@@ -1030,6 +1034,55 @@ class OptimizeBuiltinCalls(Visitor.EnvTransform):
                 )
         return node
 
+    ### special methods
+
+    def _handle_simple_methodany___new__(self, node, args, is_unbound_method):
+        """Replace 'exttype.__new__(exttype)' by a call to exttype->tp_new()
+        """
+        obj = node.function.obj
+        if not is_unbound_method or len(args) != 1:
+            return node
+        type_arg = args[0]
+        if not obj.is_name or not type_arg.is_name:
+            # play safe
+            return node
+        if obj.type != Builtin.type_type or type_arg.type != Builtin.type_type:
+            # not a known type, play safe
+            return node
+        if not type_arg.type_entry or not obj.type_entry:
+            if obj.name != type_arg.name:
+                return node
+            # otherwise, we know it's a type and we know it's the same
+            # type for both - that should do
+        elif type_arg.type_entry != obj.type_entry:
+            # different types - do what CPython does at runtime
+            error(type_arg.pos, "%s.__new__(%s) is not safe, use %s.__new__()" %
+                  (obj.type_entry.name, type_arg.type_entry.name,
+                   type_arg.type_entry.name))
+            return node
+
+        return_type = None
+        if obj.type_entry:
+            return_type = obj.type_entry.type
+        if return_type is None and type_arg.type_entry:
+            return_type = type_arg.type_entry.type
+        if return_type is None:
+            return_type = PyrexTypes.py_object_type
+
+        # FIXME: we could potentially look up the actual tp_new C method
+        # of the extension type and call that instead of the generic slot
+        func_type = PyrexTypes.CFuncType(
+            return_type, [
+                PyrexTypes.CFuncTypeArg("type", PyrexTypes.py_object_type, None)
+                ])
+
+        return ExprNodes.PythonCapiCallNode(
+            node.pos, "__Pyx_tp_new", func_type,
+            args = args,
+            utility_code = tpnew_utility_code,
+            is_temp = node.is_temp
+            )
+
     ### methods of builtin types
 
     PyObject_Append_func_type = PyrexTypes.CFuncType(
@@ -1317,6 +1370,16 @@ static INLINE PyObject* __Pyx_Type(PyObject* o) {
 )
 
 
+tpnew_utility_code = UtilityCode(
+proto = """
+static INLINE PyObject* __Pyx_tp_new(PyObject* type_obj) {
+    return (PyObject*) (((PyTypeObject*)(type_obj))->tp_new(
+        (PyTypeObject*)(type_obj), %(TUPLE)s, NULL));
+}
+""" % {'TUPLE' : Naming.empty_tuple}
+)
+
+
 class ConstantFolding(Visitor.VisitorTransform, SkipDeclarations):
     """Calculate the result of constant expressions to store it in
     ``expr_node.constant_result``, and replace trivial cases by their
diff --git a/tests/errors/tp_new_errors.pyx b/tests/errors/tp_new_errors.pyx
new file mode 100644 (file)
index 0000000..05b5615
--- /dev/null
@@ -0,0 +1,29 @@
+
+cdef class MyType:
+    def __init__(self):
+        print "INIT"
+
+cdef class MySubType(MyType):
+    def __init__(self):
+        print "INIT"
+
+cdef class MyOtherType:
+    def __init__(self):
+        print "INIT"
+
+def make_new():
+    m = MyType.__new__(MyType)
+    m = MyOtherType.__new__(MyOtherType)
+    return m
+
+def make_new_error():
+    m = MySubType.__new__(MyType)
+    m = MyOtherType.__new__(MyType)
+    m = MyOtherType.__new__(MySubType)
+    return m
+
+_ERRORS = """
+20:32: MySubType.__new__(MyType) is not safe, use MyType.__new__()
+21:34: MyOtherType.__new__(MyType) is not safe, use MyType.__new__()
+22:37: MyOtherType.__new__(MySubType) is not safe, use MySubType.__new__()
+"""
diff --git a/tests/run/tp_new.pyx b/tests/run/tp_new.pyx
new file mode 100644 (file)
index 0000000..87ad202
--- /dev/null
@@ -0,0 +1,113 @@
+
+cimport cython
+
+cdef class MyType:
+    def __init__(self):
+        print "INIT"
+
+cdef class MySubType(MyType):
+    def __init__(self):
+        print "INIT"
+
+class MyClass(object):
+    def __init__(self):
+        print "INIT"
+
+class MyTypeSubClass(MyType):
+    def __init__(self):
+        print "INIT"
+
+# only this can be safely optimised:
+
+@cython.test_assert_path_exists('//PythonCapiCallNode')
+@cython.test_fail_if_path_exists('//SimpleCallNode/AttributeNode')
+def make_new():
+    """
+    >>> isinstance(make_new(), MyType)
+    True
+    """
+    m = MyType.__new__(MyType)
+    return m
+
+@cython.test_assert_path_exists('//PythonCapiCallNode')
+@cython.test_fail_if_path_exists('//SimpleCallNode/AttributeNode')
+def make_new_builtin():
+    """
+    >>> isinstance(make_new_builtin(), tuple)
+    True
+    """
+    m = dict.__new__(dict)
+    m = list.__new__(list)
+    m = tuple.__new__(tuple)
+    return m
+
+# these cannot:
+
+@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode')
+@cython.test_fail_if_path_exists('//PythonCapiCallNode')
+def make_new_pyclass():
+    """
+    >>> isinstance(make_new_pyclass(), MyTypeSubClass)
+    True
+    """
+    m = MyClass.__new__(MyClass)
+    m = MyTypeSubClass.__new__(MyTypeSubClass)
+    return m
+
+@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode')
+@cython.test_fail_if_path_exists('//PythonCapiCallNode')
+def make_new_args(type t1=None, type t2=None):
+    """
+    >>> isinstance(make_new_args(), MyType)
+    True
+    >>> isinstance(make_new_args(MyType), MyType)
+    True
+    >>> isinstance(make_new_args(MyType, MyType), MyType)
+    True
+
+    >>> isinstance(make_new_args(MyType, MySubType), MySubType)
+    Traceback (most recent call last):
+    TypeError: tp_new.MyType.__new__(tp_new.MySubType) is not safe, use tp_new.MySubType.__new__()
+    >>> isinstance(make_new_args(MySubType, MyType), MyType)
+    Traceback (most recent call last):
+    TypeError: tp_new.MySubType.__new__(tp_new.MyType): tp_new.MyType is not a subtype of tp_new.MySubType
+    """
+    if t1 is None:
+        t1 = MyType
+    if t2 is None:
+        t2 = MyType
+    m = t1.__new__(t2)
+    return m
+
+@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode')
+@cython.test_fail_if_path_exists('//PythonCapiCallNode')
+def make_new_none(type t1=None, type t2=None):
+    """
+    >>> isinstance(make_new_none(), MyType)
+    Traceback (most recent call last):
+    TypeError: object.__new__(X): X is not a type object (NoneType)
+    """
+    m = t1.__new__(t2)
+    return m
+
+@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode')
+@cython.test_fail_if_path_exists('//PythonCapiCallNode')
+def make_new_none_typed(tuple t1=None, tuple t2=None):
+    """
+    >>> isinstance(make_new_none(), MyType)
+    Traceback (most recent call last):
+    TypeError: object.__new__(X): X is not a type object (NoneType)
+    """
+    m = t1.__new__(t2)
+    return m
+
+@cython.test_assert_path_exists('//SimpleCallNode/AttributeNode')
+@cython.test_fail_if_path_exists('//PythonCapiCallNode')
+def make_new_untyped(t):
+    """
+    >>> make_new_untyped(None)
+    Traceback (most recent call last):
+    TypeError: object.__new__(X): X is not a type object (NoneType)
+    """
+    m = t.__new__(t)
+    return m