Repository: dtolnay/cxx (language: rust)

Issue:
Order sensitivity across namespaces
The namespace sorting introduced in #370 does not handle alphabetically lesser namespaces referencing items from alphabetically greater namespaces.

```rust
#[cxx::bridge]
mod ffi {
    #[namespace = "first"]
    struct First {
        second: Box<Second>,
    }

    #[namespace = "second"]
    struct Second {
        i: i32,
    }
}
```

```console
target/debug/build/demo-d74fe843f72e57b6/out/cxxbridge/sources/demo/src/main.rs.cc:154:17: error: ‘::second’ has not been declared
  154 |   ::rust::Box<::second::Second> second;
      |                 ^~~~~~
target/debug/build/demo-d74fe843f72e57b6/out/cxxbridge/sources/demo/src/main.rs.cc:154:31: error: template argument 1 is invalid
  154 |   ::rust::Box<::second::Second> second;
      |                               ^
```


Discussion / hints:
Good spot.

I am assuming that the correct fix is to write out forward declarations across all namespaces, and then iterate all the namespaces again to write everything else. I shall do so when I get a moment.
Actually that is nonsense and won't work at all. I need to first look into how sensitive cxx is to definition order irrespective of namespaces.
There is order sensitivity without namespaces too:

```rust
    struct A {
        b: B
    }

    struct B {
        c: i32
    }
```

works in normal Rust code, but does not work in a `#[cxx::bridge]` mod.

```
cargo:warning=/Users/adetaylor/dev/cxx/target/debug/build/demo-0110b4ddc5238254/out/cxxbridge/sources/demo/src/main.rs.cc:205:21: error: field has incomplete type '::org::example::B'
  cargo:warning=  ::org::example::B b;
  cargo:warning=                    ^
  cargo:warning=/Users/adetaylor/dev/cxx/target/debug/build/demo-0110b4ddc5238254/out/cxxbridge/sources/demo/src/main.rs.cc:189:8: note: forward declaration of 'org::example::B'
  cargo:warning=struct B;
  cargo:warning=       ^
  cargo:warning=1 error generated.
```

I assume this is a known limitation of what can be put in `cxx::bridge` mods, but I think it probably makes sense to fix this at the same time by doing a topological sort of all the structs then outputting them in that order. This sounds fun. However, it would be nice to use an algorithm which, instead of sorting into a list of types, sorts into a list _of lists_ of types. Then within each inner list we can continue to sort by namespace to avoid outputting too many zillions of namespace declarations.
The "field has incomplete type" issue is #292. I should have used `second: Box<Second>` for this one; fixed the repro at the top. Topological sort could solve both but I would also accept just forward declarations to close this one.
OK cool. I will probably just do the forward declaration thing then. It may not be for a few days; it's proving to be a busy weekend.

Fix patch:
diff --git a/gen/src/write.rs b/gen/src/write.rs
index f25922239..dfdade22e 100644
--- a/gen/src/write.rs
+++ b/gen/src/write.rs
@@ -32,6 +32,7 @@ pub(super) fn gen<'a>(apis: &[Api], types: &'a Types, opt: &Opt, header: bool) -
 
     let apis_by_namespace = NamespaceEntries::new(apis);
 
+    gen_namespace_forward_declarations(&apis_by_namespace, out);
     gen_namespace_contents(&apis_by_namespace, types, opt, header, out);
 
     if !header {
@@ -44,13 +45,7 @@ pub(super) fn gen<'a>(apis: &[Api], types: &'a Types, opt: &Opt, header: bool) -
     out_file
 }
 
-fn gen_namespace_contents(
-    ns_entries: &NamespaceEntries,
-    types: &Types,
-    opt: &Opt,
-    header: bool,
-    out: &mut OutFile,
-) {
+fn gen_namespace_forward_declarations(ns_entries: &NamespaceEntries, out: &mut OutFile) {
     let apis = ns_entries.entries();
 
     out.next_section();
@@ -63,6 +58,24 @@ fn gen_namespace_contents(
         }
     }
 
+    out.next_section();
+
+    for (child_ns, child_ns_entries) in ns_entries.children() {
+        writeln!(out, "namespace {} {{", child_ns);
+        gen_namespace_forward_declarations(&child_ns_entries, out);
+        writeln!(out, "}} // namespace {}", child_ns);
+    }
+}
+
+fn gen_namespace_contents(
+    ns_entries: &NamespaceEntries,
+    types: &Types,
+    opt: &Opt,
+    header: bool,
+    out: &mut OutFile,
+) {
+    let apis = ns_entries.entries();
+
     let mut methods_for_type = HashMap::new();
     for api in apis.iter() {
         if let Api::RustFunction(efn) = api {


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index fee5bcb11..c79aff3d1 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -101,6 +101,16 @@ pub mod ffi {
         z: usize,
     }
 
+    #[namespace = "first"]
+    struct First {
+        second: Box<Second>,
+    }
+
+    #[namespace = "second"]
+    struct Second {
+        i: i32,
+    }
+
     extern "C" {
         include!("tests/ffi/tests.h");