[pypy-commit] cffi default: Merge branch sirtom67/float_complex, adding support for complex numbers.

arigo pypy.commits at gmail.com
Tue May 30 13:10:26 EDT 2017


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r2948:71fd95269a8f
Date: 2017-05-30 19:10 +0200
http://bitbucket.org/cffi/cffi/changeset/71fd95269a8f/

Log:	Merge branch sirtom67/float_complex, adding support for complex
	numbers. Thanks sirtom67 for doing most of the work!

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -116,36 +116,38 @@
 /************************************************************/
 
 /* base type flag: exactly one of the following: */
-#define CT_PRIMITIVE_SIGNED   1    /* signed integer */
-#define CT_PRIMITIVE_UNSIGNED 2    /* unsigned integer */
-#define CT_PRIMITIVE_CHAR     4    /* char, wchar_t */
-#define CT_PRIMITIVE_FLOAT    8    /* float, double, long double */
-#define CT_POINTER           16    /* pointer, excluding ptr-to-func */
-#define CT_ARRAY             32    /* array */
-#define CT_STRUCT            64    /* struct */
-#define CT_UNION            128    /* union */
-#define CT_FUNCTIONPTR      256    /* pointer to function */
-#define CT_VOID             512    /* void */
+#define CT_PRIMITIVE_SIGNED   0x001   /* signed integer */
+#define CT_PRIMITIVE_UNSIGNED 0x002   /* unsigned integer */
+#define CT_PRIMITIVE_CHAR     0x004   /* char, wchar_t */
+#define CT_PRIMITIVE_FLOAT    0x008   /* float, double, long double */
+#define CT_POINTER            0x010   /* pointer, excluding ptr-to-func */
+#define CT_ARRAY              0x020   /* array */
+#define CT_STRUCT             0x040   /* struct */
+#define CT_UNION              0x080   /* union */
+#define CT_FUNCTIONPTR        0x100   /* pointer to function */
+#define CT_VOID               0x200   /* void */
+#define CT_PRIMITIVE_COMPLEX  0x400   /* float _Complex, double _Complex */
 
 /* other flags that may also be set in addition to the base flag: */
-#define CT_IS_VOIDCHAR_PTR       1024
-#define CT_PRIMITIVE_FITS_LONG   2048
-#define CT_IS_OPAQUE             4096
-#define CT_IS_ENUM               8192
-#define CT_IS_PTR_TO_OWNED      16384   /* only owned if CDataOwning_Type */
-#define CT_CUSTOM_FIELD_POS     32768
-#define CT_IS_LONGDOUBLE        65536
-#define CT_IS_BOOL             131072
-#define CT_IS_FILE             262144
-#define CT_IS_VOID_PTR         524288
-#define CT_WITH_VAR_ARRAY     1048576
-#define CT_IS_UNSIZED_CHAR_A  2097152
-#define CT_LAZY_FIELD_LIST    4194304
-#define CT_WITH_PACKED_CHANGE 8388608
+#define CT_IS_VOIDCHAR_PTR     0x00001000
+#define CT_PRIMITIVE_FITS_LONG 0x00002000
+#define CT_IS_OPAQUE           0x00004000
+#define CT_IS_ENUM             0x00008000
+#define CT_IS_PTR_TO_OWNED     0x00010000 /* only owned if CDataOwning_Type */
+#define CT_CUSTOM_FIELD_POS    0x00020000
+#define CT_IS_LONGDOUBLE       0x00040000
+#define CT_IS_BOOL             0x00080000
+#define CT_IS_FILE             0x00100000
+#define CT_IS_VOID_PTR         0x00200000
+#define CT_WITH_VAR_ARRAY      0x00400000
+#define CT_IS_UNSIZED_CHAR_A   0x00800000
+#define CT_LAZY_FIELD_LIST     0x01000000
+#define CT_WITH_PACKED_CHANGE  0x02000000
 #define CT_PRIMITIVE_ANY  (CT_PRIMITIVE_SIGNED |        \
                            CT_PRIMITIVE_UNSIGNED |      \
                            CT_PRIMITIVE_CHAR |          \
-                           CT_PRIMITIVE_FLOAT)
+                           CT_PRIMITIVE_FLOAT |         \
+                           CT_PRIMITIVE_COMPLEX)
 
 typedef struct _ctypedescr {
     PyObject_VAR_HEAD
@@ -883,6 +885,26 @@
     return 0;
 }
 
+static Py_complex
+read_raw_complex_data(char *target, int size)
+{
+    Py_complex r = {0.0, 0.0};
+    if (size == 2*sizeof(float)) {
+        float real_part, imag_part;
+        memcpy(&real_part, target + 0,             sizeof(float));
+        memcpy(&imag_part, target + sizeof(float), sizeof(float));
+        r.real = real_part;
+        r.imag = imag_part;
+        return r;
+    }
+    if (size == 2*sizeof(double)) {
+        memcpy(&r, target, 2*sizeof(double));
+        return r;
+    }
+    Py_FatalError("read_raw_complex_data: bad complex size");
+    return r;
+}
+
 static void
 write_raw_float_data(char *target, double source, int size)
 {
@@ -898,6 +920,25 @@
     _write_raw_data(long double);
 }
 
+#define _write_raw_complex_data(type)                      \
+    do {                                                   \
+        if (size == 2*sizeof(type)) {                      \
+            type r = (type)source.real;                    \
+            type i = (type)source.imag;                    \
+            memcpy(target, &r, sizeof(type));              \
+            memcpy(target+sizeof(type), &i, sizeof(type)); \
+            return;                                        \
+        }                                                  \
+    } while(0)
+
+static void
+write_raw_complex_data(char *target, Py_complex source, int size)
+{
+    _write_raw_complex_data(float);
+    _write_raw_complex_data(double);
+    Py_FatalError("write_raw_complex_data: bad complex size");
+}
+
 static PyObject *
 new_simple_cdata(char *data, CTypeDescrObject *ct)
 {
@@ -1015,6 +1056,10 @@
             return _my_PyUnicode_FromWideChar((wchar_t *)data, 1);
 #endif
     }
+    else if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) {
+        Py_complex value = read_raw_complex_data(data, ct->ct_size);
+        return PyComplex_FromCComplex(value);
+    }
 
     PyErr_Format(PyExc_SystemError,
                  "convert_to_object: '%s'", ct->ct_name);
@@ -1519,6 +1564,13 @@
         }
         return convert_struct_from_object(data, ct, init, NULL);
     }
+    if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) {
+        Py_complex value = PyComplex_AsCComplex(init);
+        if (PyErr_Occurred())
+            return -1;
+        write_raw_complex_data(data, value, ct->ct_size);
+        return 0;
+    }
     PyErr_Format(PyExc_SystemError,
                  "convert_from_object: '%s'", ct->ct_name);
     return -1;
@@ -1956,6 +2008,11 @@
                 return read_raw_longdouble_data(cd->c_data) != 0.0;
             return read_raw_float_data(cd->c_data, cd->c_type->ct_size) != 0.0;
         }
+        if (cd->c_type->ct_flags & CT_PRIMITIVE_COMPLEX) {
+            Py_complex value = read_raw_complex_data(cd->c_data,
+                                                     cd->c_type->ct_size);
+            return value.real != 0.0 || value.imag != 0.0;
+        }
     }
     return cd->c_data != NULL;
 }
@@ -2904,6 +2961,24 @@
     }
 }
 
+static PyObject *cdata_complex(PyObject *cd_, PyObject *noarg)
+{
+    CDataObject *cd = (CDataObject *)cd_;
+
+    if (cd->c_type->ct_flags & CT_PRIMITIVE_COMPLEX) {
+        Py_complex value = read_raw_complex_data(cd->c_data, cd->c_type->ct_size);
+        PyObject *op = PyComplex_FromCComplex(value);
+        return op;
+    }
+    /* <cdata 'float'> or <cdata 'int'> cannot be directly converted by
+       calling complex(), just like <cdata 'int'> cannot be directly
+       converted by calling float() */
+
+    PyErr_Format(PyExc_TypeError, "complex() not supported on cdata '%s'",
+                 cd->c_type->ct_name);
+    return NULL;
+}
+
 static PyObject *cdata_iter(CDataObject *);
 
 static PyNumberMethods CData_as_number = {
@@ -2953,8 +3028,9 @@
 };
 
 static PyMethodDef cdata_methods[] = {
-    {"__dir__",   cdata_dir,      METH_NOARGS},
-    {NULL,        NULL}           /* sentinel */
+    {"__dir__",     cdata_dir,      METH_NOARGS},
+    {"__complex__", cdata_complex,  METH_NOARGS},
+    {NULL,          NULL}           /* sentinel */
 };
 
 static PyTypeObject CData_Type = {
@@ -3587,6 +3663,31 @@
     return cd;
 }
 
+/* returns -1 if cannot cast, 0 if we don't get a value, 1 if we do */
+static int check_bytes_for_float_compatible(PyObject *io, double *out_value)
+{
+    if (PyBytes_Check(io)) {
+        if (PyBytes_GET_SIZE(io) != 1) {
+            Py_DECREF(io);
+            return -1;
+        }
+        *out_value = (unsigned char)PyBytes_AS_STRING(io)[0];
+        return 1;
+    }
+#if HAVE_WCHAR_H
+    else if (PyUnicode_Check(io)) {
+        wchar_t ordinal;
+        if (_my_PyUnicode_AsSingleWideChar(io, &ordinal) < 0) {
+            Py_DECREF(io);
+            return -1;
+        }
+        *out_value = (long)ordinal;
+        return 1;
+    }
+#endif
+    return 0;
+}
+
 static PyObject *do_cast(CTypeDescrObject *ct, PyObject *ob)
 {
     CDataObject *cd;
@@ -3628,6 +3729,7 @@
         /* cast to a float */
         double value;
         PyObject *io;
+        int res;
 
         if (CData_Check(ob)) {
             CDataObject *cdsrc = (CDataObject *)ob;
@@ -3643,37 +3745,23 @@
             Py_INCREF(io);
         }
 
-        if (PyBytes_Check(io)) {
-            if (PyBytes_GET_SIZE(io) != 1) {
-                Py_DECREF(io);
-                goto cannot_cast;
-            }
-            value = (unsigned char)PyBytes_AS_STRING(io)[0];
-        }
-#if HAVE_WCHAR_H
-        else if (PyUnicode_Check(io)) {
-            wchar_t ordinal;
-            if (_my_PyUnicode_AsSingleWideChar(io, &ordinal) < 0) {
-                Py_DECREF(io);
-                goto cannot_cast;
-            }
-            value = (long)ordinal;
-        }
-#endif
-        else if ((ct->ct_flags & CT_IS_LONGDOUBLE) &&
+        res = check_bytes_for_float_compatible(io, &value);
+        if (res == -1)
+            goto cannot_cast;
+        if (res == 0) {
+            if ((ct->ct_flags & CT_IS_LONGDOUBLE) &&
                  CData_Check(io) &&
                  (((CDataObject *)io)->c_type->ct_flags & CT_IS_LONGDOUBLE)) {
-            long double lvalue;
-            char *data = ((CDataObject *)io)->c_data;
-            /*READ(data, sizeof(long double)*/
-            lvalue = read_raw_longdouble_data(data);
-            Py_DECREF(io);
-            cd = _new_casted_primitive(ct);
-            if (cd != NULL)
-                write_raw_longdouble_data(cd->c_data, lvalue);
-            return (PyObject *)cd;
-        }
-        else {
+                long double lvalue;
+                char *data = ((CDataObject *)io)->c_data;
+                /*READ(data, sizeof(long double)*/
+                lvalue = read_raw_longdouble_data(data);
+                Py_DECREF(io);
+                cd = _new_casted_primitive(ct);
+                if (cd != NULL)
+                    write_raw_longdouble_data(cd->c_data, lvalue);
+                return (PyObject *)cd;
+            }
             value = PyFloat_AsDouble(io);
         }
         Py_DECREF(io);
@@ -3689,6 +3777,45 @@
         }
         return (PyObject *)cd;
     }
+    else if (ct->ct_flags & CT_PRIMITIVE_COMPLEX) {
+        /* cast to a complex */
+        Py_complex value;
+        PyObject *io;
+        int res;
+
+        if (CData_Check(ob)) {
+            CDataObject *cdsrc = (CDataObject *)ob;
+
+            if (!(cdsrc->c_type->ct_flags & CT_PRIMITIVE_ANY))
+                goto cannot_cast;
+            io = convert_to_object(cdsrc->c_data, cdsrc->c_type);
+            if (io == NULL)
+                return NULL;
+        }
+        else {
+            io = ob;
+            Py_INCREF(io);
+        }
+
+        res = check_bytes_for_float_compatible(io, &value.real);
+        if (res == -1)
+            goto cannot_cast;
+        if (res == 1) {
+            // got it from string
+            value.imag = 0.0;
+        } else {
+            value = PyComplex_AsCComplex(io);
+        }
+        Py_DECREF(io);
+        if (PyErr_Occurred()) {
+            return NULL;
+        }
+        cd = _new_casted_primitive(ct);
+        if (cd != NULL) {
+            write_raw_complex_data(cd->c_data, value, ct->ct_size);
+        }
+        return (PyObject *)cd;
+    }
     else {
         PyErr_Format(PyExc_TypeError, "cannot cast to ctype '%s'",
                      ct->ct_name);
@@ -3954,6 +4081,11 @@
     return NULL;
 }
 
+/* according to the C standard, these types should be equivalent to the
+   _Complex types for the purposes of storage (not arguments in calls!) */
+typedef float cffi_float_complex_t[2];
+typedef double cffi_double_complex_t[2];
+
 static PyObject *new_primitive_type(const char *name)
 {
 #define ENUM_PRIMITIVE_TYPES                                    \
@@ -3971,6 +4103,8 @@
        EPTYPE(f, float, CT_PRIMITIVE_FLOAT )                    \
        EPTYPE(d, double, CT_PRIMITIVE_FLOAT )                   \
        EPTYPE(ld, long double, CT_PRIMITIVE_FLOAT | CT_IS_LONGDOUBLE ) \
+       EPTYPE2(fc, "float _Complex", cffi_float_complex_t, CT_PRIMITIVE_COMPLEX ) \
+       EPTYPE2(dc, "double _Complex", cffi_double_complex_t, CT_PRIMITIVE_COMPLEX ) \
        ENUM_PRIMITIVE_TYPES_WCHAR                               \
        EPTYPE(b, _Bool, CT_PRIMITIVE_UNSIGNED | CT_IS_BOOL )    \
      /* the following types are not primitive in the C sense */ \
@@ -4081,6 +4215,13 @@
         else
             goto bad_ffi_type;
     }
+    else if (ptypes->flags & CT_PRIMITIVE_COMPLEX) {
+        /* As of March 2017, still no libffi support for complex.
+           It fails silently if we try to use ffi_type_complex_float
+           or ffi_type_complex_double.  Better not use it at all.
+         */
+        ffitype = NULL;
+    }
     else {
         switch (ptypes->size) {
         case 1: ffitype = &ffi_type_uint8; break;
@@ -4773,7 +4914,7 @@
 {
     const char *place = is_result_type ? "return value" : "argument";
 
-    if (ct->ct_flags & CT_PRIMITIVE_ANY) {
+    if (ct->ct_flags & (CT_PRIMITIVE_ANY & ~CT_PRIMITIVE_COMPLEX)) {
         return (ffi_type *)ct->ct_extra;
     }
     else if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) {
@@ -4899,9 +5040,16 @@
         return NULL;
     }
     else {
+        char *extra = "";
+        if (ct->ct_flags & CT_PRIMITIVE_COMPLEX)
+            extra = " (the support for complex types inside libffi "
+                    "is mostly missing at this point, so CFFI only "
+                    "supports complex types as arguments or return "
+                    "value in API-mode functions)";
+
         PyErr_Format(PyExc_NotImplementedError,
-                     "ctype '%s' (size %zd) not supported as %s",
-                     ct->ct_name, ct->ct_size, place);
+                     "ctype '%s' (size %zd) not supported as %s%s",
+                     ct->ct_name, ct->ct_size, place, extra);
         return NULL;
     }
 }
@@ -6579,6 +6727,20 @@
     return -42;
 }
 
+#if 0   /* libffi doesn't properly support complexes currently */
+        /* also, MSVC might not support _Complex... */
+        /* if this is enabled one day, remember to also add _Complex
+         * arguments in addition to return values. */
+static float _Complex _testfunc24(float a, float b)
+{
+    return a + I*2.0*b;
+}
+static double _Complex _testfunc25(double a, double b)
+{
+    return a + I*2.0*b;
+}
+#endif
+
 static PyObject *b__testfunc(PyObject *self, PyObject *args)
 {
     /* for testing only */
@@ -6611,6 +6773,10 @@
     case 21: f = &_testfunc21; break;
     case 22: f = &_testfunc22; break;
     case 23: f = &_testfunc23; break;
+#if 0
+    case 24: f = &_testfunc24; break;
+    case 25: f = &_testfunc25; break;
+#endif
     default:
         PyErr_SetNone(PyExc_ValueError);
         return NULL;
diff --git a/c/parse_c_type.c b/c/parse_c_type.c
--- a/c/parse_c_type.c
+++ b/c/parse_c_type.c
@@ -25,7 +25,7 @@
     /* keywords */
     TOK__BOOL,
     TOK_CHAR,
-    //TOK__COMPLEX,
+    TOK__COMPLEX,
     TOK_CONST,
     TOK_DOUBLE,
     TOK_ENUM,
@@ -159,6 +159,7 @@
         if (tok->size == 5 && !memcmp(p, "_Bool", 5))  tok->kind = TOK__BOOL;
         if (tok->size == 7 && !memcmp(p,"__cdecl",7))  tok->kind = TOK_CDECL;
         if (tok->size == 9 && !memcmp(p,"__stdcall",9))tok->kind = TOK_STDCALL;
+        if (tok->size == 8 && !memcmp(p,"_Complex",8)) tok->kind = TOK__COMPLEX;
         break;
     case 'c':
         if (tok->size == 4 && !memcmp(p, "char", 4))   tok->kind = TOK_CHAR;
@@ -601,6 +602,7 @@
 {
     unsigned int t0;
     _cffi_opcode_t t1;
+    _cffi_opcode_t t1complex;
     int modifiers_length, modifiers_sign;
 
  qualifiers:
@@ -656,6 +658,8 @@
         break;
     }
 
+    t1complex = 0;
+
     if (modifiers_length || modifiers_sign) {
 
         switch (tok->kind) {
@@ -666,6 +670,7 @@
         case TOK_STRUCT:
         case TOK_UNION:
         case TOK_ENUM:
+        case TOK__COMPLEX:
             return parse_error(tok, "invalid combination of types");
 
         case TOK_DOUBLE:
@@ -719,9 +724,11 @@
             break;
         case TOK_FLOAT:
             t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_FLOAT);
+            t1complex = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_FLOATCOMPLEX);
             break;
         case TOK_DOUBLE:
             t1 = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_DOUBLE);
+            t1complex = _CFFI_OP(_CFFI_OP_PRIMITIVE, _CFFI_PRIM_DOUBLECOMPLEX);
             break;
         case TOK_IDENTIFIER:
         {
@@ -788,6 +795,13 @@
         }
         next_token(tok);
     }
+    if (tok->kind == TOK__COMPLEX)
+    {
+        if (t1complex == 0)
+            return parse_error(tok, "_Complex type combination unsupported");
+        t1 = t1complex;
+        next_token(tok);
+    }
 
     return parse_sequel(tok, write_ds(tok, t1));
 }
diff --git a/c/realize_c_type.c b/c/realize_c_type.c
--- a/c/realize_c_type.c
+++ b/c/realize_c_type.c
@@ -151,6 +151,8 @@
         "uint_fast64_t",
         "intmax_t",
         "uintmax_t",
+        "float _Complex",
+        "double _Complex",
     };
     PyObject *x;
 
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -185,37 +185,56 @@
         py.test.raises(TypeError, cast, p, None)
 
 def test_complex_types():
-    py.test.skip("later")
     INF = 1E200 * 1E200
     for name in ["float", "double"]:
-        p = new_primitive_type("_Complex " + name)
-        assert bool(cast(p, 0))
+        p = new_primitive_type(name + " _Complex")
+        assert bool(cast(p, 0)) is False
         assert bool(cast(p, INF))
         assert bool(cast(p, -INF))
-        assert bool(cast(p, 0j))
+        assert bool(cast(p, 0j)) is False
         assert bool(cast(p, INF*1j))
         assert bool(cast(p, -INF*1j))
+        # "can't convert complex to float", like CPython's "float(0j)"
         py.test.raises(TypeError, int, cast(p, -150))
         py.test.raises(TypeError, long, cast(p, -150))
         py.test.raises(TypeError, float, cast(p, -150))
         assert complex(cast(p, 1.25)) == 1.25
         assert complex(cast(p, 1.25j)) == 1.25j
-        assert float(cast(p, INF*1j)) == INF*1j
-        assert float(cast(p, -INF)) == -INF
+        assert complex(cast(p, complex(0,INF))) == complex(0,INF)
+        assert complex(cast(p, -INF)) == -INF
         if name == "float":
             assert complex(cast(p, 1.1j)) != 1.1j         # rounding error
             assert complex(cast(p, 1E200+3j)) == INF+3j   # limited range
-            assert complex(cast(p, 3+1E200j)) == 3+INF*1j # limited range
+            assert complex(cast(p, complex(3,1E200))) == complex(3,INF) # limited range
 
-        assert cast(p, -1.1j) != cast(p, -1.1j)
+        assert cast(p, -1.1j) == cast(p, -1.1j)
         assert repr(complex(cast(p, -0.0)).real) == '-0.0'
-        assert repr(complex(cast(p, -0j))) == '-0j'
-        assert complex(cast(p, '\x09')) == 9.0
-        assert complex(cast(p, True)) == 1.0
+        #assert repr(complex(cast(p, -0j))) == '-0j'   # http://bugs.python.org/issue29602
+        assert complex(cast(p, b'\x09')) == 9.0 + 0j
+        assert complex(cast(p, u+'\x09')) == 9.0 + 0j
+        assert complex(cast(p, True)) == 1.0 + 0j
         py.test.raises(TypeError, cast, p, None)
         #
-        py.test.raises(cast, new_primitive_type(name), 1+2j)
-    py.test.raises(cast, new_primitive_type("int"), 1+2j)
+        py.test.raises(TypeError, cast, new_primitive_type(name), 1+0j)
+        #
+        for basetype in ["char", "int", "uint64_t", "float",
+                         "double", "long double"]:
+            baseobj = cast(new_primitive_type(basetype), 65)
+            py.test.raises(TypeError, complex, baseobj)
+        #
+        BArray = new_array_type(new_pointer_type(p), 10)
+        x = newp(BArray, None)
+        x[5] = 12.34 + 56.78j
+        assert type(x[5]) is complex
+        assert abs(x[5] - (12.34 + 56.78j)) < 1e-5
+        assert (x[5] == 12.34 + 56.78j) == (name == "double")  # rounding error
+        #
+        class Foo:
+            def __complex__(self):
+                return 2 + 3j
+        assert complex(Foo()) == 2 + 3j
+        assert complex(cast(p, Foo())) == 2 + 3j
+    py.test.raises(TypeError, cast, new_primitive_type("int"), 1+0j)
 
 def test_character_type():
     p = new_primitive_type("char")
@@ -1116,6 +1135,34 @@
     BSShort = new_primitive_type("short")
     assert f(3, cast(BSChar, -3), cast(BUChar, 200), cast(BSShort, -5)) == 192
 
+def test_call_function_24():
+    BFloat = new_primitive_type("float")
+    BFloatComplex = new_primitive_type("float _Complex")
+    BFunc3 = new_function_type((BFloat, BFloat), BFloatComplex, False)
+    if 0:   # libffi returning nonsense silently, so logic disabled for now
+        f = cast(BFunc3, _testfunc(24))
+        result = f(1.25, 5.1)
+        assert type(result) == complex
+        assert result.real == 1.25   # exact
+        assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact
+    else:
+        f = cast(BFunc3, _testfunc(9))
+        py.test.raises(NotImplementedError, f, 12.3, 34.5)
+
+def test_call_function_25():
+    BDouble = new_primitive_type("double")
+    BDoubleComplex = new_primitive_type("double _Complex")
+    BFunc3 = new_function_type((BDouble, BDouble), BDoubleComplex, False)
+    if 0:   # libffi returning nonsense silently, so logic disabled for now
+        f = cast(BFunc3, _testfunc(25))
+        result = f(1.25, 5.1)
+        assert type(result) == complex
+        assert result.real == 1.25   # exact
+        assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-10) # inexact
+    else:
+        f = cast(BFunc3, _testfunc(9))
+        py.test.raises(NotImplementedError, f, 12.3, 34.5)
+
 def test_cannot_call_with_a_autocompleted_struct():
     BSChar = new_primitive_type("signed char")
     BDouble = new_primitive_type("double")
diff --git a/cffi/cffi_opcode.py b/cffi/cffi_opcode.py
--- a/cffi/cffi_opcode.py
+++ b/cffi/cffi_opcode.py
@@ -105,8 +105,11 @@
 PRIM_UINT_FAST64   = 45
 PRIM_INTMAX        = 46
 PRIM_UINTMAX       = 47
+PRIM_FLOATCOMPLEX  = 48
+PRIM_DOUBLECOMPLEX = 49
 
-_NUM_PRIM          = 48
+
+_NUM_PRIM          = 50
 _UNKNOWN_PRIM          = -1
 _UNKNOWN_FLOAT_PRIM    = -2
 _UNKNOWN_LONG_DOUBLE   = -3
@@ -128,6 +131,8 @@
     'float':              PRIM_FLOAT,
     'double':             PRIM_DOUBLE,
     'long double':        PRIM_LONGDOUBLE,
+    'float _Complex':     PRIM_FLOATCOMPLEX,
+    'double _Complex':    PRIM_DOUBLECOMPLEX,
     '_Bool':              PRIM_BOOL,
     'wchar_t':            PRIM_WCHAR,
     'int8_t':             PRIM_INT8,
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -95,7 +95,8 @@
 
 
 class BasePrimitiveType(BaseType):
-    pass
+    def is_complex_type(self):
+        return False
 
 
 class PrimitiveType(BasePrimitiveType):
@@ -116,6 +117,8 @@
         'float':              'f',
         'double':             'f',
         'long double':        'f',
+        'float _Complex':     'j',
+        'double _Complex':    'j',
         '_Bool':              'i',
         # the following types are not primitive in the C sense
         'wchar_t':            'c',
@@ -163,6 +166,8 @@
         return self.ALL_PRIMITIVE_TYPES[self.name] == 'i'
     def is_float_type(self):
         return self.ALL_PRIMITIVE_TYPES[self.name] == 'f'
+    def is_complex_type(self):
+        return self.ALL_PRIMITIVE_TYPES[self.name] == 'j'
 
     def build_backend_type(self, ffi, finishlist):
         return global_cache(self, ffi, 'new_primitive_type', self.name)
diff --git a/cffi/parse_c_type.h b/cffi/parse_c_type.h
--- a/cffi/parse_c_type.h
+++ b/cffi/parse_c_type.h
@@ -79,8 +79,10 @@
 #define _CFFI_PRIM_UINT_FAST64  45
 #define _CFFI_PRIM_INTMAX       46
 #define _CFFI_PRIM_UINTMAX      47
+#define _CFFI_PRIM_FLOATCOMPLEX 48
+#define _CFFI_PRIM_DOUBLECOMPLEX 49
 
-#define _CFFI__NUM_PRIM         48
+#define _CFFI__NUM_PRIM         50
 #define _CFFI__UNKNOWN_PRIM           (-1)
 #define _CFFI__UNKNOWN_FLOAT_PRIM     (-2)
 #define _CFFI__UNKNOWN_LONG_DOUBLE    (-3)
diff --git a/cffi/recompiler.py b/cffi/recompiler.py
--- a/cffi/recompiler.py
+++ b/cffi/recompiler.py
@@ -506,7 +506,7 @@
 
     def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode):
         extraarg = ''
-        if isinstance(tp, model.BasePrimitiveType):
+        if isinstance(tp, model.BasePrimitiveType) and not tp.is_complex_type():
             if tp.is_integer_type() and tp.name != '_Bool':
                 converter = '_cffi_to_c_int'
                 extraarg = ', %s' % tp.name
@@ -524,8 +524,10 @@
                                                     tovar, errcode)
             return
         #
-        elif isinstance(tp, model.StructOrUnionOrEnum):
-            # a struct (not a struct pointer) as a function argument
+        elif (isinstance(tp, model.StructOrUnionOrEnum) or
+              isinstance(tp, model.BasePrimitiveType)):
+            # a struct (not a struct pointer) as a function argument;
+            # or, a complex (the same code works)
             self._prnt('  if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)'
                       % (tovar, self._gettypenum(tp), fromvar))
             self._prnt('    %s;' % errcode)
@@ -570,7 +572,7 @@
                 return '_cffi_from_c_int(%s, %s)' % (var, tp.name)
             elif isinstance(tp, model.UnknownFloatType):
                 return '_cffi_from_c_double(%s)' % (var,)
-            elif tp.name != 'long double':
+            elif tp.name != 'long double' and not tp.is_complex_type():
                 return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var)
             else:
                 return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % (
diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst
--- a/doc/source/cdef.rst
+++ b/doc/source/cdef.rst
@@ -642,16 +642,15 @@
 
 * Any ``__attribute__`` or ``#pragma pack(n)``
 
-* Additional types: complex numbers, special-size floating and fixed
-  point types, vector types, and so on.  You might be able to access an
-  array of complex numbers by declaring it as an array of ``struct
-  my_complex { double real, imag; }``, but in general you should declare
-  them as ``struct { ...; }`` and cannot access them directly.  This
-  means that you cannot call any function which has an argument or
-  return value of this type (this would need added support in libffi).
-  You need to write wrapper functions in C, e.g. ``void
-  foo_wrapper(struct my_complex c) { foo(c.real + c.imag*1j); }``, and
-  call ``foo_wrapper`` rather than ``foo`` directly.
+* Additional types: special-size floating and fixed
+  point types, vector types, and so on.
+
+* The C99 types ``float _Complex`` and ``double _Complex`` are supported
+  by cffi since version 1.11, but not libffi: you cannot call C
+  functions with complex arguments or return value, except if they are
+  directly API-mode functions.  The type ``long double _Complex`` is not
+  supported at all (declare and use it as if it were an array of two
+  ``long double``, and write wrapper functions in C with set_source()).
 
 Note that declarations like ``int field[];`` in
 structures are interpreted as variable-length structures.  Declarations
diff --git a/doc/source/ref.rst b/doc/source/ref.rst
--- a/doc/source/ref.rst
+++ b/doc/source/ref.rst
@@ -618,8 +618,8 @@
 |    C type     |   writing into         | reading from     |other operations|
 +===============+========================+==================+================+
 |   integers    | an integer or anything | a Python int or  | int(), bool()  |
-|   and enums   | on which int() works   | long, depending  | `(******)`,    |
-|   `(*****)`   | (but not a float!).    | on the type      | ``<``          |
+|   and enums   | on which int() works   | long, depending  | `[6]`,         |
+|   `[5]`       | (but not a float!).    | on the type      | ``<``          |
 |               | Must be within range.  | (ver. 1.10: or a |                |
 |               |                        | bool)            |                |
 +---------------+------------------------+------------------+----------------+
@@ -636,14 +636,19 @@
 +---------------+------------------------+------------------+----------------+
 |``long double``| another <cdata> with   | a <cdata>, to    | float(), int(),|
 |               | a ``long double``, or  | avoid loosing    | bool()         |
-|               | anything on which      | precision `(***)`|                |
+|               | anything on which      | precision `[3]`  |                |
 |               | float() works          |                  |                |
 +---------------+------------------------+------------------+----------------+
-|  pointers     | another <cdata> with   | a <cdata>        |``[]`` `(****)`,|
+| ``float``     | a complex number       | a Python complex | complex(),     |
+| ``_Complex``, | or anything on which   | number           | bool()         |
+| ``double``    | complex() works        |                  | `[7]`          |
+| ``_Complex``  |                        |                  |                |
++---------------+------------------------+------------------+----------------+
+|  pointers     | another <cdata> with   | a <cdata>        |``[]`` `[4]`,   |
 |               | a compatible type (i.e.|                  |``+``, ``-``,   |
 |               | same type              |                  |bool()          |
 |               | or ``void*``, or as an |                  |                |
-|               | array instead) `(*)`   |                  |                |
+|               | array instead) `[1]`   |                  |                |
 +---------------+------------------------+                  |                |
 |  ``void *``   | another <cdata> with   |                  |                |
 |               | any pointer or array   |                  |                |
@@ -655,10 +660,10 @@
 |               |                        |                  | struct fields  |
 +---------------+------------------------+                  +----------------+
 | function      | same as pointers       |                  | bool(),        |
-| pointers      |                        |                  | call `(**)`    |
+| pointers      |                        |                  | call `[2]`     |
 +---------------+------------------------+------------------+----------------+
 |  arrays       | a list or tuple of     | a <cdata>        |len(), iter(),  |
-|               | items                  |                  |``[]`` `(****)`,|
+|               | items                  |                  |``[]`` `[4]`,   |
 |               |                        |                  |``+``, ``-``    |
 +---------------+------------------------+                  +----------------+
 | ``char[]``,   | same as arrays, or a   |                  | len(), iter(), |
@@ -680,7 +685,7 @@
 |               | with at most one field |                  | fields         |
 +---------------+------------------------+------------------+----------------+
 
-`(*)` ``item *`` is ``item[]`` in function arguments:
+`[1]` ``item *`` is ``item[]`` in function arguments:
 
    In a function declaration, as per the C standard, a ``item *``
    argument is identical to a ``item[]`` argument (and ``ffi.cdef()``
@@ -701,7 +706,7 @@
    (On PyPy, this optimization is only available since PyPy 5.4
    with CFFI 1.8.)
 
-`(**)` C function calls are done with the GIL released.
+`[2]` C function calls are done with the GIL released.
 
    Note that we assume that the called functions are *not* using the
    Python API from Python.h.  For example, we don't check afterwards
@@ -713,7 +718,7 @@
    ``libpypy-c.dll`` on their own.  But really, don't do that in the
    first place.)
 
-`(***)` ``long double`` support:
+`[3]` ``long double`` support:
 
    We keep ``long double`` values inside a cdata object to avoid
    loosing precision.  Normal Python floating-point numbers only
@@ -724,7 +729,7 @@
    and use a family of C functions like ``long double add(long double
    a, long double b);``.
 
-`(****)` Slicing with ``x[start:stop]``:
+`[4]` Slicing with ``x[start:stop]``:
 
    Slicing is allowed, as long as you specify explicitly both ``start``
    and ``stop`` (and don't give any ``step``).  It gives a cdata
@@ -738,7 +743,7 @@
    say ``chararray[10:15] = "hello"``, but the assigned string must be of
    exactly the correct length; no implicit null character is added.)
 
-`(*****)` Enums are handled like ints:
+`[5]` Enums are handled like ints:
 
    Like C, enum types are mostly int types (unsigned or signed, int or
    long; note that GCC's first choice is unsigned).  Reading an enum
@@ -747,7 +752,7 @@
    lib.FOO``.  If you really want to get their value as a string, use
    ``ffi.string(ffi.cast("the_enum_type", x.field))``.
 
-`(******)` bool() on a primitive cdata:
+`[6]` bool() on a primitive cdata:
 
    *New in version 1.7.*  In previous versions, it only worked on
    pointers; for primitives it always returned True.
@@ -760,6 +765,13 @@
    Also, when converting from a byte string to a ``_Bool[]``, only the
    bytes ``\x00`` and ``\x01`` are accepted.
 
+`[7]` libffi does not support complex numbers:
+
+   *New in version 1.11:* CFFI now supports complex numbers directly.
+   Note however that libffi does not.  This means that C functions that
+   take directly as argument types or return type a complex type cannot
+   be called by CFFI, unless they are directly using the API mode.
+
 .. _file:
 
 Support for FILE
diff --git a/testing/cffi0/test_verify.py b/testing/cffi0/test_verify.py
--- a/testing/cffi0/test_verify.py
+++ b/testing/cffi0/test_verify.py
@@ -239,15 +239,18 @@
         tp = model.PrimitiveType(typename)
         C = tp.is_char_type()
         F = tp.is_float_type()
+        X = tp.is_complex_type()
         I = tp.is_integer_type()
         assert C == (typename in ('char', 'wchar_t'))
         assert F == (typename in ('float', 'double', 'long double'))
-        assert I + F + C == 1      # one and only one of them is true
+        assert X == (typename in ('float _Complex', 'double _Complex'))
+        assert I + F + C + X == 1      # one and only one of them is true
 
 def test_all_integer_and_float_types():
     typenames = []
     for typename in all_primitive_types:
         if (all_primitive_types[typename] == 'c' or
+            all_primitive_types[typename] == 'j' or    # complex
             typename == '_Bool' or typename == 'long double'):
             pass
         else:
diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py
--- a/testing/cffi1/test_new_ffi_1.py
+++ b/testing/cffi1/test_new_ffi_1.py
@@ -1704,6 +1704,8 @@
             "ptrdiff_t",
             "size_t",
             "ssize_t",
+            'double _Complex',
+            'float _Complex',
             ])
         for name in PRIMITIVE_TO_INDEX:
             x = ffi.sizeof(name)
