Commits

Armin Rigo committed c0fb2ac

Fix enums to use the same rule as gcc: they are actually not always
'int', but the first of the following types that fits: 'unsigned int',
'int', 'unsigned long', 'long'.

  • Participants
  • Parent commits 7ee5999
  • Branches enum-as-int

Comments (0)

Files changed (4)

File c/_cffi_backend.c

     return (PyObject *)cd;
 }
 
-static PyObject *convert_cdata_to_enum_string(CDataObject *cd, int both)
-{
-    int value;
-    PyObject *d_key, *d_value;
-    CTypeDescrObject *ct = cd->c_type;
-
-    assert(ct->ct_flags & CT_IS_ENUM);
-    value = (int)read_raw_signed_data(cd->c_data, ct->ct_size);
-    d_key = PyInt_FromLong(value);
-    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)
-            d_value = PyText_FromFormat("%d: %s", value,
-                                        PyText_AS_UTF8(d_value));
-        else
-            Py_INCREF(d_value);
-    }
-    else
-        d_value = PyObject_Str(d_key);
-    Py_DECREF(d_key);
-    return d_value;
-}
-
 static CDataObject *_new_casted_primitive(CTypeDescrObject *ct);  /*forward*/
 
 static PyObject *
 
 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;
                 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
     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,
 
     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);
             }
         }
         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;
         tmpkey = NULL;
     }
 
+    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);
     if (combined == NULL)
         goto error;
     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;
     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;
 
     assert int(cast(BEnum, 0)) == 0
     assert int(cast(BEnum, -242 + 2**128)) == -242
     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))
         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")
     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")
     BChar = new_primitive_type("char")

File cffi/backend_ctypes.py

         assert isinstance(name, str)
         reverse_mapping = dict(zip(reversed(enumvalues),
                                    reversed(enumerators)))
-        CTypesInt = self.ffi._get_cached_btype(model.PrimitiveType('int'))
+        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__ = []

File testing/backend_tests.py

         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 ffi.string(ffi.cast("enum baz", 0x1000)) == "A"
         assert ffi.string(ffi.cast("enum baz", 0x2000)) == "B"
         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 = 'B'")
+        py.test.raises(TypeError, "s.e = '2'")
         py.test.raises(TypeError, "s.e = '#2'")
         py.test.raises(TypeError, "s.e = '#7'")