Author: Armin Rigo <[email protected]>
Branch: enum-as-int
Changeset: r1147:c0fb2acdd6f7
Date: 2013-02-12 18:14 +0100
http://bitbucket.org/cffi/cffi/changeset/c0fb2acdd6f7/
Log: 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'.
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -823,32 +823,6 @@
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 *
@@ -1432,6 +1406,38 @@
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;
@@ -3866,8 +3872,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
@@ -4086,6 +4091,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,
@@ -4109,6 +4118,7 @@
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);
@@ -4132,11 +4142,29 @@
}
}
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;
@@ -4146,6 +4174,32 @@
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;
@@ -4153,10 +4207,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;
@@ -4167,10 +4221,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;
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -1301,6 +1301,10 @@
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))
@@ -1326,11 +1330,66 @@
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")
@@ -1348,6 +1407,23 @@
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")
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -928,7 +928,23 @@
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__ = []
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -873,6 +873,8 @@
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"
@@ -890,10 +892,11 @@
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'")
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit