type narrowing for cdef methods
authorRobert Bradshaw <robertwb@math.washington.edu>
Fri, 1 Jun 2007 00:47:16 +0000 (17:47 -0700)
committerRobert Bradshaw <robertwb@math.washington.edu>
Fri, 1 Jun 2007 00:47:16 +0000 (17:47 -0700)
Now if you inherit cdef methods from another class, you may re-declare the
arguments and return variables to be sub-types of the original declared type.
This will be especially convenient for the SAGE arithmetic architecture.
Type-checking is performed if necessary.

Cython/Compiler/Nodes.py
Cython/Compiler/PyrexTypes.py
Cython/Compiler/Symtab.py

index ebbe9f3c9df746e6581e7a899c458f4290af6fbe..5f8d83f743cb25e6852cf4d73894fad24b42c522 100644 (file)
@@ -1944,7 +1944,7 @@ class CFuncDefNode(FuncDefNode):
         for arg in self.type.args:
             if not arg.name:
                 error(arg.pos, "Missing argument name")
-            self.declare_argument(env, arg)
+            arg.entry = self.declare_argument(env, arg)
             
     def generate_function_header(self, code, with_pymethdef):
         arg_decls = []
@@ -1989,7 +1989,27 @@ class CFuncDefNode(FuncDefNode):
         pass
     
     def generate_argument_type_tests(self, code):
-        pass
+        # Generate type tests for args whose type in a parent
+        # class is a supertype of the declared type.
+        for arg in self.type.args:
+            if arg.needs_type_test:
+                self.generate_arg_type_test(arg, code)
+    
+    def generate_arg_type_test(self, arg, code):
+        # Generate type test for one argument.
+        if arg.type.typeobj_is_available():
+            typeptr_cname = arg.type.typeptr_cname
+            arg_code = "((PyObject *)%s)" % arg.entry.cname
+            code.putln(
+                'if (!__Pyx_ArgTypeTest(%s, %s, %d, "%s")) %s' % (
+                    arg_code, 
+                    typeptr_cname,
+                    not arg.not_none,
+                    arg.name,
+                    code.error_goto(arg.pos)))
+        else:
+            error(arg.pos, "Cannot test type of extern C class "
+                "without type object name specification")
     
     def error_value(self):
         if self.return_type.is_pyobject:
@@ -3804,6 +3824,9 @@ static int __Pyx_GetVtable(PyObject *dict, void *vtabptr); /*proto*/
 static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, char *modname); /*proto*/
 static int __Pyx_InternStrings(__Pyx_InternTabEntry *t); /*proto*/
 static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /*proto*/
+
+#DEFINE __Pyx_PyBool_FromLong(b) ((b) ? (Py_INCREF(Py_True), Py_True) : (Py_INCREF(Py_False), Py_False))
+#DEFINE __Pyx_PyObject_IsTrue(x) ({PyObject *_x = (x); _x == Py_True ? 1 : (_x) == Py_False ? 0 : PyObject_IsTrue(_x)})
 """
 
 get_name_predeclaration = \
index 1cad073580202bc8351e0fade09e555098e7043f..fba2f78bfec583b59ce1d33e90a2db04fab658e5 100644 (file)
@@ -347,8 +347,8 @@ class CBIntType(CIntType):
 
     # TODO: this should be a macro "(__ ? Py_True : Py_False)"
     #       and no error checking should be needed (just an incref). 
-    to_py_function = "PyBool_FromLong"
-    from_py_function = "PyObject_IsTrue"
+    to_py_function = "__Pyx_PyBool_FromLong"
+    from_py_function = "__Pyx_PyObject_IsTrue"
     
 
 class CPySSizeTType(CIntType):
@@ -528,6 +528,30 @@ class CFuncType(CType):
         if not self.return_type.same_as(other_type.return_type):
             return 0
         return 1
+        
+    def narrower_c_signature_than(self, other_type, as_cmethod = 0):
+        return self.narrower_c_signature_than_resolved_type(other_type.resolve(), as_cmethod)
+        
+    def narrower_c_signature_than_resolved_type(self, other_type, as_cmethod):
+        if other_type is error_type:
+            return 1
+        if not other_type.is_cfunction:
+            return 0
+        nargs = len(self.args)
+        if nargs <> len(other_type.args):
+            return 0
+        for i in range(as_cmethod, nargs):
+            if not self.args[i].type.subtype_of_resolved_type(other_type.args[i].type):
+                return 0
+            else:
+                self.args[i].needs_type_test = other_type.args[i].needs_type_test \
+                        or not self.args[i].type.same_as(other_type.args[i].type)
+        if self.has_varargs <> other_type.has_varargs:
+            return 0
+        if not self.return_type.subtype_of_resolved_type(other_type.return_type):
+            return 0
+        return 1
+
     
     def same_exception_signature_as(self, other_type):
         return self.same_exception_signature_as_resolved_type(
@@ -576,6 +600,8 @@ class CFuncTypeArg:
         self.cname = Naming.var_prefix + name
         self.type = type
         self.pos = pos
+        self.not_none = False
+        self.needs_type_test = False # TODO: should these defaults be set in analyse_types()?
     
     def __repr__(self):
         return "%s:%s" % (self.name, repr(self.type))
index 515225d93554c2d5659f8f0815870e204c607d55..2e97dfcd6e7e6ee0f2e748502f3d0e0f3815f04a 100644 (file)
@@ -1075,7 +1075,11 @@ class CClassScope(ClassScope):
                 if defining and entry.func_cname:
                     error(pos, "'%s' already defined" % name)
                 if not entry.type.same_as(type, as_cmethod = 1):
-                    error(pos, "Signature does not match previous declaration")
+                    old_type = entry.type
+                    if type.narrower_c_signature_than(entry.type, as_cmethod = 1):
+                        entry.type = type
+                    else:
+                        error(pos, "Signature not compatible with previous declaration")
         else:
             if self.defined:
                 error(pos,