Commits

Armin Rigo committed 010bdb0

Finally clean up the distinction between the C types "function" vs
"pointer to function". Add a test about it to backend_tests.

  • Participants
  • Parent commits 7cf76f8

Comments (0)

Files changed (9)

                           &ellipsis))
         return NULL;
 
-    if (fresult->ct_size < 0 && !(fresult->ct_flags & CT_VOID)) {
-        PyErr_SetString(PyExc_TypeError, "result type is of unknown size");
+    if (fresult->ct_flags & (CT_STRUCT|CT_UNION)) {
+        PyErr_SetString(PyExc_NotImplementedError,
+                        "functions returning a struct or a union");
+        return NULL;
+    }
+    if ((fresult->ct_size < 0 && !(fresult->ct_flags & CT_VOID)) ||
+        (fresult->ct_flags & CT_ARRAY)) {
+        PyErr_Format(PyExc_TypeError, "invalid result type: '%s'",
+                     fresult->ct_name);
         return NULL;
     }
 
     assert list(p.a1) == ['f', 'o', 'o'] + ['\x00'] * 7
     p.a1 = ['x', 'y']
     assert str(p.a1) == 'xyo'
+
+def test_no_struct_return_in_func():
+    BFunc = new_function_type((), new_void_type())
+    BArray = new_array_type(new_pointer_type(BFunc), 5)        # works
+    new_function_type((), BFunc)    # works
+    new_function_type((), new_primitive_type("int"))
+    new_function_type((), new_pointer_type(BFunc))
+    py.test.raises(NotImplementedError, new_function_type, (),
+                   new_struct_type("foo_s"))
+    py.test.raises(NotImplementedError, new_function_type, (),
+                   new_union_type("foo_u"))
+    py.test.raises(TypeError, new_function_type, (), BArray)
         assert isinstance(name, str) or name is None
         return _make_ffi_library(self, name)
 
-    def typeof(self, cdecl):
+    def typeof(self, cdecl, consider_function_as_funcptr=False):
         """Parse the C type given as a string and return the
         corresponding Python type: <class 'ffi.CData<...>'>.
         It can also be used on 'cdata' instance to get its C type.
         """
         if isinstance(cdecl, basestring):
             try:
-                return self._parsed_types[cdecl]
+                btype, cfaf = self._parsed_types[cdecl]
+                if consider_function_as_funcptr and not cfaf:
+                    raise KeyError
             except KeyError:
-                type = self._parser.parse_type(cdecl)
+                cfaf = consider_function_as_funcptr
+                type = self._parser.parse_type(cdecl,
+                           consider_function_as_funcptr=cfaf)
                 btype = self._get_cached_btype(type)
-                self._parsed_types[cdecl] = btype
-                return btype
+                self._parsed_types[cdecl] = btype, cfaf
+            return btype
         else:
             return self._backend.typeof(cdecl)
 
         """
         if not callable(python_callable):
             raise TypeError("the 'python_callable' argument is not callable")
-        BFunc = self.typeof(cdecl)
+        BFunc = self.typeof(cdecl, consider_function_as_funcptr=True)
         return self._backend.callback(BFunc, python_callable, error)
 
     def getctype(self, cdecl, replace_with=''):

cffi/backend_ctypes.py

             nameargs.append('...')
         nameargs = ', '.join(nameargs)
         #
-        class CTypesFunction(CTypesGenericPtr):
+        class CTypesFunctionPtr(CTypesGenericPtr):
             __slots__ = ['_own_callback', '_name']
             _ctype = ctypes.CFUNCTYPE(getattr(BResult, '_ctype', None),
                                       *[BArg._ctype for BArg in BArgs],
                         *[BArg._ctype for BArg in BArgs],
                         use_errno=True)
                 else:
-                    callback_ctype = CTypesFunction._ctype
+                    callback_ctype = CTypesFunctionPtr._ctype
                 self._as_ctype_ptr = callback_ctype(callback)
                 self._address = ctypes.cast(self._as_ctype_ptr,
                                             ctypes.c_void_p).value
                 self._own_callback = init
 
+            @staticmethod
+            def _initialize(ctypes_ptr, value):
+                if value:
+                    raise NotImplementedError("ctypes backend: not supported: "
+                                          "initializers for function pointers")
+
             def __repr__(self):
                 c_name = getattr(self, '_name', None)
                 if c_name:
             def _get_own_repr(self):
                 if getattr(self, '_own_callback', None) is not None:
                     return 'calling %r' % (self._own_callback,)
-                return super(CTypesFunction, self)._get_own_repr()
+                return super(CTypesFunctionPtr, self)._get_own_repr()
 
             def __call__(self, *args):
                 if has_varargs:
                 result = self._as_ctype_ptr(*ctypes_args)
                 return BResult._from_ctypes(result)
         #
-        CTypesFunction._fix_class()
-        return CTypesFunction
+        CTypesFunctionPtr._fix_class()
+        return CTypesFunctionPtr
 
     def new_enum_type(self, name, enumerators, enumvalues):
         mapping = dict(zip(enumerators, enumvalues))
     def _parse_decl(self, decl):
         node = decl.type
         if isinstance(node, pycparser.c_ast.FuncDecl):
-            self._declare('function ' + decl.name,
-                          self._get_type(node, name=decl.name))
+            tp = self._get_type(node, name=decl.name)
+            assert isinstance(tp, model.RawFunctionType)
+            tp = self._get_type_pointer(tp)
+            self._declare('function ' + decl.name, tp)
         else:
             if isinstance(node, pycparser.c_ast.Struct):
                 # XXX do we need self._declare in any of those?
                 else:
                     self._declare('variable ' + decl.name, tp)
 
-    def parse_type(self, cdecl, force_pointer=False):
+    def parse_type(self, cdecl, force_pointer=False,
+                   consider_function_as_funcptr=False):
         ast, macros = self._parse('void __dummy(%s);' % cdecl)
         assert not macros
         typenode = ast.ext[-1].type.args.params[0].type
-        return self._get_type(typenode, force_pointer=force_pointer)
+        type = self._get_type(typenode, force_pointer=force_pointer)
+        if consider_function_as_funcptr:
+            if isinstance(type, model.RawFunctionType):
+                type = self._get_type_pointer(type)
+        return type
 
     def _declare(self, name, obj):
         if name in self._declarations:
         self._declarations[name] = obj
 
     def _get_type_pointer(self, type, const=False):
-        if isinstance(type, model.FunctionType):
-            return type # "pointer-to-function" ~== "function"
+        if isinstance(type, model.RawFunctionType):
+            return model.FunctionPtrType(type.args, type.result, type.ellipsis)
         if const:
             return model.ConstPointerType(type)
         return model.PointerType(type)
                                convert_array_to_pointer=True)
                 for argdeclnode in params]
         result = self._get_type(typenode.type)
-        return model.FunctionType(tuple(args), result, ellipsis)
+        return model.RawFunctionType(tuple(args), result, ellipsis)
 
     def _is_constant_declaration(self, typenode, const=False):
         if isinstance(typenode, pycparser.c_ast.ArrayDecl):
         return ffi._backend.new_primitive_type(self.name)
 
 
-class FunctionType(BaseType):
+class RawFunctionType(BaseType):
+    # Corresponds to a C type like 'int(int)', which is the C type of
+    # a function, but not a pointer-to-function.  The backend has no
+    # notion of such a type; it's used temporarily by parsing.
     _attrs_ = ('args', 'result', 'ellipsis')
 
     def __init__(self, args, result, ellipsis):
         if self.ellipsis:
             reprargs.append('...')
         reprargs = reprargs or ['void']
-        replace_with = '(*%s)(%s)' % (replace_with, ', '.join(reprargs))
+        replace_with = '(%s)(%s)' % (replace_with, ', '.join(reprargs))
         return self.result._get_c_name(replace_with)
 
     def prepare_backend_type(self, ffi):
+        from . import api
+        raise api.CDefError("cannot render the type %r: it is a function "
+                            "type, not a pointer-to-function type" % (self,))
+
+
+class FunctionPtrType(RawFunctionType):
+
+    def _get_c_name(self, replace_with):
+        return RawFunctionType._get_c_name(self, '*'+replace_with)
+
+    def prepare_backend_type(self, ffi):
         args = [ffi._get_cached_btype(self.result)]
         for tp in self.args:
             args.append(ffi._get_cached_btype(tp))
     def convert_expr_from_c(self, tp, var):
         if isinstance(tp, model.PrimitiveType):
             return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var)
-        elif isinstance(tp, (model.PointerType, model.FunctionType)):
+        elif isinstance(tp, (model.PointerType, model.FunctionPtrType)):
             return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % (
                 var, self.gettypenum(tp))
         elif isinstance(tp, model.ArrayType):
     # function declarations
 
     def generate_cpy_function_decl(self, tp, name):
-        assert isinstance(tp, model.FunctionType)
+        assert isinstance(tp, model.FunctionPtrType)
         if tp.ellipsis:
             # cannot support vararg functions better than this: check for its
             # exact type (including the fixed arguments), and build it as a

testing/backend_tests.py

 import py
 import sys, ctypes
-from cffi import FFI
+from cffi import FFI, CDefError
 
 SIZE_OF_INT   = ctypes.sizeof(ctypes.c_int)
 SIZE_OF_LONG  = ctypes.sizeof(ctypes.c_long)
         assert ffi.getctype("int[5]", '*') == "int(*)[5]"
         assert ffi.getctype("int[5]", '*foo') == "int(*foo)[5]"
         assert ffi.getctype("int[5]", ' ** foo ') == "int(** foo)[5]"
+
+    def test_array_of_func_ptr(self):
+        ffi = FFI(backend=self.Backend())
+        f = ffi.cast("int(*)(int)", 42)
+        assert f != ffi.NULL
+        py.test.raises(CDefError, ffi.cast, "int(int)", 42)
+        py.test.raises(CDefError, ffi.new, "int([5])(int)")
+        a = ffi.new("int(*[5])(int)", [f])
+        assert ffi.getctype(ffi.typeof(a)) == "int(*[5])(int)"
+        assert len(a) == 5
+        assert a[0] == f
+        assert a[1] == ffi.NULL
+        py.test.raises(TypeError, ffi.cast, "int(*)(int)[5]", 0)
+        #
+        def cb(n):
+            return n + 1
+        f = ffi.callback("int(*)(int)", cb)
+        a = ffi.new("int(*[5])(int)", [f, f])
+        assert a[1](42) == 43

testing/test_ctypes.py

+import py
 from testing import backend_tests
 from cffi.backend_ctypes import CTypesBackend
 
     
     Backend = CTypesBackend
     TypeRepr = "<class 'ffi.CData<%s>'>"
+
+    def test_array_of_func_ptr(self):
+        py.test.skip("ctypes backend: not supported: "
+                     "initializers for function pointers")