Author: Armin Rigo <[email protected]>
Branch:
Changeset: r1160:70927696eb9c
Date: 2013-02-16 10:17 +0100
http://bitbucket.org/cffi/cffi/changeset/70927696eb9c/
Log: Moving the determination of the base integer type of an enum out of
C code, into the 'cffi' package. This is work in progress; it
should eventually permit the new test to fully pass. For now wrote
a warning in the doc.
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -4253,20 +4253,15 @@
char *ename;
PyObject *enumerators, *enumvalues;
PyObject *dict1 = NULL, *dict2 = NULL, *combined = NULL, *tmpkey = NULL;
- ffi_type *ffitype;
int name_size;
- CTypeDescrObject *td;
+ CTypeDescrObject *td, *basetd;
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",
+
+ if (!PyArg_ParseTuple(args, "sO!O!O!:new_enum_type",
&ename,
&PyTuple_Type, &enumerators,
- &PyTuple_Type, &enumvalues))
+ &PyTuple_Type, &enumvalues,
+ &CTypeDescr_Type, &basetd))
return NULL;
n = PyTuple_GET_SIZE(enumerators);
@@ -4276,6 +4271,12 @@
return NULL;
}
+ if (!(basetd->ct_flags & (CT_PRIMITIVE_SIGNED|CT_PRIMITIVE_UNSIGNED))) {
+ PyErr_SetString(PyExc_TypeError,
+ "expected a primitive signed or unsigned base type");
+ return NULL;
+ }
+
dict1 = PyDict_New();
if (dict1 == NULL)
goto error;
@@ -4284,8 +4285,7 @@
goto error;
for (i=n; --i >= 0; ) {
- long lvalue;
- unsigned long ulvalue;
+ long long lvalue;
PyObject *value = PyTuple_GET_ITEM(enumvalues, i);
tmpkey = PyTuple_GET_ITEM(enumerators, i);
Py_INCREF(tmpkey);
@@ -4308,31 +4308,8 @@
goto error;
}
}
- lvalue = PyLong_AsLong(value);
- 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 (convert_from_object((char*)&lvalue, basetd, value) < 0)
+ goto error; /* out-of-range or badly typed 'value' */
if (PyDict_SetItem(dict1, tmpkey, value) < 0)
goto error;
if (PyDict_SetItem(dict2, value, tmpkey) < 0)
@@ -4341,32 +4318,6 @@
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;
@@ -4374,12 +4325,6 @@
Py_CLEAR(dict2);
Py_CLEAR(dict1);
- switch (size) {
- case 4: ffitype = &ffi_type_sint32; break;
- case 8: ffitype = &ffi_type_sint64; break;
- default: Py_FatalError("'int' or 'long' is not 4 or 8 bytes"); return NULL;
- }
-
name_size = strlen("enum ") + strlen(ename) + 1;
td = ctypedescr_new(name_size);
if (td == NULL)
@@ -4388,11 +4333,10 @@
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 = size;
- td->ct_length = size == sizeof(int) ? offsetof(struct aligncheck_int, y)
- : offsetof(struct aligncheck_long, y);
- td->ct_extra = ffitype;
- td->ct_flags = flags;
+ td->ct_size = basetd->ct_size;
+ td->ct_length = basetd->ct_length; /* alignment */
+ td->ct_extra = basetd->ct_extra; /* ffi type */
+ td->ct_flags = basetd->ct_flags | CT_IS_ENUM;
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
@@ -1272,25 +1272,29 @@
py.test.raises(TypeError, callback, BFunc, cb, -42)
def test_enum_type():
- BEnum = new_enum_type("foo", (), ())
+ BUInt = new_primitive_type("unsigned int")
+ BEnum = new_enum_type("foo", (), (), BUInt)
assert repr(BEnum) == "<ctype 'enum foo'>"
assert BEnum.kind == "enum"
assert BEnum.cname == "enum foo"
assert BEnum.elements == {}
#
- BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
+ BInt = new_primitive_type("int")
+ BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20), BInt)
assert BEnum.kind == "enum"
assert BEnum.elements == {-20: 'ab', 0: 'def', 1: 'c'}
# 'elements' is not the real dict, but merely a copy
BEnum.elements[2] = '??'
assert BEnum.elements == {-20: 'ab', 0: 'def', 1: 'c'}
#
- BEnum = new_enum_type("bar", ('ab', 'cd'), (5, 5))
+ BEnum = new_enum_type("bar", ('ab', 'cd'), (5, 5), BUInt)
assert BEnum.elements == {5: 'ab'}
assert BEnum.relements == {'ab': 5, 'cd': 5}
def test_cast_to_enum():
- BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
+ BInt = new_primitive_type("int")
+ BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20), BInt)
+ assert sizeof(BEnum) == sizeof(BInt)
e = cast(BEnum, 0)
assert repr(e) == "<cdata 'enum foo' 0: def>"
assert repr(cast(BEnum, -42)) == "<cdata 'enum foo' -42>"
@@ -1302,18 +1306,27 @@
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))
+ BUInt = new_primitive_type("unsigned int")
+ BEnum = new_enum_type("bar", ('def', 'c', 'ab'), (0, 1, 20), BUInt)
e = cast(BEnum, -1)
assert repr(e) == "<cdata 'enum bar' 4294967295>" # unsigned int
+ #
+ BLong = new_primitive_type("long")
+ BEnum = new_enum_type("baz", (), (), BLong)
+ assert sizeof(BEnum) == sizeof(BLong)
+ e = cast(BEnum, -1)
+ assert repr(e) == "<cdata 'enum baz' -1>"
def test_enum_with_non_injective_mapping():
- BEnum = new_enum_type("foo", ('ab', 'cd'), (7, 7))
+ BInt = new_primitive_type("int")
+ BEnum = new_enum_type("foo", ('ab', 'cd'), (7, 7), BInt)
e = cast(BEnum, 7)
assert repr(e) == "<cdata 'enum foo' 7: ab>"
assert string(e) == 'ab'
def test_enum_in_struct():
- BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
+ BInt = new_primitive_type("int")
+ BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20), BInt)
BStruct = new_struct_type("bar")
BStructPtr = new_pointer_type(BStruct)
complete_struct_or_union(BStruct, [('a1', BEnum, -1)])
@@ -1326,7 +1339,7 @@
"unsupported operand type for int(): 'NoneType'" in str(e.value)) #PyPy
py.test.raises(TypeError, 'p.a1 = "def"')
if sys.version_info < (3,):
- BEnum2 = new_enum_type(unicode("foo"), (unicode('abc'),), (5,))
+ BEnum2 = new_enum_type(unicode("foo"), (unicode('abc'),), (5,), BInt)
assert string(cast(BEnum2, 5)) == 'abc'
assert type(string(cast(BEnum2, 5))) is str
@@ -1335,66 +1348,25 @@
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")
+ for BPrimitive in [new_primitive_type("int"),
+ new_primitive_type("unsigned int"),
+ new_primitive_type("long"),
+ new_primitive_type("unsigned long")]:
+ for x in [max_uint, max_int, max_ulong, max_long]:
+ for testcase in [x, x+1, -x-1, -x-2]:
+ if int(cast(BPrimitive, testcase)) == testcase:
+ # fits
+ BEnum = new_enum_type("foo", ("AA",), (testcase,),
+ BPrimitive)
+ assert int(cast(BEnum, testcase)) == testcase
+ else:
+ # overflows
+ py.test.raises(OverflowError, new_enum_type,
+ "foo", ("AA",), (testcase,), BPrimitive)
def test_callback_returning_enum():
BInt = new_primitive_type("int")
- BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20))
+ BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20), BInt)
def cb(n):
if n & 1:
return cast(BEnum, n)
@@ -1410,7 +1382,8 @@
def test_callback_returning_enum_unsigned():
BInt = new_primitive_type("int")
- BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, 20))
+ BUInt = new_primitive_type("unsigned int")
+ BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, 20), BUInt)
def cb(n):
if n & 1:
return cast(BEnum, n)
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -924,27 +924,10 @@
CTypesFunctionPtr._fix_class()
return CTypesFunctionPtr
- def new_enum_type(self, name, enumerators, enumvalues):
+ def new_enum_type(self, name, enumerators, enumvalues, CTypesInt):
assert isinstance(name, str)
reverse_mapping = dict(zip(reversed(enumvalues),
reversed(enumerators)))
- 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/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -356,10 +356,11 @@
partial = False
partial_resolved = False
- def __init__(self, name, enumerators, enumvalues):
+ def __init__(self, name, enumerators, enumvalues, baseinttype=None):
self.name = name
self.enumerators = enumerators
self.enumvalues = enumvalues
+ self.baseinttype = baseinttype
def check_not_partial(self):
if self.partial and not self.partial_resolved:
@@ -368,9 +369,41 @@
def build_backend_type(self, ffi, finishlist):
self.check_not_partial()
+ base_btype = self.build_baseinttype(ffi, finishlist)
return global_cache(self, ffi, 'new_enum_type', self.name,
- self.enumerators, self.enumvalues, key=self)
+ self.enumerators, self.enumvalues,
+ base_btype, key=self)
+ def build_baseinttype(self, ffi, finishlist):
+ if self.baseinttype is not None:
+ return self.baseinttype.get_cached_btype(ffi, finishlist)
+ #
+ if self.enumvalues:
+ smallest_value = min(self.enumvalues)
+ largest_value = max(self.enumvalues)
+ else:
+ smallest_value = 0
+ largest_value = 0
+ if smallest_value < 0: # needs a signed type
+ sign = 1
+ candidate1 = PrimitiveType("int")
+ candidate2 = PrimitiveType("long")
+ else:
+ sign = 0
+ candidate1 = PrimitiveType("unsigned int")
+ candidate2 = PrimitiveType("unsigned long")
+ btype1 = candidate1.get_cached_btype(ffi, finishlist)
+ btype2 = candidate2.get_cached_btype(ffi, finishlist)
+ size1 = ffi.sizeof(btype1)
+ size2 = ffi.sizeof(btype2)
+ if (smallest_value >= ((-1) << (8*size1-1)) and
+ largest_value < (1 << (8*size1-sign))):
+ return btype1
+ if (smallest_value >= ((-1) << (8*size2-1)) and
+ largest_value < (1 << (8*size2-sign))):
+ return btype2
+ raise api.CDefError("%s values don't all fit into either 'long' "
+ "or 'unsigned long'" % self._get_c_name(''))
def unknown_type(name, structname=None):
if structname is None:
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -1216,7 +1216,10 @@
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``.
+ 0.5 and before, it was always ``int``. *Unimplemented: if the very
+ large values are not declared, the enum size will be incorrectly
+ deduced! Work around this by making sure that you name the largest
+ value and/or any negative value in the cdef.*
Debugging dlopen'ed C libraries
diff --git a/testing/test_verify.py b/testing/test_verify.py
--- a/testing/test_verify.py
+++ b/testing/test_verify.py
@@ -1442,6 +1442,35 @@
res = lib2.myfunc(lib2.AA)
assert res == 2
+def test_enum_size():
+ cases = [('123', 4, 4294967295),
+ ('4294967295U', 4, 4294967295),
+ ('-123', 4, -1),
+ ('-2147483647-1', 4, -1),
+ ]
+ if FFI().sizeof("long") == 8:
+ cases += [('4294967296L', 8, 2**64-1),
+ ('%dUL' % (2**64-1), 8, 2**64-1),
+ ('-2147483649L', 8, -1),
+ ('%dL-1L' % (1-2**63), 8, -1)]
+ for hidden_value, expected_size, expected_minus1 in cases:
+ ffi = FFI()
+ ffi.cdef("enum foo_e { AA, BB, ... };")
+ lib = ffi.verify("enum foo_e { AA, BB=%s };" % hidden_value)
+ assert lib.AA == 0
+ assert lib.BB == eval(hidden_value.replace('U', '').replace('L', ''))
+ assert ffi.sizeof("enum foo_e") == expected_size
+ assert int(ffi.cast("enum foo_e", -1)) == expected_minus1
+ # test with the large value hidden:
+ # disabled so far, doesn't work
+## for hidden_value, expected_size, expected_minus1 in cases:
+## ffi = FFI()
+## ffi.cdef("enum foo_e { AA, BB, ... };")
+## lib = ffi.verify("enum foo_e { AA, BB=%s };" % hidden_value)
+## assert lib.AA == 0
+## assert ffi.sizeof("enum foo_e") == expected_size
+## assert int(ffi.cast("enum foo_e", -1)) == expected_minus1
+
def test_string_to_voidp_arg():
ffi = FFI()
ffi.cdef("int myfunc(void *);")
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit