minor refactoring; fix handling first argument in classmethods; fix ticket #462:...
authorStefan Behnel <scoder@users.berlios.de>
Sat, 5 Dec 2009 22:39:57 +0000 (23:39 +0100)
committerStefan Behnel <scoder@users.berlios.de>
Sat, 5 Dec 2009 22:39:57 +0000 (23:39 +0100)
Cython/Compiler/Nodes.py
tests/bugs.txt
tests/run/cdef_classmethod.pyx [new file with mode: 0644]
tests/run/cdef_methods_T462.pyx

index c2ed69239f13e5a0afec03c0d5684b98e7b3e6c5..403cfdc5105a4334572f7be4aadc6d0bd221f290 100644 (file)
@@ -671,7 +671,7 @@ class CSimpleBaseTypeNode(CBaseTypeNode):
     # longness         integer
     # complex          boolean
     # is_self_arg      boolean      Is self argument of C method
-    # is_type_arg      boolean      Is type argument of class method
+    # ##is_type_arg      boolean      Is type argument of class method
 
     child_attrs = []
     arg_name = None   # in case the argument name was interpreted as a type
@@ -690,6 +690,8 @@ class CSimpleBaseTypeNode(CBaseTypeNode):
             if self.is_self_arg and env.is_c_class_scope:
                 #print "CSimpleBaseTypeNode.analyse: defaulting to parent type" ###
                 type = env.parent_type
+            ## elif self.is_type_arg and env.is_c_class_scope:
+            ##     type = Builtin.type_type
             else:
                 type = py_object_type
         else:
@@ -706,6 +708,8 @@ class CSimpleBaseTypeNode(CBaseTypeNode):
                 elif could_be_name:
                     if self.is_self_arg and env.is_c_class_scope:
                         type = env.parent_type
+                    ## elif self.is_type_arg and env.is_c_class_scope:
+                    ##     type = Builtin.type_type
                     else:
                         type = py_object_type
                     self.arg_name = self.name
@@ -1599,7 +1603,7 @@ class DefNode(FuncDefNode):
     decorators = None
     entry = None
     acquire_gil = 0
-    
+    self_in_stararg = 0
 
     def __init__(self, pos, **kwds):
         FuncDefNode.__init__(self, pos, **kwds)
@@ -1675,6 +1679,28 @@ class DefNode(FuncDefNode):
                             directive_locals = getattr(cfunc, 'directive_locals', {}))
     
     def analyse_declarations(self, env):
+        self.is_classmethod = self.is_staticmethod = False
+        if self.decorators:
+            for decorator in self.decorators:
+                func = decorator.decorator
+                if func.is_name:
+                    self.is_classmethod |= func.name == 'classmethod'
+                    self.is_staticmethod |= func.name == 'staticmethod'
+
+        if self.is_classmethod and env.lookup_here('classmethod'):
+            # classmethod() was overridden - not much we can do here ...
+            self.is_classmethod = False
+        if self.is_staticmethod and env.lookup_here('staticmethod'):
+            # staticmethod() was overridden - not much we can do here ...
+            self.is_staticmethod = False
+
+        self.analyse_argument_types(env)
+        self.declare_pyfunction(env)
+        self.analyse_signature(env)
+        self.return_type = self.entry.signature.return_type()
+        self.create_local_scope(env)
+
+    def analyse_argument_types(self, env):
         directive_locals = self.directive_locals = env.directives['locals']
         for arg in self.args:
             if hasattr(arg, 'name'):
@@ -1707,27 +1733,8 @@ class DefNode(FuncDefNode):
             if arg.not_none and not arg.type.is_extension_type:
                 error(self.pos,
                     "Only extension type arguments can have 'not None'")
-        self.declare_pyfunction(env)
-        self.analyse_signature(env)
-        self.return_type = self.entry.signature.return_type()
-        self.create_local_scope(env)
 
     def analyse_signature(self, env):
-        self.is_classmethod = self.is_staticmethod = False
-        if self.decorators:
-            for decorator in self.decorators:
-                func = decorator.decorator
-                if func.is_name:
-                    self.is_classmethod |= func.name == 'classmethod'
-                    self.is_staticmethod |= func.name == 'staticmethod'
-
-        if self.is_classmethod and env.lookup_here('classmethod'):
-            # classmethod() was overridden - not much we can do here ...
-            self.is_classmethod = False
-        if self.is_staticmethod and env.lookup_here('staticmethod'):
-            # staticmethod() was overridden - not much we can do here ...
-            self.is_staticmethod = False
-
         any_type_tests_needed = 0
         if self.entry.is_special:
             self.entry.trivial_signature = len(self.args) == 1 and not (self.star_arg or self.starstar_arg)
@@ -1745,36 +1752,46 @@ class DefNode(FuncDefNode):
                 elif len(self.args) == 2:
                     if self.args[1].default is None and not self.args[1].kw_only:
                         self.entry.signature = TypeSlots.ibinaryfunc
+
         sig = self.entry.signature
         nfixed = sig.num_fixed_args()
-        for i in range(nfixed):
-            if i < len(self.args):
-                arg = self.args[i]
-                arg.is_generic = 0
-                if sig.is_self_arg(i) and not self.is_staticmethod:
-                    if self.is_classmethod:
-                        arg.is_type_arg = 1
-                        arg.hdr_type = arg.type = Builtin.type_type
-                    else:
-                        arg.is_self_arg = 1
-                        arg.hdr_type = arg.type = env.parent_type
-                    arg.needs_conversion = 0
-                else:
-                    arg.hdr_type = sig.fixed_arg_type(i)
-                    if not arg.type.same_as(arg.hdr_type):
-                        if arg.hdr_type.is_pyobject and arg.type.is_pyobject:
-                            arg.needs_type_test = 1
-                            any_type_tests_needed = 1
-                        else:
-                            arg.needs_conversion = 1
-                if arg.needs_conversion:
-                    arg.hdr_cname = Naming.arg_prefix + arg.name
+        if sig is TypeSlots.pymethod_signature and nfixed == 1 \
+               and len(self.args) == 0 and self.star_arg:
+            # this is the only case where a diverging number of
+            # arguments is not an error - when we have no explicit
+            # 'self' parameter as in method(*args)
+            sig = self.entry.signature = TypeSlots.pyfunction_signature # self is not 'really' used
+            self.self_in_stararg = 1
+            nfixed = 0
+
+        for i in range(min(nfixed, len(self.args))):
+            arg = self.args[i]
+            arg.is_generic = 0
+            if sig.is_self_arg(i) and not self.is_staticmethod:
+                if self.is_classmethod:
+                    arg.is_type_arg = 1
+                    arg.hdr_type = arg.type = Builtin.type_type
                 else:
-                    arg.hdr_cname = Naming.var_prefix + arg.name
+                    arg.is_self_arg = 1
+                    arg.hdr_type = arg.type = env.parent_type
+                arg.needs_conversion = 0
             else:
-                self.bad_signature()
-                return
-        if nfixed < len(self.args):
+                arg.hdr_type = sig.fixed_arg_type(i)
+                if not arg.type.same_as(arg.hdr_type):
+                    if arg.hdr_type.is_pyobject and arg.type.is_pyobject:
+                        arg.needs_type_test = 1
+                        any_type_tests_needed = 1
+                    else:
+                        arg.needs_conversion = 1
+            if arg.needs_conversion:
+                arg.hdr_cname = Naming.arg_prefix + arg.name
+            else:
+                arg.hdr_cname = Naming.var_prefix + arg.name
+
+        if nfixed > len(self.args):
+            self.bad_signature()
+            return
+        elif nfixed < len(self.args):
             if not sig.has_generic_args:
                 self.bad_signature()
             for arg in self.args:
@@ -1802,7 +1819,9 @@ class DefNode(FuncDefNode):
 
     def signature_has_nongeneric_args(self):
         argcount = len(self.args)
-        if argcount == 0 or (argcount == 1 and self.args[0].is_self_arg):
+        if argcount == 0 or (
+                argcount == 1 and (self.args[0].is_self_arg or
+                                   self.args[0].is_type_arg)):
             return 0
         return 1
 
@@ -1845,7 +1864,7 @@ class DefNode(FuncDefNode):
             arg.entry.used = 1
             arg.entry.is_self_arg = arg.is_self_arg
             if arg.hdr_type:
-                if arg.is_self_arg or \
+                if arg.is_self_arg or arg.is_type_arg or \
                     (arg.type.is_extension_type and not arg.hdr_type.is_extension_type):
                         arg.entry.is_declared_generic = 1
         self.declare_python_arg(env, self.star_arg)
@@ -1881,12 +1900,12 @@ class DefNode(FuncDefNode):
     def generate_function_header(self, code, with_pymethdef, proto_only=0):
         arg_code_list = []
         sig = self.entry.signature
-        if sig.has_dummy_arg:
+        if sig.has_dummy_arg or self.self_in_stararg:
             arg_code_list.append(
                 "PyObject *%s" % Naming.self_cname)
         for arg in self.args:
             if not arg.is_generic:
-                if arg.is_self_arg:
+                if arg.is_self_arg or arg.is_type_arg:
                     arg_code_list.append("PyObject *%s" % arg.hdr_cname)
                 else:
                     arg_code_list.append(
@@ -1941,7 +1960,7 @@ class DefNode(FuncDefNode):
     def generate_argument_parsing_code(self, env, code):
         # Generate PyArg_ParseTuple call for generic
         # arguments, if any.
-        if self.entry.signature.has_dummy_arg:
+        if self.entry.signature.has_dummy_arg and not self.self_in_stararg:
             # get rid of unused argument warning
             code.putln("%s = %s;" % (Naming.self_cname, Naming.self_cname))
 
@@ -1974,14 +1993,14 @@ class DefNode(FuncDefNode):
                 arg_entry = arg.entry
                 if arg.is_generic:
                     if arg.default:
-                        if not arg.is_self_arg:
+                        if not arg.is_self_arg and not arg.is_type_arg:
                             if arg.kw_only:
                                 kw_only_args.append(arg)
                             else:
                                 positional_args.append(arg)
                     elif arg.kw_only:
                         kw_only_args.append(arg)
-                    elif not arg.is_self_arg:
+                    elif not arg.is_self_arg and not arg.is_type_arg:
                         positional_args.append(arg)
 
             self.generate_tuple_and_keyword_parsing_code(
@@ -2061,9 +2080,38 @@ class DefNode(FuncDefNode):
                     self.starstar_arg.entry.cname, self.error_value()))
             self.starstar_arg.entry.xdecref_cleanup = 0
             code.put_gotref(self.starstar_arg.entry.cname)
-            
 
-        if self.star_arg:
+        if self.self_in_stararg:
+            # need to create a new tuple with 'self' inserted as first item
+            code.put("%s = PyTuple_New(PyTuple_GET_SIZE(%s)+1); if (unlikely(!%s)) " % (
+                    self.star_arg.entry.cname,
+                    Naming.args_cname,
+                    self.star_arg.entry.cname))
+            if self.starstar_arg:
+                code.putln("{")
+                code.put_decref(self.starstar_arg.entry.cname, py_object_type)
+                code.putln("return %s;" % self.error_value())
+                code.putln("}")
+            else:
+                code.putln("return %s;" % self.error_value())
+            code.put_gotref(self.star_arg.entry.cname)
+            code.put_incref(Naming.self_cname, py_object_type)
+            code.put_giveref(Naming.self_cname)
+            code.putln("PyTuple_SET_ITEM(%s, 0, %s);" % (
+                self.star_arg.entry.cname, Naming.self_cname))
+            temp = code.funcstate.allocate_temp(PyrexTypes.c_py_ssize_t_type, manage_ref=False)
+            code.putln("for (%s=0; %s < PyTuple_GET_SIZE(%s); %s++) {" % (
+                temp, temp, Naming.args_cname, temp))
+            code.putln("PyObject* item = PyTuple_GET_ITEM(%s, %s);" % (
+                Naming.args_cname, temp))
+            code.put_incref("item", py_object_type)
+            code.put_giveref("item")
+            code.putln("PyTuple_SET_ITEM(%s, %s+1, item);" % (
+                self.star_arg.entry.cname, temp))
+            code.putln("}")
+            code.funcstate.release_temp(temp)
+            self.star_arg.entry.xdecref_cleanup = 0
+        elif self.star_arg:
             code.put_incref(Naming.args_cname, py_object_type)
             code.putln("%s = %s;" % (
                     self.star_arg.entry.cname,
index b41b895a4a70079cfd1af094de156971328776f6..55e7e2c3df15948183cbb2335604ab9c3cdb361e 100644 (file)
@@ -7,4 +7,3 @@ numpy_ValueError_T172
 unsignedbehaviour_T184
 missing_baseclass_in_predecl_T262
 cfunc_call_tuple_args_T408
-cdef_methods_T462
diff --git a/tests/run/cdef_classmethod.pyx b/tests/run/cdef_classmethod.pyx
new file mode 100644 (file)
index 0000000..86891e4
--- /dev/null
@@ -0,0 +1,76 @@
+
+cimport cython
+
+cdef class cclass:
+
+    @classmethod
+    def test0(cls):
+        """
+        >>> cclass.test0()
+        'type object'
+        """
+        return cython.typeof(cls)
+
+    @classmethod
+    def test0_args(*args):
+        """
+        >>> cclass.test0_args(1,2,3)
+        ('Python object', (1, 2, 3))
+        """
+        return cython.typeof(args[0]), args[1:]
+
+    @classmethod
+    def test1(cls, arg):
+        """
+        >>> cclass.test1(1)
+        ('type object', 1)
+        """
+        return cython.typeof(cls), arg
+
+    @classmethod
+    def test2(cls, arg1, arg2):
+        """
+        >>> cclass.test2(1,2)
+        ('type object', 1, 2)
+        """
+        return cython.typeof(cls), arg1, arg2
+
+    @classmethod
+    def test1_args(cls, *args):
+        """
+        >>> cclass.test1_args(1,2,3)
+        ('type object', (1, 2, 3))
+        """
+        return cython.typeof(cls), args
+
+    @classmethod
+    def test2_args(cls, arg, *args):
+        """
+        >>> cclass.test2_args(1,2,3)
+        ('type object', 1, (2, 3))
+        """
+        return cython.typeof(cls), arg, args
+
+    @classmethod
+    def test0_args_kwargs(*args, **kwargs):
+        """
+        >>> cclass.test0_args_kwargs(1,2,3)
+        ('Python object', (1, 2, 3), {})
+        """
+        return cython.typeof(args[0]), args[1:], kwargs
+
+    @classmethod
+    def test1_args_kwargs(cls, *args, **kwargs):
+        """
+        >>> cclass.test1_args_kwargs(1,2,3)
+        ('type object', (1, 2, 3), {})
+        """
+        return cython.typeof(cls), args, kwargs
+
+    @classmethod
+    def test2_args_kwargs(cls, arg, *args, **kwargs):
+        """
+        >>> cclass.test2_args_kwargs(1,2,3)
+        ('type object', 1, (2, 3), {})
+        """
+        return cython.typeof(cls), arg, args, kwargs
index 7a5bc340b8119c82aada522776488a8f74593ea3..24440ddf03de68f46132dc46775cd8c514fab7f7 100644 (file)
@@ -12,20 +12,27 @@ cdef class cclass:
     def test_self_1(self, arg):
         """
         >>> cclass().test_self_1(1)
-        ('cclass', (1,))
+        ('cclass', 1)
         """
         return cython.typeof(self), arg
 
     def test_self_args(self, *args):
         """
-        >>> cclass().normal_test1_args(1,2,3)
+        >>> cclass().test_self_args(1,2,3)
         ('cclass', (1, 2, 3))
         """
         return cython.typeof(self), args
 
     def test_args(*args):
         """
-        >>> cclass().normal_test0_args(1,2,3):
-        ("Python object", (1, 2, 3))
+        >>> cclass().test_args(1,2,3)
+        ('Python object', (1, 2, 3))
         """
         return cython.typeof(args[0]), args[1:]
+
+    def test_args_kwargs(*args, **kwargs):
+        """
+        >>> cclass().test_args_kwargs(1,2,3, a=4)
+        ('Python object', (1, 2, 3), {'a': 4})
+        """
+        return cython.typeof(args[0]), args[1:], kwargs