# HG changeset patch
# User Armin Rigo <arigo@tunes.org>
# Date 1590010186 -7200
#      Wed May 20 23:29:46 2020 +0200
# Node ID 3011cd0272f3b4cd116521e2d395867c6def5d55
# Parent  6476ac8a033c2fefade6ca51c2fbc4c345100a28
#453

Special-case typedefs of arrays with '...' length, so that they can
be used through recompiler.py as if they had a specified length in
most cases.

diff --git a/cffi/cparser.py b/cffi/cparser.py
--- a/cffi/cparser.py
+++ b/cffi/cparser.py
@@ -395,7 +395,8 @@
                         realtype = self._get_unknown_ptr_type(decl)
                     else:
                         realtype, quals = self._get_type_and_quals(
-                            decl.type, name=decl.name, partial_length_ok=True)
+                            decl.type, name=decl.name, partial_length_ok=True,
+                            typedef_example="*(%s *)0" % (decl.name,))
                     self._declare('typedef ' + decl.name, realtype, quals=quals)
                 elif decl.__class__.__name__ == 'Pragma':
                     pass    # skip pragma, only in pycparser 2.15
@@ -562,7 +563,8 @@
             return model.NamedPointerType(type, declname, quals)
         return model.PointerType(type, quals)
 
-    def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False):
+    def _get_type_and_quals(self, typenode, name=None, partial_length_ok=False,
+                            typedef_example=None):
         # first, dereference typedefs, if we have it already parsed, we're good
         if (isinstance(typenode, pycparser.c_ast.TypeDecl) and
             isinstance(typenode.type, pycparser.c_ast.IdentifierType) and
@@ -579,8 +581,18 @@
             else:
                 length = self._parse_constant(
                     typenode.dim, partial_length_ok=partial_length_ok)
+            # a hack: in 'typedef int foo_t[...][...];', don't use '...' as
+            # the length but use directly the C expression that would be
+            # generated by recompiler.py.  This lets the typedef be used in
+            # many more places within recompiler.py
+            if typedef_example is not None:
+                if length == '...':
+                    length = '_cffi_array_len(%s)' % (typedef_example,)
+                typedef_example = "*" + typedef_example
+            #
             tp, quals = self._get_type_and_quals(typenode.type,
-                                partial_length_ok=partial_length_ok)
+                                partial_length_ok=partial_length_ok,
+                                typedef_example=typedef_example)
             return model.ArrayType(tp, length), quals
         #
         if isinstance(typenode, pycparser.c_ast.PtrDecl):
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -307,11 +307,14 @@
         self.c_name_with_marker = (
             self.item.c_name_with_marker.replace('&', brackets))
 
+    def length_is_unknown(self):
+        return isinstance(self.length, str)
+
     def resolve_length(self, newlength):
         return ArrayType(self.item, newlength)
 
     def build_backend_type(self, ffi, finishlist):
-        if self.length == '...':
+        if self.length_is_unknown():
             raise CDefError("cannot render the type %r: unknown length" %
                             (self,))
         self.item.get_cached_btype(ffi, finishlist)   # force the item BType
@@ -430,7 +433,7 @@
                 fsize = fieldsize[i]
                 ftype = self.fldtypes[i]
                 #
-                if isinstance(ftype, ArrayType) and ftype.length == '...':
+                if isinstance(ftype, ArrayType) and ftype.length_is_unknown():
                     # fix the length to match the total size
                     BItemType = ftype.item.get_cached_btype(ffi, finishlist)
                     nlen, nrest = divmod(fsize, ffi.sizeof(BItemType))
diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py
--- a/cffi/vengine_cpy.py
+++ b/cffi/vengine_cpy.py
@@ -762,7 +762,7 @@
         if isinstance(tp, model.ArrayType):
             tp_ptr = model.PointerType(tp.item)
             self._generate_cpy_const(False, name, tp, vartp=tp_ptr,
-                                     size_too = (tp.length == '...'))
+                                     size_too = tp.length_is_unknown())
         else:
             tp_ptr = model.PointerType(tp)
             self._generate_cpy_const(False, name, tp_ptr, category='var')