diff --git a/testing/cffi1/test_parse_c_type.py b/testing/cffi1/test_parse_c_type.py
--- a/testing/cffi1/test_parse_c_type.py
+++ b/testing/cffi1/test_parse_c_type.py
@@ -155,6 +155,8 @@
             ("long int", lib._CFFI_PRIM_LONG),
             ("unsigned short", lib._CFFI_PRIM_USHORT),
             ("long double", lib._CFFI_PRIM_LONGDOUBLE),
+            (" float  _Complex", lib._CFFI_PRIM_FLOATCOMPLEX),
+            ("double _Complex ", lib._CFFI_PRIM_DOUBLECOMPLEX),
             ]:
         assert parse(simple_type) == ['->', Prim(expected)]
 
@@ -280,6 +282,11 @@
     parse_error("int[5](*)", "unexpected symbol", 6)
     parse_error("int a(*)", "identifier expected", 6)
     parse_error("int[123456789012345678901234567890]", "number too large", 4)
+    #
+    parse_error("_Complex", "identifier expected", 0)
+    parse_error("int _Complex", "_Complex type combination unsupported", 4)
+    parse_error("long double _Complex", "_Complex type combination unsupported",
+                12)
 
 def test_number_too_large():
     num_max = sys.maxsize
diff --git a/testing/cffi1/test_realize_c_type.py b/testing/cffi1/test_realize_c_type.py
--- a/testing/cffi1/test_realize_c_type.py
+++ b/testing/cffi1/test_realize_c_type.py
@@ -47,7 +47,6 @@
     for name in cffi_opcode.PRIMITIVE_TO_INDEX:
         check(name, name)
 
-
 def check_func(input, expected_output=None):
     import _cffi_backend
     ffi = _cffi_backend.FFI()
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -2001,6 +2001,60 @@
     """)
     assert lib.f1(52).a == 52
 
+def test_function_returns_float_complex():
+    if sys.platform == 'win32':
+        py.test.skip("MSVC may not support _Complex")
+    ffi = FFI()
+    ffi.cdef("float _Complex f1(float a, float b);");
+    lib = verify(ffi, "test_function_returns_float_complex", """
+        #include <complex.h>
+        static float _Complex f1(float a, float b) { return a + I*2.0*b; }
+    """)
+    result = lib.f1(1.25, 5.1)
+    assert type(result) == complex
+    assert result.real == 1.25   # exact
+    assert (result.imag != 2*5.1) and (abs(result.imag - 2*5.1) < 1e-5) # inexact
+
+def test_function_returns_double_complex():
+    if sys.platform == 'win32':
+        py.test.skip("MSVC may not support _Complex")
+    ffi = FFI()
+    ffi.cdef("double _Complex f1(double a, double b);");
+    lib = verify(ffi, "test_function_returns_double_complex", """
+        #include <complex.h>
+        static double _Complex f1(double a, double b) { return a + I*2.0*b; }
+    """)
+    result = lib.f1(1.25, 5.1)
+    assert type(result) == complex
+    assert result.real == 1.25   # exact
+    assert result.imag == 2*5.1  # exact
+
+def test_function_argument_float_complex():
+    if sys.platform == 'win32':
+        py.test.skip("MSVC may not support _Complex")
+    ffi = FFI()
+    ffi.cdef("float f1(float _Complex x);");
+    lib = verify(ffi, "test_function_argument_float_complex", """
+        #include <complex.h>
+        static float f1(float _Complex x) { return cabsf(x); }
+    """)
+    x = complex(12.34, 56.78)
+    result = lib.f1(x)
+    assert abs(result - abs(x)) < 1e-5
+
+def test_function_argument_double_complex():
+    if sys.platform == 'win32':
+        py.test.skip("MSVC may not support _Complex")
+    ffi = FFI()
+    ffi.cdef("double f1(double _Complex);");
+    lib = verify(ffi, "test_function_argument_double_complex", """
+        #include <complex.h>
+        static double f1(double _Complex x) { return cabs(x); }
+    """)
+    x = complex(12.34, 56.78)
+    result = lib.f1(x)
+    assert abs(result - abs(x)) < 1e-11
+
 def test_typedef_array_dotdotdot():
     ffi = FFI()
     ffi.cdef("""
diff --git a/testing/cffi1/test_verify1.py b/testing/cffi1/test_verify1.py
--- a/testing/cffi1/test_verify1.py
+++ b/testing/cffi1/test_verify1.py
@@ -219,15 +219,18 @@
         tp = model.PrimitiveType(typename)
         C = tp.is_char_type()
         F = tp.is_float_type()
+        X = tp.is_complex_type()
         I = tp.is_integer_type()
         assert C == (typename in ('char', 'wchar_t'))
         assert F == (typename in ('float', 'double', 'long double'))
-        assert I + F + C == 1      # one and only one of them is true
+        assert X == (typename in ('float _Complex', 'double _Complex'))
+        assert I + F + C + X == 1      # one and only one of them is true
 
 def test_all_integer_and_float_types():
     typenames = []
     for typename in all_primitive_types:
         if (all_primitive_types[typename] == 'c' or
+            all_primitive_types[typename] == 'j' or    # complex
             typename == '_Bool' or typename == 'long double'):
             pass
         else:


More information about the pypy-commit mailing list