Commits

Armin Rigo  committed 5383715

Revert 0d5efadab0ac and use a different approach: reading a field
of type 'foo[0]' returns not a <cdata 'foo[0]'>, but a <cdata 'foo *'>.
This is a bit of a special-case, but more consistent with what we
can do with the cdata.

  • Participants
  • Parent commits 0d5efad

Comments (0)

Files changed (5)

File c/_cffi_backend.c

     PyObject_HEAD
     CTypeDescrObject *cf_type;
     Py_ssize_t cf_offset;
-    short cf_bitshift, cf_bitsize;
+    short cf_bitshift;   /* >= 0: bitshift; or BS_REGULAR or BS_EMPTY_ARRAY */
+    short cf_bitsize;
     struct cfieldobject_s *cf_next;
 } CFieldObject;
+#define BS_REGULAR     (-1)      /* a regular field, not with bitshift */
+#define BS_EMPTY_ARRAY (-2)      /* a field which is an array 'type[0]' */
 
 static PyTypeObject CTypeDescr_Type;
 static PyTypeObject CField_Type;
                             "negative index not supported");
             return NULL;
         }
-        if (i >= get_array_length(cd) && cd->c_type->ct_length != 0) {
+        if (i >= get_array_length(cd)) {
             PyErr_Format(PyExc_IndexError,
                          "index too large for cdata '%s' (expected %zd < %zd)",
                          cd->c_type->ct_name,
         if (cf != NULL) {
             /* read the field 'cf' */
             char *data = cd->c_data + cf->cf_offset;
-            if (cf->cf_bitshift >= 0)
+            if (cf->cf_bitshift == BS_REGULAR)
+                return convert_to_object(data, cf->cf_type);
+            else if (cf->cf_bitshift == BS_EMPTY_ARRAY)
+                return new_simple_cdata(data,
+                    (CTypeDescrObject *)cf->cf_type->ct_stuff);
+            else
                 return convert_to_object_bitfield(data, cf);
-            else
-                return convert_to_object(data, cf->cf_type);
         }
     }
     return PyObject_GenericGetAttr((PyObject *)cd, attr);
         if (fbitsize < 0 || (fbitsize == 8 * ftype->ct_size &&
                              !(ftype->ct_flags & CT_PRIMITIVE_CHAR))) {
             fbitsize = -1;
-            bitshift = -1;
+            if (ftype->ct_flags & CT_ARRAY && ftype->ct_length == 0)
+                bitshift = BS_EMPTY_ARRAY;
+            else
+                bitshift = BS_REGULAR;
             prev_bit_position = 0;
         }
         else {
             assert repr(x).endswith("E+902>")
             assert float(x) == float("inf")
 
-def test_array_of_length_zero():
-    p = new_pointer_type(new_primitive_type("int"))
-    p0 = new_array_type(p, 0)
-    p3 = new_array_type(p, 3)
-    a1 = newp(p3, [61, 62, 63])
-    a2 = cast(p0, a1)
-    assert a2[0] == 61
-    assert a2[1] == 62
-    assert a2[2] == 63
-    a2[2] = 64
-    assert list(a1) == [61, 62, 64]
-    assert list(a2) == []
+def test_get_array_of_length_zero():
+    for length in [0, 5, 10]:
+        BLong = new_primitive_type("long")
+        BLongP = new_pointer_type(BLong)
+        BArray0 = new_array_type(BLongP, length)
+        BStruct = new_struct_type("foo")
+        BStructPtr = new_pointer_type(BStruct)
+        complete_struct_or_union(BStruct, [('a1', BArray0, -1)])
+        p = newp(BStructPtr, None)
+        if length == 0:
+            assert repr(p.a1).startswith("<cdata 'long *' 0x")
+        else:
+            assert repr(p.a1).startswith("<cdata 'long[%d]' 0x" % length)

File cffi/backend_ctypes.py

             else:
                 __slots__.append('_ctype')
             _reftypename = BItem._get_c_name(brackets)
+            _declared_length = length
+            _CTPtr = CTypesPtr
 
             def __init__(self, init):
                 if length is None:
                 return len(self._blob)
 
             def __getitem__(self, index):
-                if 0 <= index < len(self._blob):
-                    x = self._blob[index]
-                elif len(self._blob) == 0:
-                    x = ctypes.cast(self._blob, CTypesPtr._ctype)[index]
-                else:
+                if not (0 <= index < len(self._blob)):
                     raise IndexError
-                return BItem._from_ctypes(x)
+                return BItem._from_ctypes(self._blob[index])
 
             def __setitem__(self, index, value):
-                x = BItem._to_ctypes(value)
-                if 0 <= index < len(self._blob):
-                    self._blob[index] = x
-                elif len(self._blob) == 0:
-                    ctypes.cast(self._blob, CTypesPtr._ctype)[index] = x
-                else:
+                if not (0 <= index < len(self._blob)):
                     raise IndexError
+                self._blob[index] = BItem._to_ctypes(value)
 
             if kind == 'char' or kind == 'byte':
                 def _to_string(self, maxlen):
             def _get_own_repr(self):
                 if getattr(self, '_own', False):
                     return 'owning %d bytes' % (ctypes.sizeof(self._blob),)
-                return super(CTypesPtr, self)._get_own_repr()
+                return super(CTypesArray, self)._get_own_repr()
 
             def _convert_to_address(self, BClass):
                 if BClass in (CTypesPtr, None) or BClass._automatic_casts:
                         other * ctypes.sizeof(BItem._ctype))
                 else:
                     return NotImplemented
+
+            @classmethod
+            def _cast_from(cls, source):
+                raise NotImplementedError("casting to %r" % (
+                    cls._get_c_name(),))
         #
         CTypesArray._fix_class()
         return CTypesArray
                     return BField._from_ctypes(p.contents)
                 def setter(self, value, fname=fname, BField=BField):
                     setattr(self._blob, fname, BField._to_ctypes(value))
+                #
+                if issubclass(BField, CTypesGenericArray):
+                    setter = None
+                    if BField._declared_length == 0:
+                        def getter(self, fname=fname, BFieldPtr=BField._CTPtr,
+                                   offset=CTypesStructOrUnion._offsetof(fname),
+                                   PTR=ctypes.POINTER(BField._ctype)):
+                            addr = ctypes.addressof(self._blob)
+                            p = ctypes.cast(addr + offset, PTR)
+                            return BFieldPtr._from_ctypes(p)
+                #
             else:
                 def getter(self, fname=fname, BField=BField):
                     return BField._from_ctypes(getattr(self._blob, fname))

File testing/backend_tests.py

         assert repr(p) == "<cdata 'int[]' owning %d bytes>" % (2*SIZE_OF_INT)
         #
         p = ffi.new("int[]", 0)
-        #py.test.raises(IndexError, "p[0]") ---
-        #   actually works, for test_struct_containing_array_varsize_workaround
+        py.test.raises(IndexError, "p[0]")
         py.test.raises(ValueError, ffi.new, "int[]", -1)
         assert repr(p) == "<cdata 'int[]' owning 0 bytes>"
 
         f.close()
         os.unlink(filename)
 
+    def test_array_in_struct(self):
+        ffi = FFI(backend=self.Backend())
+        ffi.cdef("struct foo_s { int len; short data[5]; };")
+        p = ffi.new("struct foo_s *")
+        p.data[3] = 5
+        assert p.data[3] == 5
+        assert repr(p.data).startswith("<cdata 'short[5]' 0x")
+
     def test_struct_containing_array_varsize_workaround(self):
         ffi = FFI(backend=self.Backend())
         ffi.cdef("struct foo_s { int len; short data[0]; };")
         p = ffi.new("char[]", ffi.sizeof("struct foo_s") + 7 * SIZE_OF_SHORT)
         q = ffi.cast("struct foo_s *", p)
         assert q.len == 0
+        # 'q.data' gets not a 'short[0]', but just a 'short *' instead
+        assert repr(q.data).startswith("<cdata 'short *' 0x")
         assert q.data[6] == 0
         q.data[6] = 15
         assert q.data[6] == 15
         assert p.b == 19
         assert p.c == 17
         assert p.d == 17
+
+    def test_cast_to_array_type(self):
+        ffi = FFI(backend=self.Backend())
+        p = ffi.new("int[4]", [-5])
+        q = ffi.cast("int[3]", p)
+        assert q[0] == -5
+        assert repr(q).startswith("<cdata 'int[3]' 0x")

File testing/test_ctypes.py

     def test_array_argument_as_list(self):
         py.test.skip("ctypes backend: not supported: passing a list "
                      "for a pointer argument")
+
+    def test_cast_to_array_type(self):
+        py.test.skip("ctypes backend: not supported: casting to array")