[pypy-commit] cffi default: hg merge enum-as-int

arigo noreply at buildbot.pypy.org
Tue Feb 12 20:29:39 CET 2013


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r1151:4aed2f4e2bc1
Date: 2013-02-12 18:26 +0100
http://bitbucket.org/cffi/cffi/changeset/4aed2f4e2bc1/

Log:	hg merge enum-as-int

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -824,35 +824,6 @@
     return (PyObject *)cd;
 }
 
-static PyObject *convert_enum_string_to_int(CTypeDescrObject *ct, PyObject *ob)
-{
-    PyObject *d_value;
-    char *p = PyText_AsUTF8(ob);
-    if (p == NULL)
-        return NULL;
-
-    if (p[0] == '#') {
-        char *number = p + 1;       /* strip initial '#' */
-        PyObject *ob2 = PyText_FromString(number);
-        if (ob2 == NULL)
-            return NULL;
-
-        d_value = PyNumber_Long(ob2);
-        Py_DECREF(ob2);
-    }
-    else {
-        d_value = PyDict_GetItem(PyTuple_GET_ITEM(ct->ct_stuff, 0), ob);
-        if (d_value == NULL) {
-            PyErr_Format(PyExc_ValueError,
-                         "'%s' is not an enumerator for %s",
-                         p, ct->ct_name);
-            return NULL;
-        }
-        Py_INCREF(d_value);
-    }
-    return d_value;
-}
-
 static CDataObject *_new_casted_primitive(CTypeDescrObject *ct);  /*forward*/
 
 static PyObject *
@@ -887,21 +858,7 @@
         PY_LONG_LONG value;
         /*READ(data, ct->ct_size)*/
         value = read_raw_signed_data(data, ct->ct_size);
-
-        if (ct->ct_flags & CT_IS_ENUM) {
-            PyObject *d_value, *d_key = PyInt_FromLong((int)value);
-            if (d_key == NULL)
-                return NULL;
-
-            d_value = PyDict_GetItem(PyTuple_GET_ITEM(ct->ct_stuff, 1), d_key);
-            Py_DECREF(d_key);
-            if (d_value != NULL)
-                Py_INCREF(d_value);
-            else
-                d_value = PyText_FromFormat("#%d", (int)value);
-            return d_value;
-        }
-        else if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG)
+        if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG)
             return PyInt_FromLong((long)value);
         else
             return PyLong_FromLongLong(value);