@@ -774,7 +774,7 @@
         value = getattr(library, name)
         if isinstance(tp, model.ArrayType):   # int a[5] is "constant" in the
                                               # sense that "a=..." is forbidden
-            if tp.length == '...':
+            if tp.length_is_unknown():
                 assert isinstance(value, tuple)
                 (value, size) = value
                 BItemType = self.ffi._get_cached_btype(tp.item)
diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py
--- a/cffi/vengine_gen.py
+++ b/cffi/vengine_gen.py
@@ -565,7 +565,7 @@
 
     def _generate_gen_variable_decl(self, tp, name):
         if isinstance(tp, model.ArrayType):
-            if tp.length == '...':
+            if tp.length_is_unknown():
                 prnt = self._prnt
                 funcname = '_cffi_sizeof_%s' % (name,)
                 self.export_symbols.append(funcname)
@@ -584,7 +584,7 @@
     def _loaded_gen_variable(self, tp, name, module, library):
         if isinstance(tp, model.ArrayType):   # int a[5] is "constant" in the
                                               # sense that "a=..." is forbidden
-            if tp.length == '...':
+            if tp.length_is_unknown():
                 funcname = '_cffi_sizeof_%s' % (name,)
                 BFunc = self.ffi._typeof_locked('size_t(*)(void)')[0]
                 function = module.load_function(BFunc, funcname)
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -2122,6 +2122,40 @@
     p = ffi.new("vmat_t", 4)
     assert ffi.sizeof(p[3]) == 8 * ffi.sizeof("int")
 
+def test_typedef_array_dotdotdot_usage():
+    ffi = FFI()
+    ffi.cdef("""
+        typedef int foo_t[...];
+        typedef int mat_t[...][...];
+        struct s { foo_t a; foo_t *b; foo_t **c; };
+        int myfunc(foo_t a, foo_t *b, foo_t **c);
+        struct sm { mat_t a; mat_t *b; mat_t **c; };
+        int myfuncm(mat_t a, mat_t *b, mat_t **c);
+        """)
+    lib = verify(ffi, "test_typedef_array_dotdotdot_usage", """
+        typedef int foo_t[50];
+        typedef int mat_t[6][7];
+        struct s { foo_t a; foo_t *b; foo_t **c; };
+        static int myfunc(foo_t a, foo_t *b, foo_t **c) { return (**c)[49]; }
+        struct sm { mat_t a; mat_t *b; mat_t **c; };
+        static int myfuncm(mat_t a, mat_t *b, mat_t **c) { return (**c)[5][6]; }
+    """)
+    assert ffi.sizeof("foo_t") == 50 * ffi.sizeof("int")
+    p = ffi.new("struct s *")
+    assert ffi.sizeof(p[0]) == 50 * ffi.sizeof("int") + 2 * ffi.sizeof("void *")
+    p.a[49] = 321
+    p.b = ffi.addressof(p, 'a')
+    p.c = ffi.addressof(p, 'b')
+    assert lib.myfunc(ffi.NULL, ffi.NULL, p.c) == 321
+    #
+    assert ffi.sizeof("mat_t") == 42 * ffi.sizeof("int")
+    p = ffi.new("struct sm *")
+    assert ffi.sizeof(p[0]) == 42 * ffi.sizeof("int") + 2 * ffi.sizeof("void *")
+    p.a[5][6] = -321
+    p.b = ffi.addressof(p, 'a')
+    p.c = ffi.addressof(p, 'b')
+    assert lib.myfuncm(ffi.NULL, ffi.NULL, p.c) == -321
+
 def test_call_with_custom_field_pos():
     ffi = FFI()
     ffi.cdef("""