Repository: dtolnay/cxx (language: rust)

Issue:
Nested structs lead to incomplete type error
If I put this in my `mod ffi`:

    struct A {
        b: B,
    }
    struct B {
        c: i32,
    }

I get this error during compilation:

```
warning: /.../lib.rs.cc:255:5: error: field has incomplete type 'org::example::B'
warning:   B b;
warning:     ^
warning: /.../lib.rs.cc:225:8: note: forward declaration of 'org::example::B'
warning: struct B;
warning:        ^
warning: 1 error generated.
```

If I change it to `b: Box<B>` it works fine.


Discussion / hints:
We'll want to implement a topological sort for data structures to fix this. The current implementation in cxx::bridge for shared structs is really basic, it just forward declares them all in original order and then defines them all in original order.

```cpp
struct A;
struct B;

struct A final {
  B b;
};

struct B final {
  int32_t c;
};
```

That's fine if forward references are boxed (as you noticed) or if the structs are topologically ordered already. (So a workaround for you is to put B before A in the cxx::bridge source file.)
Marking <kbd>help wanted</kbd>. A correct topological sort implementation here should handle translating this:

```rust
struct X { y: Y }
struct Y { z: UniquePtr<Z> }
struct Z { x: X }
```

into this:

```cpp
struct Z;

struct Y final { std::unique_ptr<Z> z; };
struct X final { Y y; };
struct Z final { X x; };
```

Fix patch:
diff --git a/gen/src/toposort.rs b/gen/src/toposort.rs
index 97b10f9dc..62a1148e4 100644
--- a/gen/src/toposort.rs
+++ b/gen/src/toposort.rs
@@ -1,8 +1,44 @@
-use crate::syntax::{Api, Types};
-use std::iter::FromIterator;
+use crate::syntax::{Api, Struct, Type, Types};
+use std::collections::btree_map::{BTreeMap as Map, Entry};
 
-pub fn sort<'a>(apis: &'a [Api], types: &Types) -> Vec<&'a Api> {
-    // TODO https://github.com/dtolnay/cxx/issues/292
-    let _ = types;
-    Vec::from_iter(apis)
+enum Mark {
+    Visiting,
+    Visited,
+}
+
+pub fn sort<'a>(apis: &'a [Api], types: &Types<'a>) -> Vec<&'a Struct> {
+    let mut sorted = Vec::new();
+    let ref mut marks = Map::new();
+    for api in apis {
+        if let Api::Struct(strct) = api {
+            visit(strct, &mut sorted, marks, types);
+        }
+    }
+    sorted
+}
+
+fn visit<'a>(
+    strct: &'a Struct,
+    sorted: &mut Vec<&'a Struct>,
+    marks: &mut Map<*const Struct, Mark>,
+    types: &Types<'a>,
+) {
+    match marks.entry(strct) {
+        Entry::Occupied(entry) => match entry.get() {
+            Mark::Visiting => panic!("not a DAG"), // FIXME
+            Mark::Visited => return,
+        },
+        Entry::Vacant(entry) => {
+            entry.insert(Mark::Visiting);
+        }
+    }
+    for field in &strct.fields {
+        if let Type::Ident(ident) = &field.ty {
+            if let Some(inner) = types.structs.get(&ident.rust) {
+                visit(inner, sorted, marks, types);
+            }
+        }
+    }
+    marks.insert(strct, Mark::Visited);
+    sorted.push(strct);
 }
diff --git a/gen/src/write.rs b/gen/src/write.rs
index c806e55d0..414ea4670 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -76,14 +76,8 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
         }
     }
 
-    for api in toposort::sort(apis, out.types) {
+    for api in apis {
         match api {
-            Api::Struct(strct) => {
-                out.next_section();
-                if !out.types.cxx.contains(&strct.name.rust) {
-                    write_struct(out, strct);
-                }
-            }
             Api::Enum(enm) => {
                 out.next_section();
                 if out.types.cxx.contains(&enm.name.rust) {
@@ -102,6 +96,13 @@ fn write_data_structures<'a>(out: &mut OutFile<'a>, apis: &'a [Api]) {
         }
     }
 
+    for strct in toposort::sort(apis, out.types) {
+        out.next_section();
+        if !out.types.cxx.contains(&strct.name.rust) {
+            write_struct(out, strct);
+        }
+    }
+
     out.next_section();
     for api in apis {
         if let Api::TypeAlias(ety) = api {


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index af8a9f540..9ea34501e 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -277,6 +277,27 @@ pub mod ffi {
         #[cxx_name = "rAliasedFunction"]
         fn r_aliased_function(x: i32) -> String;
     }
+
+    struct Dag0 {
+        i: i32,
+    }
+
+    struct Dag1 {
+        dag2: Dag2,
+        vec: Vec<Dag3>,
+    }
+
+    struct Dag2 {
+        dag4: Dag4,
+    }
+
+    struct Dag3 {
+        dag1: Dag1,
+    }
+
+    struct Dag4 {
+        dag0: Dag0,
+    }
 }
 
 pub type R = usize;