@@ -1190,27 +1147,8 @@
     }
     if (ct->ct_flags & CT_PRIMITIVE_SIGNED) {
         PY_LONG_LONG value = _my_PyLong_AsLongLong(init);
-
-        if (value == -1 && PyErr_Occurred()) {
-            if (!(ct->ct_flags & CT_IS_ENUM))
-                return -1;
-            else {
-                PyObject *ob;
-                PyErr_Clear();
-                if (!PyTextAny_Check(init)) {
-                    expected = "str or int";
-                    goto cannot_convert;
-                }
-
-                ob = convert_enum_string_to_int(ct, init);
-                if (ob == NULL)
-                    return -1;
-                value = PyLong_AsLongLong(ob);
-                Py_DECREF(ob);
-                if (value == -1 && PyErr_Occurred())
-                    return -1;
-            }
-        }
+        if (value == -1 && PyErr_Occurred())
+            return -1;
         write_raw_integer_data(buf, value, ct->ct_size);
         if (value != read_raw_signed_data(buf, ct->ct_size))
             goto overflow;
@@ -1469,20 +1407,48 @@
 
 static PyObject *cdata_float(CDataObject *cd);  /*forward*/
 
+static PyObject *convert_cdata_to_enum_string(CDataObject *cd, int both)
+{
+    PyObject *d_key, *d_value;
+    CTypeDescrObject *ct = cd->c_type;
+
+    assert(ct->ct_flags & CT_IS_ENUM);
+    d_key = convert_to_object(cd->c_data, ct);
+    if (d_key == NULL)
+        return NULL;
+
+    d_value = PyDict_GetItem(PyTuple_GET_ITEM(ct->ct_stuff, 1), d_key);
+    if (d_value != NULL) {
+        if (both) {
+            PyObject *o = PyObject_Str(d_key);
+            if (o == NULL)
+                d_value = NULL;
+            else {
+                d_value = PyText_FromFormat("%s: %s",
+                                            PyText_AS_UTF8(o),
+                                            PyText_AS_UTF8(d_value));
+                Py_DECREF(o);
+            }
+        }
+        else
+            Py_INCREF(d_value);
+    }
+    else
+        d_value = PyObject_Str(d_key);
+    Py_DECREF(d_key);
+    return d_value;
+}
+
 static PyObject *cdata_repr(CDataObject *cd)
 {
     char *extra;
     PyObject *result, *s;
 
     if (cd->c_type->ct_flags & CT_PRIMITIVE_ANY) {
-        if (!(cd->c_type->ct_flags & CT_IS_LONGDOUBLE)) {
-            PyObject *o = convert_to_object(cd->c_data, cd->c_type);
-            if (o == NULL)
-                return NULL;
-            s = PyObject_Repr(o);
-            Py_DECREF(o);
+        if (cd->c_type->ct_flags & CT_IS_ENUM) {
+            s = convert_cdata_to_enum_string(cd, 1);
         }
-        else {
+        else if (cd->c_type->ct_flags & CT_IS_LONGDOUBLE) {
             long double lvalue;
             char buffer[128];   /* big enough */
             /*READ(cd->c_data, sizeof(long double)*/
@@ -1490,6 +1456,13 @@
             sprintf(buffer, "%LE", lvalue);
             s = PyText_FromString(buffer);
         }
+        else {
+            PyObject *o = convert_to_object(cd->c_data, cd->c_type);
+            if (o == NULL)
+                return NULL;
+            s = PyObject_Repr(o);
+            Py_DECREF(o);
+        }
     }
     else if ((cd->c_type->ct_flags & CT_ARRAY) && cd->c_type->ct_length < 0) {
         s = PyText_FromFormat("sliced length %zd", get_array_length(cd));
@@ -2745,14 +2718,6 @@
                                  (CT_POINTER|CT_FUNCTIONPTR|CT_ARRAY)) {
         value = (Py_intptr_t)((CDataObject *)ob)->c_data;
     }
-    else if ((ct->ct_flags & CT_IS_ENUM) && PyTextAny_Check(ob)) {
-        ob = convert_enum_string_to_int(ct, ob);
-        if (ob == NULL)
-            return NULL;
-        cd = cast_to_integer_or_char(ct, ob);
-        Py_DECREF(ob);
-        return cd;
-    }
 #if PY_MAJOR_VERSION < 3
     else if (PyString_Check(ob)) {
         if (PyString_GET_SIZE(ob) != 1) {
@@ -4074,8 +4039,7 @@
                 return -1;
             }
         }
-        if ((ctype->ct_flags & (CT_PRIMITIVE_SIGNED | CT_IS_ENUM))
-                == CT_PRIMITIVE_SIGNED) {
+        if (ctype->ct_flags & CT_PRIMITIVE_SIGNED) {
             PY_LONG_LONG value;
             /* It's probably fine to always zero-extend, but you never
                know: maybe some code somewhere expects a negative
@@ -4294,6 +4258,10 @@
     CTypeDescrObject *td;
     Py_ssize_t i, n;
     struct aligncheck_int { char x; int y; };
+    struct aligncheck_long { char x; long y; };
+    long smallest_item = 0;
+    unsigned long largest_item = 0;
+    int size, flags;
 
     if (!PyArg_ParseTuple(args, "sO!O!:new_enum_type",
                           &ename,
@@ -4311,8 +4279,13 @@
     dict1 = PyDict_New();
     if (dict1 == NULL)
         goto error;
+    dict2 = PyDict_New();
+    if (dict2 == NULL)
+        goto error;
+
     for (i=n; --i >= 0; ) {
         long lvalue;
+        unsigned long ulvalue;
         PyObject *value = PyTuple_GET_ITEM(enumvalues, i);
         tmpkey = PyTuple_GET_ITEM(enumerators, i);
         Py_INCREF(tmpkey);
@@ -4336,25 +4309,62 @@
             }
         }
         lvalue = PyLong_AsLong(value);
-        if ((lvalue == -1 && PyErr_Occurred()) || lvalue != (int)lvalue) {
-            PyErr_Format(PyExc_OverflowError,
-                         "enum '%s' declaration for '%s' does not fit an int",
-                         ename, PyText_AS_UTF8(tmpkey));
-            goto error;
+        if (PyErr_Occurred()) {
+            PyErr_Clear();
+            ulvalue = PyLong_AsUnsignedLong(value);
+            if (PyErr_Occurred()) {
+                PyErr_Format(PyExc_OverflowError,
+                             "enum '%s' declaration for '%s' does not fit "
+                             "a long or unsigned long",
+                             ename, PyText_AS_UTF8(tmpkey));
+                goto error;
+            }
+            if (ulvalue > largest_item)
+                largest_item = ulvalue;
+        }
+        else {
+            if (lvalue < 0) {
+                if (lvalue < smallest_item)
+                    smallest_item = lvalue;
+            }
+            else {
+                ulvalue = (unsigned long)lvalue;
+                if (ulvalue > largest_item)
+                    largest_item = ulvalue;
+            }
         }
         if (PyDict_SetItem(dict1, tmpkey, value) < 0)
             goto error;
+        if (PyDict_SetItem(dict2, value, tmpkey) < 0)
+            goto error;
         Py_DECREF(tmpkey);
         tmpkey = NULL;
     }
 
-    dict2 = PyDict_New();
-    if (dict2 == NULL)
-        goto error;
-    for (i=n; --i >= 0; ) {
-        if (PyDict_SetItem(dict2, PyTuple_GET_ITEM(enumvalues, i),
-                                  PyTuple_GET_ITEM(enumerators, i)) < 0)
+    if (smallest_item < 0) {
+        flags = CT_PRIMITIVE_SIGNED | CT_PRIMITIVE_FITS_LONG | CT_IS_ENUM;
+        if (smallest_item == (int)smallest_item &&
+                 largest_item <= (unsigned long)INT_MAX) {
+            size = sizeof(int);
+        }
+        else if (largest_item <= (unsigned long)LONG_MAX) {
+            size = sizeof(long);
+        }
+        else {
+            PyErr_Format(PyExc_OverflowError,
+                         "enum '%s' values don't all fit into either 'long' "
+                         "or 'unsigned long'", ename);
             goto error;
+        }
+    }
+    else if (sizeof(unsigned int) < sizeof(unsigned long) &&
+             largest_item == (unsigned int)largest_item) {
+        flags = CT_PRIMITIVE_UNSIGNED | CT_PRIMITIVE_FITS_LONG | CT_IS_ENUM;
+        size = sizeof(unsigned int);
+    }
+    else {
+        flags = CT_PRIMITIVE_UNSIGNED | CT_IS_ENUM;
+        size = sizeof(unsigned long);
     }
 
     combined = PyTuple_Pack(2, dict1, dict2);
@@ -4364,10 +4374,10 @@
     Py_CLEAR(dict2);
     Py_CLEAR(dict1);
 
-    switch (sizeof(int)) {
+    switch (size) {
     case 4: ffitype = &ffi_type_sint32; break;
     case 8: ffitype = &ffi_type_sint64; break;
-    default: Py_FatalError("'int' is not 4 or 8 bytes");
+    default: Py_FatalError("'int' or 'long' is not 4 or 8 bytes"); return NULL;
     }
 
     name_size = strlen("enum ") + strlen(ename) + 1;
@@ -4378,10 +4388,11 @@
     memcpy(td->ct_name, "enum ", strlen("enum "));
     memcpy(td->ct_name + strlen("enum "), ename, name_size - strlen("enum "));
     td->ct_stuff = combined;
-    td->ct_size = sizeof(int);
-    td->ct_length = offsetof(struct aligncheck_int, y);
+    td->ct_size = size;
+    td->ct_length = size == sizeof(int) ? offsetof(struct aligncheck_int, y)
+                                        : offsetof(struct aligncheck_long, y);
     td->ct_extra = ffitype;
-    td->ct_flags = CT_PRIMITIVE_SIGNED | CT_PRIMITIVE_FITS_LONG | CT_IS_ENUM;
+    td->ct_flags = flags;
     td->ct_name_position = name_size - 1;
     return (PyObject *)td;
 
@@ -4604,7 +4615,7 @@
 #endif
     }
     else if (cd->c_type->ct_flags & CT_IS_ENUM) {
-        return convert_to_object(cd->c_data, cd->c_type);
+        return convert_cdata_to_enum_string(cd, 0);
     }
     else if (cd->c_type->ct_flags & CT_IS_BOOL) {
         /* fall through to TypeError */
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -1292,22 +1292,24 @@
 def test_cast_to_enum():
     BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
     e = cast(BEnum, 0)
-    assert repr(e) == "<cdata 'enum foo' 'def'>"
+    assert repr(e) == "<cdata 'enum foo' 0: def>"
+    assert repr(cast(BEnum, -42)) == "<cdata 'enum foo' -42>"
+    assert repr(cast(BEnum, -20)) == "<cdata 'enum foo' -20: ab>"
     assert string(e) == 'def'
     assert string(cast(BEnum, -20)) == 'ab'
-    assert string(cast(BEnum, 'c')) == 'c'
-    assert int(cast(BEnum, 'c')) == 1
-    assert int(cast(BEnum, 'def')) == 0
+    assert int(cast(BEnum, 1)) == 1
+    assert int(cast(BEnum, 0)) == 0
     assert int(cast(BEnum, -242 + 2**128)) == -242
-    assert string(cast(BEnum, -242 + 2**128)) == '#-242'
-    assert string(cast(BEnum, '#-20')) == 'ab'
-    assert repr(cast(BEnum, '#-20')) == "<cdata 'enum foo' 'ab'>"
-    assert repr(cast(BEnum, '#-21')) == "<cdata 'enum foo' '#-21'>"
+    assert string(cast(BEnum, -242 + 2**128)) == '-242'
+    #
+    BEnum = new_enum_type("bar", ('def', 'c', 'ab'), (0, 1, 20))
+    e = cast(BEnum, -1)
+    assert repr(e) == "<cdata 'enum bar' 4294967295>"     # unsigned int
 
 def test_enum_with_non_injective_mapping():
     BEnum = new_enum_type("foo", ('ab', 'cd'), (7, 7))
     e = cast(BEnum, 7)
-    assert repr(e) == "<cdata 'enum foo' 'ab'>"
+    assert repr(e) == "<cdata 'enum foo' 7: ab>"
     assert string(e) == 'ab'
 
 def test_enum_in_struct():
@@ -1316,36 +1318,111 @@
     BStructPtr = new_pointer_type(BStruct)
     complete_struct_or_union(BStruct, [('a1', BEnum, -1)])
     p = newp(BStructPtr, [-20])
-    assert p.a1 == "ab"
-    p = newp(BStructPtr, ["c"])
-    assert p.a1 == "c"
+    assert p.a1 == -20
+    p = newp(BStructPtr, [12])
+    assert p.a1 == 12
     e = py.test.raises(TypeError, newp, BStructPtr, [None])
-    assert "must be a str or int, not NoneType" in str(e.value)
+    assert "an integer is required" in str(e.value)
+    py.test.raises(TypeError, 'p.a1 = "def"')
     if sys.version_info < (3,):
-        p.a1 = unicode("def")
-        assert p.a1 == "def" and type(p.a1) is str
-        py.test.raises(UnicodeEncodeError, "p.a1 = unichr(1234)")
         BEnum2 = new_enum_type(unicode("foo"), (unicode('abc'),), (5,))
-        assert string(cast(BEnum2, unicode('abc'))) == 'abc'
+        assert string(cast(BEnum2, 5)) == 'abc'
+        assert type(string(cast(BEnum2, 5))) is str
 
 def test_enum_overflow():
-    for ovf in (2**63, -2**63-1, 2**31, -2**31-1):
-        e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
-                           (5, ovf))
-        assert str(e.value) == (
-            "enum 'foo' declaration for 'b' does not fit an int")
+    max_uint = 2 ** (size_of_int()*8) - 1
+    max_int = max_uint // 2
+    max_ulong = 2 ** (size_of_long()*8) - 1
+    max_long = max_ulong // 2
+    # 'unsigned int' case
+    e = new_enum_type("foo", ('a', 'b'), (0, 3))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == max_uint     # 'e' is unsigned
+    e = new_enum_type("foo", ('a', 'b'), (0, max_uint))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == max_uint
+    assert e.elements == {0: 'a', max_uint: 'b'}
+    assert e.relements == {'a': 0, 'b': max_uint}
+    # 'signed int' case
+    e = new_enum_type("foo", ('a', 'b'), (-1, max_int))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-1: 'a', max_int: 'b'}
+    assert e.relements == {'a': -1, 'b': max_int}
+    e = new_enum_type("foo", ('a', 'b'), (-max_int-1, max_int))
+    assert sizeof(e) == size_of_int()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-max_int-1: 'a', max_int: 'b'}
+    assert e.relements == {'a': -max_int-1, 'b': max_int}
+    # 'unsigned long' case
+    e = new_enum_type("foo", ('a', 'b'), (0, max_long))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == max_ulong     # 'e' is unsigned
+    e = new_enum_type("foo", ('a', 'b'), (0, max_ulong))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == max_ulong
+    assert e.elements == {0: 'a', max_ulong: 'b'}
+    assert e.relements == {'a': 0, 'b': max_ulong}
+    # 'signed long' case
+    e = new_enum_type("foo", ('a', 'b'), (-1, max_long))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-1: 'a', max_long: 'b'}
+    assert e.relements == {'a': -1, 'b': max_long}
+    e = new_enum_type("foo", ('a', 'b'), (-max_long-1, max_long))
+    assert sizeof(e) == size_of_long()
+    assert int(cast(e, -1)) == -1
+    assert e.elements == {-max_long-1: 'a', max_long: 'b'}
+    assert e.relements == {'a': -max_long-1, 'b': max_long}
+    # overflow: both negative items and items larger than max_long
+    e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
+                       (-1, max_long + 1))
+    assert str(e.value) == (
+        "enum 'foo' values don't all fit into either 'long' "
+        "or 'unsigned long'")
+    # overflow: items smaller than -max_long-1
+    e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
+                       (-max_long-2, 5))
+    assert str(e.value) == (
+        "enum 'foo' declaration for 'a' does not fit a long or unsigned long")
+    # overflow: items larger than max_ulong
+    e = py.test.raises(OverflowError, new_enum_type, "foo", ('a', 'b'),
+                       (5, max_ulong+1))
+    assert str(e.value) == (
+        "enum 'foo' declaration for 'b' does not fit a long or unsigned long")
 
 def test_callback_returning_enum():
     BInt = new_primitive_type("int")
     BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
     def cb(n):
-        return '#%d' % n
+        if n & 1:
+            return cast(BEnum, n)
+        else:
+            return n
     BFunc = new_function_type((BInt,), BEnum)
     f = callback(BFunc, cb)
-    assert f(0) == 'def'
-    assert f(1) == 'c'
-    assert f(-20) == 'ab'
-    assert f(20) == '#20'
+    assert f(0) == 0
+    assert f(1) == 1
+    assert f(-20) == -20
+    assert f(20) == 20
+    assert f(21) == 21
+
+def test_callback_returning_enum_unsigned():
+    BInt = new_primitive_type("int")
+    BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, 20))
+    def cb(n):
+        print n
+        if n & 1:
+            return cast(BEnum, n)
+        else:
+            return n
+    BFunc = new_function_type((BInt,), BEnum)
+    f = callback(BFunc, cb)
+    assert f(0) == 0
+    assert f(1) == 1
+    assert f(-21) == 2**32 - 21
+    assert f(20) == 20
+    assert f(21) == 21
 
 def test_callback_returning_char():
     BInt = new_primitive_type("int")
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -926,49 +926,43 @@
 
     def new_enum_type(self, name, enumerators, enumvalues):
         assert isinstance(name, str)
-        mapping = dict(zip(enumerators, enumvalues))
         reverse_mapping = dict(zip(reversed(enumvalues),
                                    reversed(enumerators)))
-        CTypesInt = self.ffi._get_cached_btype(model.PrimitiveType('int'))
-        #
-        def forward_map(source):
-            if not isinstance(source, str):
-                return source
-            try:
-                return mapping[source]
-            except KeyError:
-                if source.startswith('#'):
-                    try:
-                        return int(source[1:])
-                    except ValueError:
-                        pass
-            raise ValueError("%r is not an enumerator for %r" % (
-                source, CTypesEnum))
+        smallest = min(enumvalues or [0])
+        largest = max(enumvalues or [0])
+        if smallest < 0:
+            if largest == ctypes.c_int(largest).value:
+                tp = 'int'
+            elif largest == ctypes.c_long(largest).value:
+                tp = 'long'
+            else:
+                raise OverflowError
+        else:
+            if largest == ctypes.c_uint(largest).value:
+                tp = 'unsigned int'
+            elif largest == ctypes.c_ulong(largest).value:
+                tp = 'unsigned long'
+            else:
+                raise OverflowError
+        CTypesInt = self.ffi._get_cached_btype(model.PrimitiveType(tp))
         #
         class CTypesEnum(CTypesInt):
             __slots__ = []
             _reftypename = 'enum %s &' % name
 
+            def _get_own_repr(self):
+                value = self._value
+                try:
+                    return '%d: %s' % (value, reverse_mapping[value])
+                except KeyError:
+                    return str(value)
+
             def _to_string(self, maxlen):
-                return str(CTypesEnum._from_ctypes(self._value))
-
-            @classmethod
-            def _cast_from(cls, source):
-                source = forward_map(source)
-                return super(CTypesEnum, cls)._cast_from(source)
-
-            @staticmethod
-            def _to_ctypes(x):
-                x = forward_map(x)
-                return CTypesInt._to_ctypes(x)
-
-            @staticmethod
-            def _from_ctypes(value):
-                value = CTypesInt._from_ctypes(value)
+                value = self._value
                 try:
                     return reverse_mapping[value]
                 except KeyError:
-                    return '#%s' % value
+                    return str(value)
         #
         CTypesEnum._fix_class()
         return CTypesEnum
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1031,7 +1031,7 @@
 -------------------
 
 ``ffi.include(other_ffi)``: includes the typedefs, structs, unions and
-enums defined in another FFI instance.  Usage is similar to a
+enum types defined in another FFI instance.  Usage is similar to a
 ``#include`` in C, where a part of the program might include types
 defined in another part for its own usage.  Note that the include()
 method has no effect on functions, constants and global variables, which
@@ -1065,8 +1065,9 @@
   byte string or unicode string.  (Note that in some situation a single
   wchar_t may require a Python unicode string of length 2.)
 
-- If 'cdata' is an enum, returns the value of the enumerator as a
-  string, or ``#NUMBER`` if the value is out of range.
+- If 'cdata' is an enum, returns the value of the enumerator as a string.
+  If the value is out of range, it is simply returned as the stringified
+  integer.
 
 
 ``ffi.buffer(cdata, [size])``: return a buffer object that references
@@ -1193,15 +1194,16 @@
   length 0, allocating a ``char[]`` of the correct size, and casting
   it to a struct pointer)
 
-* Enum types are always ``int``.  GCC supports enums containing
-  larger constants (``unsigned int``, or ``long long``) as an extension
-  to the C standard, but CFFI does not.  Use
-  ``typedef <exact type> my_enum;`` and then some ``#define foo <value>``.
-
 .. versionadded:: 0.4
    Now supported: the common GCC extension of anonymous nested
    structs/unions inside structs/unions.
 
+.. versionadded:: 0.6
+   Enum types follow the GCC rules: they are defined as the first of
+   ``unsigned int``, ``int``, ``unsigned long`` or ``long`` that fits
+   all numeric values.  Note that the first choice is unsigned.  In CFFI
+   0.5 and before, it was always ``int``.
+
 
 Debugging dlopen'ed C libraries
 -------------------------------
@@ -1254,8 +1256,8 @@
 |    C type     |   writing into         | reading from     |other operations|
 +===============+========================+==================+================+
 |   integers    | an integer or anything | a Python int or  | int()          |
-|               | on which int() works   | long, depending  |                |
-|               | (but not a float!).    | on the type      |                |
+|   and enums   | on which int() works   | long, depending  |                |
+|   `(*****)`   | (but not a float!).    | on the type      |                |
 |               | Must be within range.  |                  |                |
 +---------------+------------------------+------------------+----------------+
 |   ``char``    | a string of length 1   | a string of      | int()          |
@@ -1313,11 +1315,6 @@
 | union         | same as struct, but    |                  | read/write     |
 |               | with at most one field |                  | fields         |
 +---------------+------------------------+------------------+----------------+
-| enum          | an integer, or the enum| the enum value   | int()          |
-|               | value as a string or   | as a string, or  |                |
-|               | as ``"#NUMBER"``       | ``"#NUMBER"``    |                |
-|               |                        | if out of range  |                |
-+---------------+------------------------+------------------+----------------+
 
 .. versionchanged:: 0.3
    `(*)` Note that when calling a function, as per C, a ``item *`` argument
@@ -1358,6 +1355,15 @@
    C.  As for slice assignment, it accepts any iterable, including a list
    of items or another array-like cdata object, but the length must match.
 
+.. versionchanged:: 0.6
+   `(*****)` Enums are now handled like ints (unsigned or signed, int or
+   long, like GCC; note that the first choice is unsigned).  In previous
+   versions, you would get the enum's value as a string.  Now we follow the C
+   convention and treat them as really equivalent to integers.  To compare
+   their value symbolically, use code like ``if x.field == lib.FOO``.
+   If you really want to get their value as a string, use
+   ``ffi.string(ffi.cast("the_enum_type", x.field))``.
+
 
 Reference: verifier
 -------------------
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -860,54 +860,55 @@
     def test_enum(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum foo { A, B, CC, D };")
-        assert int(ffi.cast("enum foo", "A")) == 0
-        assert int(ffi.cast("enum foo", "B")) == 1
-        assert int(ffi.cast("enum foo", "CC")) == 2
-        assert int(ffi.cast("enum foo", "D")) == 3
-        ffi.cdef("enum bar { A, B=-2, CC, D };")
-        assert int(ffi.cast("enum bar", "A")) == 0
-        assert int(ffi.cast("enum bar", "B")) == -2
-        assert int(ffi.cast("enum bar", "CC")) == -1
-        assert int(ffi.cast("enum bar", "D")) == 0
-        assert ffi.cast("enum bar", "B") != ffi.cast("enum bar", "B")
-        assert ffi.cast("enum foo", "A") != ffi.cast("enum bar", "A")
-        assert ffi.cast("enum bar", "A") != ffi.cast("int", 0)
-        assert repr(ffi.cast("enum bar", "CC")) == "<cdata 'enum bar' 'CC'>"
-        py.test.raises(ValueError, ffi.cast, "enum bar", "UNKNOWN")
+        assert ffi.string(ffi.cast("enum foo", 0)) == "A"
+        assert ffi.string(ffi.cast("enum foo", 2)) == "CC"
+        assert ffi.string(ffi.cast("enum foo", 3)) == "D"
+        assert ffi.string(ffi.cast("enum foo", 4)) == "4"
+        ffi.cdef("enum bar { A, B=-2, CC, D, E };")
+        assert ffi.string(ffi.cast("enum bar", 0)) == "A"
+        assert ffi.string(ffi.cast("enum bar", -2)) == "B"
+        assert ffi.string(ffi.cast("enum bar", -1)) == "CC"
+        assert ffi.string(ffi.cast("enum bar", 1)) == "E"
+        assert ffi.cast("enum bar", -2) != ffi.cast("enum bar", -2)
+        assert ffi.cast("enum foo", 0) != ffi.cast("enum bar", 0)
+        assert ffi.cast("enum bar", 0) != ffi.cast("int", 0)
+        assert repr(ffi.cast("enum bar", -1)) == "<cdata 'enum bar' -1: CC>"
+        assert repr(ffi.cast("enum foo", -1)) == (  # enums are unsigned, if
+            "<cdata 'enum foo' 4294967295>")        # they contain no neg value
         ffi.cdef("enum baz { A=0x1000, B=0x2000 };")
-        assert int(ffi.cast("enum baz", "A")) == 0x1000
-        assert int(ffi.cast("enum baz", "B")) == 0x2000
+        assert ffi.string(ffi.cast("enum baz", 0x1000)) == "A"
+        assert ffi.string(ffi.cast("enum baz", 0x2000)) == "B"
 
     def test_enum_in_struct(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum foo { A, B, C, D }; struct bar { enum foo e; };")
         s = ffi.new("struct bar *")
         s.e = 0
-        assert s.e == "A"
-        s.e = "D"
-        assert s.e == "D"
-        assert s[0].e == "D"
-        s[0].e = "C"
-        assert s.e == "C"
-        assert s[0].e == "C"
+        assert s.e == 0
+        s.e = 3
+        assert s.e == 3
+        assert s[0].e == 3
+        s[0].e = 2
+        assert s.e == 2
+        assert s[0].e == 2
         s.e = ffi.cast("enum foo", -1)
-        assert s.e == '#-1'
-        assert s[0].e == '#-1'
+        assert s.e == 4294967295
+        assert s[0].e == 4294967295
         s.e = s.e
-        py.test.raises(TypeError, "s.e = None")
+        py.test.raises(TypeError, "s.e = 'B'")
+        py.test.raises(TypeError, "s.e = '2'")
+        py.test.raises(TypeError, "s.e = '#2'")
+        py.test.raises(TypeError, "s.e = '#7'")
 
     def test_enum_non_contiguous(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum foo { A, B=42, C };")
-        assert int(ffi.cast("enum foo", "A")) == 0
-        assert int(ffi.cast("enum foo", "B")) == 42
-        assert int(ffi.cast("enum foo", "C")) == 43
         assert ffi.string(ffi.cast("enum foo", 0)) == "A"
         assert ffi.string(ffi.cast("enum foo", 42)) == "B"
         assert ffi.string(ffi.cast("enum foo", 43)) == "C"
         invalid_value = ffi.cast("enum foo", 2)
         assert int(invalid_value) == 2
-        assert ffi.string(invalid_value) == "#2"
+        assert ffi.string(invalid_value) == "2"
 
     def test_array_of_struct(self):
         ffi = FFI(backend=self.Backend())
@@ -1301,7 +1302,7 @@
     def test_enum_with_non_injective_mapping(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("enum e { AA=0, BB=0, CC=0, DD=0 };")
-        e = ffi.cast("enum e", 'CC')
+        e = ffi.cast("enum e", 0)
         assert ffi.string(e) == "AA"     # pick the first one arbitrarily
 
     def test_nested_anonymous_struct(self):
diff --git a/testing/test_unicode_literals.py b/testing/test_unicode_literals.py
--- a/testing/test_unicode_literals.py
+++ b/testing/test_unicode_literals.py
@@ -49,7 +49,7 @@
 def test_enum():
     ffi = FFI()
     ffi.cdef("enum foo_e { AA, BB, CC };")        # unicode literal
-    x = ffi.cast("enum foo_e", "BB")
+    x = ffi.cast("enum foo_e", 1)
     assert int(ffi.cast("int", x)) == 1
 
 def test_dlopen():
diff --git a/testing/test_verify.py b/testing/test_verify.py
--- a/testing/test_verify.py
+++ b/testing/test_verify.py
@@ -558,13 +558,12 @@
     ffi.cdef("enum ee { EE1, EE2, EE3, ... \n \t };")
     py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE2')
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
-    assert int(ffi.cast('enum ee', 'EE3')) == -10
-    py.test.raises(ValueError, ffi.cast, 'enum ee', '__dotdotdot0__')
+    assert ffi.string(ffi.cast('enum ee', 11)) == "EE2"
+    assert ffi.string(ffi.cast('enum ee', -10)) == "EE3"
     #
     # try again
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
+    assert ffi.string(ffi.cast('enum ee', 11)) == "EE2"
 
 def test_full_enum():
     ffi = FFI()
@@ -578,25 +577,35 @@
     lib = ffi.verify("enum ee { EE1, EE2, EE3, EE4 };")
     assert lib.EE3 == 2
 
+def test_enum_usage():
+    ffi = FFI()
+    ffi.cdef("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;")
+    lib = ffi.verify("enum ee { EE1,EE2 }; typedef struct { enum ee x; } *sp;")
+    assert lib.EE2 == 1
+    s = ffi.new("sp", [lib.EE2])
+    assert s.x == 1
+    s.x = 17
+    assert s.x == 17
+
 def test_nonfull_enum_syntax2():
     ffi = FFI()
     ffi.cdef("enum ee { EE1, EE2=\t..., EE3 };")
     py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1')
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
-    assert int(ffi.cast('enum ee', 'EE3')) == -10
+    assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2'
+    assert ffi.string(ffi.cast('enum ee', -10)) == 'EE3'
     #
     ffi = FFI()
     ffi.cdef("enum ee { EE1, EE2=\t... };")
     py.test.raises(VerificationMissing, ffi.cast, 'enum ee', 'EE1')
     ffi.verify("enum ee { EE1=10, EE2, EE3=-10, EE4 };")
-    assert int(ffi.cast('enum ee', 'EE2')) == 11
+    assert ffi.string(ffi.cast('enum ee', 11)) == 'EE2'
     #
     ffi = FFI()
     ffi.cdef("enum ee2 { EE4=..., EE5=..., ... };")
     ffi.verify("enum ee2 { EE4=-1234-5, EE5 }; ")
-    assert int(ffi.cast('enum ee2', 'EE4')) == -1239
-    assert int(ffi.cast('enum ee2', 'EE5')) == -1238
+    assert ffi.string(ffi.cast('enum ee2', -1239)) == 'EE4'
+    assert ffi.string(ffi.cast('enum ee2', -1238)) == 'EE5'
 
 def test_get_set_errno():
     ffi = FFI()
@@ -961,7 +970,7 @@
         int foo_func(enum foo_e e) { return e; }
     """)
     assert lib.foo_func(lib.BB) == 2
-    assert lib.foo_func("BB") == 2
+    py.test.raises(TypeError, lib.foo_func, "BB")
 
 def test_enum_as_function_result():
     ffi = FFI()
@@ -973,7 +982,7 @@
         enum foo_e { AA, CC, BB };
         enum foo_e foo_func(int x) { return x; }
     """)
-    assert lib.foo_func(lib.BB) == "BB"
+    assert lib.foo_func(lib.BB) == lib.BB == 2
 
 def test_enum_values():
     ffi = FFI()
@@ -1001,7 +1010,7 @@
     ffi = FFI()
     ffi.cdef("typedef enum { AA, BB, ... } enum1_t;")
     lib = ffi.verify("typedef enum { AA, CC, BB } enum1_t;")
-    assert ffi.string(ffi.cast("enum1_t", 1)) == '#1'
+    assert ffi.string(ffi.cast("enum1_t", 1)) == '1'
     assert ffi.string(ffi.cast("enum1_t", 2)) == 'BB'
     assert lib.AA == 0
     assert lib.BB == 2
@@ -1016,7 +1025,7 @@
         typedef enum { AA, CC, BB } foo_t;
         foo_t foo_func(int x) { return x; }
     """)
-    assert lib.foo_func(lib.BB) == "BB"
+    assert lib.foo_func(lib.BB) == lib.BB == 2
 
 def test_callback_calling_convention():
     py.test.skip("later")
@@ -1429,7 +1438,7 @@
     ffi2.cdef("int myfunc(enum foo_e);")
     lib2 = ffi2.verify("enum foo_e { CC, BB, AA };"
                        "int myfunc(enum foo_e x) { return (int)x; }")
-    res = lib2.myfunc("AA")
+    res = lib2.myfunc(lib2.AA)
     assert res == 2
 
 def test_string_to_voidp_arg():


More information about the pypy-commit mailing list