Author: Armin Rigo <ar...@tunes.org> 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 pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit