From 82132cf30e3d715a934e80389b100058940c9ebd Mon Sep 17 00:00:00 2001
From: Walter Gray <walter@0m.dev>
Date: Thu, 16 Jan 2025 15:34:08 -0800
Subject: [PATCH 1/3] Add support for nested namespaces for C++

---
 tool/src/cpp/formatter.rs |  2 ++
 tool/src/cpp/header.rs    | 23 ++++++++++++++++++-----
 2 files changed, 20 insertions(+), 5 deletions(-)

diff --git a/tool/src/cpp/formatter.rs b/tool/src/cpp/formatter.rs
index f9abef2f9..231a5e01a 100644
--- a/tool/src/cpp/formatter.rs
+++ b/tool/src/cpp/formatter.rs
@@ -56,6 +56,7 @@ impl<'tcx> Cpp2Formatter<'tcx> {
             .rename
             .apply(resolved.name().as_str().into());
         if let Some(ref ns) = resolved.attrs().namespace {
+            let ns = ns.replace("::", std::path::MAIN_SEPARATOR_STR);
             format!("{ns}/{type_name}.d.hpp")
         } else {
             format!("{type_name}.d.hpp")
@@ -70,6 +71,7 @@ impl<'tcx> Cpp2Formatter<'tcx> {
             .rename
             .apply(resolved.name().as_str().into());
         if let Some(ref ns) = resolved.attrs().namespace {
+            let ns = ns.replace("::", std::path::MAIN_SEPARATOR_STR);
             format!("{ns}/{type_name}.hpp")
         } else {
             format!("{type_name}.hpp")
diff --git a/tool/src/cpp/header.rs b/tool/src/cpp/header.rs
index 0224ed32f..d5cf5818b 100644
--- a/tool/src/cpp/header.rs
+++ b/tool/src/cpp/header.rs
@@ -121,9 +121,12 @@ impl fmt::Write for Header {
 
 impl fmt::Display for Header {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let header_guard = &self.path;
-        let header_guard = header_guard.replace(".d.hpp", "_D_HPP").replace('/', "_");
-        let header_guard = header_guard.replace(".hpp", "_HPP").replace('/', "_");
+        let header_guard = &self
+            .path
+            .replace(".d.hpp", "_D_HPP")
+            .replace(".hpp", "_HPP")
+            .replace("\\", "_")
+            .replace("/", "_");
         let body: Cow<str> = if self.body.is_empty() {
             "// No Content\n\n".into()
         } else {
@@ -146,10 +149,20 @@ impl fmt::Display for Header {
                             if ns == other_ns {
                                 Cow::Borrowed(file)
                             } else {
-                                Cow::Owned(format!("../{s}"))
+                                Cow::Owned(format!(
+                                    "{}{s}",
+                                    "../".repeat(
+                                        s.chars().filter(|c| *c == '/' || *c == '\\').count() + 1
+                                    )
+                                ))
                             }
                         } else {
-                            Cow::Owned(format!("../{s}"))
+                            Cow::Owned(format!(
+                                "{}{s}",
+                                "../".repeat(
+                                    ns.chars().filter(|c| *c == '/' || *c == '\\').count() + 1
+                                )
+                            ))
                         }
                     } else {
                         Cow::Borrowed(s.as_str())

From 098fee94054c1a900945f91fbc46fd4d08d3d58f Mon Sep 17 00:00:00 2001
From: Walter Gray <walter@0m.dev>
Date: Tue, 21 Jan 2025 17:36:29 -0800
Subject: [PATCH 2/3] Improve logic for computing paths to relative files for
 C++

---
 .../cpp/include/nested/ns/Nested.d.hpp        | 38 +++++++++
 .../cpp/include/nested/ns/Nested.hpp          | 47 +++++++++++
 .../cpp/include/nested/ns2/Nested.d.hpp       | 38 +++++++++
 .../cpp/include/nested/ns2/Nested.hpp         | 47 +++++++++++
 feature_tests/cpp/tests/attrs.cpp             | 13 +--
 feature_tests/src/attrs.rs                    | 10 +++
 tool/src/cpp/formatter.rs                     |  4 +-
 tool/src/cpp/header.rs                        | 81 +++++++++++++------
 8 files changed, 246 insertions(+), 32 deletions(-)
 create mode 100644 feature_tests/cpp/include/nested/ns/Nested.d.hpp
 create mode 100644 feature_tests/cpp/include/nested/ns/Nested.hpp
 create mode 100644 feature_tests/cpp/include/nested/ns2/Nested.d.hpp
 create mode 100644 feature_tests/cpp/include/nested/ns2/Nested.hpp

diff --git a/feature_tests/cpp/include/nested/ns/Nested.d.hpp b/feature_tests/cpp/include/nested/ns/Nested.d.hpp
new file mode 100644
index 000000000..ad331baee
--- /dev/null
+++ b/feature_tests/cpp/include/nested/ns/Nested.d.hpp
@@ -0,0 +1,38 @@
+#ifndef nested_ns_Nested_D_HPP
+#define nested_ns_Nested_D_HPP
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <memory>
+#include <optional>
+#include "../../diplomat_runtime.hpp"
+
+
+namespace nested::ns {
+namespace capi {
+    struct Nested;
+} // namespace capi
+} // namespace
+
+namespace nested::ns {
+class Nested {
+public:
+
+  inline const nested::ns::capi::Nested* AsFFI() const;
+  inline nested::ns::capi::Nested* AsFFI();
+  inline static const nested::ns::Nested* FromFFI(const nested::ns::capi::Nested* ptr);
+  inline static nested::ns::Nested* FromFFI(nested::ns::capi::Nested* ptr);
+  inline static void operator delete(void* ptr);
+private:
+  Nested() = delete;
+  Nested(const nested::ns::Nested&) = delete;
+  Nested(nested::ns::Nested&&) noexcept = delete;
+  Nested operator=(const nested::ns::Nested&) = delete;
+  Nested operator=(nested::ns::Nested&&) noexcept = delete;
+  static void operator delete[](void*, size_t) = delete;
+};
+
+} // namespace
+#endif // nested_ns_Nested_D_HPP
diff --git a/feature_tests/cpp/include/nested/ns/Nested.hpp b/feature_tests/cpp/include/nested/ns/Nested.hpp
new file mode 100644
index 000000000..8bec22bc1
--- /dev/null
+++ b/feature_tests/cpp/include/nested/ns/Nested.hpp
@@ -0,0 +1,47 @@
+#ifndef nested_ns_Nested_HPP
+#define nested_ns_Nested_HPP
+
+#include "Nested.d.hpp"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <memory>
+#include <optional>
+#include "../../diplomat_runtime.hpp"
+
+
+namespace nested::ns {
+namespace capi {
+    extern "C" {
+    
+    
+    void namespace_Nested_destroy(Nested* self);
+    
+    } // extern "C"
+} // namespace capi
+} // namespace
+
+inline const nested::ns::capi::Nested* nested::ns::Nested::AsFFI() const {
+  return reinterpret_cast<const nested::ns::capi::Nested*>(this);
+}
+
+inline nested::ns::capi::Nested* nested::ns::Nested::AsFFI() {
+  return reinterpret_cast<nested::ns::capi::Nested*>(this);
+}
+
+inline const nested::ns::Nested* nested::ns::Nested::FromFFI(const nested::ns::capi::Nested* ptr) {
+  return reinterpret_cast<const nested::ns::Nested*>(ptr);
+}
+
+inline nested::ns::Nested* nested::ns::Nested::FromFFI(nested::ns::capi::Nested* ptr) {
+  return reinterpret_cast<nested::ns::Nested*>(ptr);
+}
+
+inline void nested::ns::Nested::operator delete(void* ptr) {
+  nested::ns::capi::namespace_Nested_destroy(reinterpret_cast<nested::ns::capi::Nested*>(ptr));
+}
+
+
+#endif // nested_ns_Nested_HPP
diff --git a/feature_tests/cpp/include/nested/ns2/Nested.d.hpp b/feature_tests/cpp/include/nested/ns2/Nested.d.hpp
new file mode 100644
index 000000000..9c81a5e30
--- /dev/null
+++ b/feature_tests/cpp/include/nested/ns2/Nested.d.hpp
@@ -0,0 +1,38 @@
+#ifndef nested_ns2_Nested_D_HPP
+#define nested_ns2_Nested_D_HPP
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <memory>
+#include <optional>
+#include "../../diplomat_runtime.hpp"
+
+
+namespace nested::ns2 {
+namespace capi {
+    struct Nested;
+} // namespace capi
+} // namespace
+
+namespace nested::ns2 {
+class Nested {
+public:
+
+  inline const nested::ns2::capi::Nested* AsFFI() const;
+  inline nested::ns2::capi::Nested* AsFFI();
+  inline static const nested::ns2::Nested* FromFFI(const nested::ns2::capi::Nested* ptr);
+  inline static nested::ns2::Nested* FromFFI(nested::ns2::capi::Nested* ptr);
+  inline static void operator delete(void* ptr);
+private:
+  Nested() = delete;
+  Nested(const nested::ns2::Nested&) = delete;
+  Nested(nested::ns2::Nested&&) noexcept = delete;
+  Nested operator=(const nested::ns2::Nested&) = delete;
+  Nested operator=(nested::ns2::Nested&&) noexcept = delete;
+  static void operator delete[](void*, size_t) = delete;
+};
+
+} // namespace
+#endif // nested_ns2_Nested_D_HPP
diff --git a/feature_tests/cpp/include/nested/ns2/Nested.hpp b/feature_tests/cpp/include/nested/ns2/Nested.hpp
new file mode 100644
index 000000000..b6ca0ccf9
--- /dev/null
+++ b/feature_tests/cpp/include/nested/ns2/Nested.hpp
@@ -0,0 +1,47 @@
+#ifndef nested_ns2_Nested_HPP
+#define nested_ns2_Nested_HPP
+
+#include "Nested.d.hpp"
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <memory>
+#include <optional>
+#include "../../diplomat_runtime.hpp"
+
+
+namespace nested::ns2 {
+namespace capi {
+    extern "C" {
+    
+    
+    void namespace_Nested2_destroy(Nested* self);
+    
+    } // extern "C"
+} // namespace capi
+} // namespace
+
+inline const nested::ns2::capi::Nested* nested::ns2::Nested::AsFFI() const {
+  return reinterpret_cast<const nested::ns2::capi::Nested*>(this);
+}
+
+inline nested::ns2::capi::Nested* nested::ns2::Nested::AsFFI() {
+  return reinterpret_cast<nested::ns2::capi::Nested*>(this);
+}
+
+inline const nested::ns2::Nested* nested::ns2::Nested::FromFFI(const nested::ns2::capi::Nested* ptr) {
+  return reinterpret_cast<const nested::ns2::Nested*>(ptr);
+}
+
+inline nested::ns2::Nested* nested::ns2::Nested::FromFFI(nested::ns2::capi::Nested* ptr) {
+  return reinterpret_cast<nested::ns2::Nested*>(ptr);
+}
+
+inline void nested::ns2::Nested::operator delete(void* ptr) {
+  nested::ns2::capi::namespace_Nested2_destroy(reinterpret_cast<nested::ns2::capi::Nested*>(ptr));
+}
+
+
+#endif // nested_ns2_Nested_HPP
diff --git a/feature_tests/cpp/tests/attrs.cpp b/feature_tests/cpp/tests/attrs.cpp
index 932b5d509..ad616579a 100644
--- a/feature_tests/cpp/tests/attrs.cpp
+++ b/feature_tests/cpp/tests/attrs.cpp
@@ -2,18 +2,21 @@
 #include "../include/ns/AttrOpaque1Renamed.hpp"
 #include "../include/ns/RenamedAttrEnum.hpp"
 #include "../include/Unnamespaced.hpp"
+#include "../include/nested/ns/Nested.hpp"
+#include "../include/nested/ns2/Nested.hpp"
 #include "assert.hpp"
 
-int main(int argc, char *argv[]) {
+int main(int argc, char *argv[])
+{
     std::unique_ptr<ns::AttrOpaque1Renamed> r = ns::AttrOpaque1Renamed::totally_not_new();
     simple_assert_eq("method should call", r->method_renamed(), 77);
     simple_assert_eq("method should call", r->abirenamed(), 123);
 
     // These C names should also resolve
-    void* renamed = (void*)ns::capi::renamed_on_abi_only;
-    std::cout<<"Renamed function at "<<renamed<<std::endl;
-    renamed = (void*)ns::capi::namespace_AttrOpaque1_method;
-    std::cout<<"Renamed function at "<<renamed<<std::endl;
+    void *renamed = (void *)ns::capi::renamed_on_abi_only;
+    std::cout << "Renamed function at " << renamed << std::endl;
+    renamed = (void *)ns::capi::namespace_AttrOpaque1_method;
+    std::cout << "Renamed function at " << renamed << std::endl;
 
     ns::RenamedAttrEnum e = ns::RenamedAttrEnum::A;
 
diff --git a/feature_tests/src/attrs.rs b/feature_tests/src/attrs.rs
index b0f565f32..89c0c8950 100644
--- a/feature_tests/src/attrs.rs
+++ b/feature_tests/src/attrs.rs
@@ -60,6 +60,16 @@ pub mod ffi {
         pub fn use_namespaced(&self, _n: &AttrOpaque1) {}
     }
 
+    #[diplomat::opaque]
+    #[diplomat::attr(auto, namespace = "nested::ns")]
+    #[diplomat::attr(not(kotlin), rename = "Nested")]
+    pub struct Nested;
+
+    #[diplomat::opaque]
+    #[diplomat::attr(auto, namespace = "nested::ns2")]
+    #[diplomat::attr(not(kotlin), rename = "Nested")]
+    pub struct Nested2;
+
     #[diplomat::opaque]
     #[diplomat::attr(not(supports = comparators), disable)]
     pub struct Comparable(u8);
diff --git a/tool/src/cpp/formatter.rs b/tool/src/cpp/formatter.rs
index 231a5e01a..1ac5a9fab 100644
--- a/tool/src/cpp/formatter.rs
+++ b/tool/src/cpp/formatter.rs
@@ -56,7 +56,7 @@ impl<'tcx> Cpp2Formatter<'tcx> {
             .rename
             .apply(resolved.name().as_str().into());
         if let Some(ref ns) = resolved.attrs().namespace {
-            let ns = ns.replace("::", std::path::MAIN_SEPARATOR_STR);
+            let ns = ns.replace("::", "/");
             format!("{ns}/{type_name}.d.hpp")
         } else {
             format!("{type_name}.d.hpp")
@@ -71,7 +71,7 @@ impl<'tcx> Cpp2Formatter<'tcx> {
             .rename
             .apply(resolved.name().as_str().into());
         if let Some(ref ns) = resolved.attrs().namespace {
-            let ns = ns.replace("::", std::path::MAIN_SEPARATOR_STR);
+            let ns = ns.replace("::", "/");
             format!("{ns}/{type_name}.hpp")
         } else {
             format!("{type_name}.hpp")
diff --git a/tool/src/cpp/header.rs b/tool/src/cpp/header.rs
index d5cf5818b..7ea5e6bd0 100644
--- a/tool/src/cpp/header.rs
+++ b/tool/src/cpp/header.rs
@@ -143,31 +143,7 @@ impl fmt::Display for Header {
             includes: self
                 .includes
                 .iter()
-                .map(|s| {
-                    if let Some((ns, _)) = self.path.split_once('/') {
-                        if let Some((other_ns, file)) = s.split_once('/') {
-                            if ns == other_ns {
-                                Cow::Borrowed(file)
-                            } else {
-                                Cow::Owned(format!(
-                                    "{}{s}",
-                                    "../".repeat(
-                                        s.chars().filter(|c| *c == '/' || *c == '\\').count() + 1
-                                    )
-                                ))
-                            }
-                        } else {
-                            Cow::Owned(format!(
-                                "{}{s}",
-                                "../".repeat(
-                                    ns.chars().filter(|c| *c == '/' || *c == '\\').count() + 1
-                                )
-                            ))
-                        }
-                    } else {
-                        Cow::Borrowed(s.as_str())
-                    }
-                })
+                .map(|s| path_diff(&self.path, s))
                 .collect(),
             forwards: &self.forwards,
             body,
@@ -177,3 +153,58 @@ impl fmt::Display for Header {
         f.write_char('\n')
     }
 }
+
+// As rsplit_once, except the first of the tuple will include the delimiter pattern
+fn rsplit_once_inclusive(str: &str, delim: char) -> Option<(&str, &str)> {
+    str.rfind(delim).map(|i| str.split_at(i + 1))
+}
+
+/// Returns the path to 'path', relative to 'base'
+fn path_diff<'a>(base: &'a str, path: &'a str) -> Cow<'a, str> {
+    let (mut base_ns, _) = rsplit_once_inclusive(base, '/').unwrap_or(("", base));
+    let (mut path_ns, file) = rsplit_once_inclusive(path, '/').unwrap_or(("", path));
+
+    let mut matching_chars = 0;
+    // Consume and count the length of the matching section
+    loop {
+        let b = base_ns.split_once('/');
+        let p = path_ns.split_once('/');
+        if let (Some(b), Some(p)) = (b, p) {
+            if b.0 == p.0 {
+                base_ns = b.1;
+                path_ns = p.1;
+                matching_chars += b.0.len() + 1; // 1 for the consumed delimiter
+                continue;
+            }
+        }
+        break;
+    }
+
+    // Base has run out without a mismatch, the relative path is a strict subset of path & can be borrowed
+    if base_ns.len() == 0 {
+        return path.split_at(matching_chars).1.into();
+    } else {
+        let up_dirs = base_ns.matches('/').count();
+        return ("../".repeat(up_dirs) + path_ns + file).into();
+    }
+}
+
+#[test]
+fn test_path_diff() {
+    let a = "a/same.hpp";
+    let b = "a/same2.hpp";
+    assert_eq!(path_diff(a, b), "same2.hpp");
+
+    let a = "root.hpp";
+    let b = "a/nested.hpp";
+    assert_eq!(path_diff(a, b), "a/nested.hpp");
+
+    let a = "a/nested.hpp";
+    let b = "root.hpp";
+    assert_eq!(path_diff(a, b), "../root.hpp");
+
+    let a = "a/b/c/d.hpp";
+    let b = "a/b/z/c/d.hpp";
+
+    assert_eq!(path_diff(a, b), "../z/c/d.hpp");
+}

From 50d135a5d0f210a5abe44afd3abcad35d37f5cce Mon Sep 17 00:00:00 2001
From: Walter Gray <walter@0m.dev>
Date: Wed, 22 Jan 2025 13:47:39 -0800
Subject: [PATCH 3/3] Fix lint & CI errors

---
 feature_tests/c/include/Nested.d.h            | 19 +++++++++
 feature_tests/c/include/Nested.h              | 25 +++++++++++
 feature_tests/c/include/Nested2.d.h           | 19 +++++++++
 feature_tests/c/include/Nested2.h             | 25 +++++++++++
 .../dart/lib/src/RenamedNested.g.dart         | 28 +++++++++++++
 .../dart/lib/src/RenamedNested2.g.dart        | 28 +++++++++++++
 feature_tests/dart/lib/src/lib.g.dart         |  2 +
 feature_tests/js/api/RenamedNested.d.ts       |  9 ++++
 feature_tests/js/api/RenamedNested.mjs        | 41 +++++++++++++++++++
 feature_tests/js/api/RenamedNested2.d.ts      |  9 ++++
 feature_tests/js/api/RenamedNested2.mjs       | 41 +++++++++++++++++++
 feature_tests/js/api/index.d.ts               |  4 ++
 feature_tests/js/api/index.mjs                |  4 ++
 .../kotlin/dev/diplomattest/somelib/Nested.kt | 31 ++++++++++++++
 .../dev/diplomattest/somelib/Nested2.kt       | 31 ++++++++++++++
 feature_tests/src/attrs.rs                    |  4 +-
 tool/src/cpp/header.rs                        |  6 +--
 17 files changed, 321 insertions(+), 5 deletions(-)
 create mode 100644 feature_tests/c/include/Nested.d.h
 create mode 100644 feature_tests/c/include/Nested.h
 create mode 100644 feature_tests/c/include/Nested2.d.h
 create mode 100644 feature_tests/c/include/Nested2.h
 create mode 100644 feature_tests/dart/lib/src/RenamedNested.g.dart
 create mode 100644 feature_tests/dart/lib/src/RenamedNested2.g.dart
 create mode 100644 feature_tests/js/api/RenamedNested.d.ts
 create mode 100644 feature_tests/js/api/RenamedNested.mjs
 create mode 100644 feature_tests/js/api/RenamedNested2.d.ts
 create mode 100644 feature_tests/js/api/RenamedNested2.mjs
 create mode 100644 feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested.kt
 create mode 100644 feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested2.kt

diff --git a/feature_tests/c/include/Nested.d.h b/feature_tests/c/include/Nested.d.h
new file mode 100644
index 000000000..f25e915ce
--- /dev/null
+++ b/feature_tests/c/include/Nested.d.h
@@ -0,0 +1,19 @@
+#ifndef Nested_D_H
+#define Nested_D_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "diplomat_runtime.h"
+
+
+
+
+
+typedef struct Nested Nested;
+
+
+
+
+#endif // Nested_D_H
diff --git a/feature_tests/c/include/Nested.h b/feature_tests/c/include/Nested.h
new file mode 100644
index 000000000..c5af245e9
--- /dev/null
+++ b/feature_tests/c/include/Nested.h
@@ -0,0 +1,25 @@
+#ifndef Nested_H
+#define Nested_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "diplomat_runtime.h"
+
+
+#include "Nested.d.h"
+
+
+
+
+
+
+
+void namespace_Nested_destroy(Nested* self);
+
+
+
+
+
+#endif // Nested_H
diff --git a/feature_tests/c/include/Nested2.d.h b/feature_tests/c/include/Nested2.d.h
new file mode 100644
index 000000000..3dc7e3fff
--- /dev/null
+++ b/feature_tests/c/include/Nested2.d.h
@@ -0,0 +1,19 @@
+#ifndef Nested2_D_H
+#define Nested2_D_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "diplomat_runtime.h"
+
+
+
+
+
+typedef struct Nested2 Nested2;
+
+
+
+
+#endif // Nested2_D_H
diff --git a/feature_tests/c/include/Nested2.h b/feature_tests/c/include/Nested2.h
new file mode 100644
index 000000000..2e91d3265
--- /dev/null
+++ b/feature_tests/c/include/Nested2.h
@@ -0,0 +1,25 @@
+#ifndef Nested2_H
+#define Nested2_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "diplomat_runtime.h"
+
+
+#include "Nested2.d.h"
+
+
+
+
+
+
+
+void namespace_Nested2_destroy(Nested2* self);
+
+
+
+
+
+#endif // Nested2_H
diff --git a/feature_tests/dart/lib/src/RenamedNested.g.dart b/feature_tests/dart/lib/src/RenamedNested.g.dart
new file mode 100644
index 000000000..71c07778a
--- /dev/null
+++ b/feature_tests/dart/lib/src/RenamedNested.g.dart
@@ -0,0 +1,28 @@
+// generated by diplomat-tool
+
+part of 'lib.g.dart';
+
+final class RenamedNested implements ffi.Finalizable {
+  final ffi.Pointer<ffi.Opaque> _ffi;
+
+  // These are "used" in the sense that they keep dependencies alive
+  // ignore: unused_field
+  final core.List<Object> _selfEdge;
+
+  // This takes in a list of lifetime edges (including for &self borrows)
+  // corresponding to data this may borrow from. These should be flat arrays containing
+  // references to objects, and this object will hold on to them to keep them alive and
+  // maintain borrow validity.
+  RenamedNested._fromFfi(this._ffi, this._selfEdge) {
+    if (_selfEdge.isEmpty) {
+      _finalizer.attach(this, _ffi.cast());
+    }
+  }
+
+  static final _finalizer = ffi.NativeFinalizer(ffi.Native.addressOf(_namespace_Nested_destroy));
+}
+
+@meta.RecordUse()
+@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>)>(isLeaf: true, symbol: 'namespace_Nested_destroy')
+// ignore: non_constant_identifier_names
+external void _namespace_Nested_destroy(ffi.Pointer<ffi.Void> self);
diff --git a/feature_tests/dart/lib/src/RenamedNested2.g.dart b/feature_tests/dart/lib/src/RenamedNested2.g.dart
new file mode 100644
index 000000000..d9c75a4fb
--- /dev/null
+++ b/feature_tests/dart/lib/src/RenamedNested2.g.dart
@@ -0,0 +1,28 @@
+// generated by diplomat-tool
+
+part of 'lib.g.dart';
+
+final class RenamedNested2 implements ffi.Finalizable {
+  final ffi.Pointer<ffi.Opaque> _ffi;
+
+  // These are "used" in the sense that they keep dependencies alive
+  // ignore: unused_field
+  final core.List<Object> _selfEdge;
+
+  // This takes in a list of lifetime edges (including for &self borrows)
+  // corresponding to data this may borrow from. These should be flat arrays containing
+  // references to objects, and this object will hold on to them to keep them alive and
+  // maintain borrow validity.
+  RenamedNested2._fromFfi(this._ffi, this._selfEdge) {
+    if (_selfEdge.isEmpty) {
+      _finalizer.attach(this, _ffi.cast());
+    }
+  }
+
+  static final _finalizer = ffi.NativeFinalizer(ffi.Native.addressOf(_namespace_Nested2_destroy));
+}
+
+@meta.RecordUse()
+@ffi.Native<ffi.Void Function(ffi.Pointer<ffi.Void>)>(isLeaf: true, symbol: 'namespace_Nested2_destroy')
+// ignore: non_constant_identifier_names
+external void _namespace_Nested2_destroy(ffi.Pointer<ffi.Void> self);
diff --git a/feature_tests/dart/lib/src/lib.g.dart b/feature_tests/dart/lib/src/lib.g.dart
index d3f59089f..2804db3d5 100644
--- a/feature_tests/dart/lib/src/lib.g.dart
+++ b/feature_tests/dart/lib/src/lib.g.dart
@@ -47,6 +47,8 @@ part 'RenamedComparable.g.dart';
 part 'RenamedMyIndexer.g.dart';
 part 'RenamedMyIterable.g.dart';
 part 'RenamedMyIterator.g.dart';
+part 'RenamedNested.g.dart';
+part 'RenamedNested2.g.dart';
 part 'RenamedOpaqueIterable.g.dart';
 part 'RenamedOpaqueIterator.g.dart';
 part 'ResultOpaque.g.dart';
diff --git a/feature_tests/js/api/RenamedNested.d.ts b/feature_tests/js/api/RenamedNested.d.ts
new file mode 100644
index 000000000..0789b90cd
--- /dev/null
+++ b/feature_tests/js/api/RenamedNested.d.ts
@@ -0,0 +1,9 @@
+// generated by diplomat-tool
+import type { pointer, codepoint } from "./diplomat-runtime.d.ts";
+
+
+
+export class RenamedNested {
+    
+    get ffiValue(): pointer;
+}
\ No newline at end of file
diff --git a/feature_tests/js/api/RenamedNested.mjs b/feature_tests/js/api/RenamedNested.mjs
new file mode 100644
index 000000000..4966774a4
--- /dev/null
+++ b/feature_tests/js/api/RenamedNested.mjs
@@ -0,0 +1,41 @@
+// generated by diplomat-tool
+import wasm from "./diplomat-wasm.mjs";
+import * as diplomatRuntime from "./diplomat-runtime.mjs";
+
+const RenamedNested_box_destroy_registry = new FinalizationRegistry((ptr) => {
+    wasm.namespace_Nested_destroy(ptr);
+});
+
+export class RenamedNested {
+    
+    // Internal ptr reference:
+    #ptr = null;
+
+    // Lifetimes are only to keep dependencies alive.
+    // Since JS won't garbage collect until there are no incoming edges.
+    #selfEdge = [];
+    
+    #internalConstructor(symbol, ptr, selfEdge) {
+        if (symbol !== diplomatRuntime.internalConstructor) {
+            console.error("RenamedNested is an Opaque type. You cannot call its constructor.");
+            return;
+        }
+        
+        this.#ptr = ptr;
+        this.#selfEdge = selfEdge;
+        
+        // Are we being borrowed? If not, we can register.
+        if (this.#selfEdge.length === 0) {
+            RenamedNested_box_destroy_registry.register(this, this.#ptr);
+        }
+        
+        return this;
+    }
+    get ffiValue() {
+        return this.#ptr;
+    }
+
+    constructor(symbol, ptr, selfEdge) {
+        return this.#internalConstructor(...arguments)
+    }
+}
\ No newline at end of file
diff --git a/feature_tests/js/api/RenamedNested2.d.ts b/feature_tests/js/api/RenamedNested2.d.ts
new file mode 100644
index 000000000..3bf349a79
--- /dev/null
+++ b/feature_tests/js/api/RenamedNested2.d.ts
@@ -0,0 +1,9 @@
+// generated by diplomat-tool
+import type { pointer, codepoint } from "./diplomat-runtime.d.ts";
+
+
+
+export class RenamedNested2 {
+    
+    get ffiValue(): pointer;
+}
\ No newline at end of file
diff --git a/feature_tests/js/api/RenamedNested2.mjs b/feature_tests/js/api/RenamedNested2.mjs
new file mode 100644
index 000000000..f03b6d503
--- /dev/null
+++ b/feature_tests/js/api/RenamedNested2.mjs
@@ -0,0 +1,41 @@
+// generated by diplomat-tool
+import wasm from "./diplomat-wasm.mjs";
+import * as diplomatRuntime from "./diplomat-runtime.mjs";
+
+const RenamedNested2_box_destroy_registry = new FinalizationRegistry((ptr) => {
+    wasm.namespace_Nested2_destroy(ptr);
+});
+
+export class RenamedNested2 {
+    
+    // Internal ptr reference:
+    #ptr = null;
+
+    // Lifetimes are only to keep dependencies alive.
+    // Since JS won't garbage collect until there are no incoming edges.
+    #selfEdge = [];
+    
+    #internalConstructor(symbol, ptr, selfEdge) {
+        if (symbol !== diplomatRuntime.internalConstructor) {
+            console.error("RenamedNested2 is an Opaque type. You cannot call its constructor.");
+            return;
+        }
+        
+        this.#ptr = ptr;
+        this.#selfEdge = selfEdge;
+        
+        // Are we being borrowed? If not, we can register.
+        if (this.#selfEdge.length === 0) {
+            RenamedNested2_box_destroy_registry.register(this, this.#ptr);
+        }
+        
+        return this;
+    }
+    get ffiValue() {
+        return this.#ptr;
+    }
+
+    constructor(symbol, ptr, selfEdge) {
+        return this.#internalConstructor(...arguments)
+    }
+}
\ No newline at end of file
diff --git a/feature_tests/js/api/index.d.ts b/feature_tests/js/api/index.d.ts
index 53f4f56e9..e89fbbc6e 100644
--- a/feature_tests/js/api/index.d.ts
+++ b/feature_tests/js/api/index.d.ts
@@ -40,6 +40,10 @@ export { RenamedMyIterable } from "./RenamedMyIterable"
 
 export { RenamedMyIterator } from "./RenamedMyIterator"
 
+export { RenamedNested } from "./RenamedNested"
+
+export { RenamedNested2 } from "./RenamedNested2"
+
 export { RenamedOpaqueIterable } from "./RenamedOpaqueIterable"
 
 export { RenamedOpaqueIterator } from "./RenamedOpaqueIterator"
diff --git a/feature_tests/js/api/index.mjs b/feature_tests/js/api/index.mjs
index bd45d4014..984b3b25c 100644
--- a/feature_tests/js/api/index.mjs
+++ b/feature_tests/js/api/index.mjs
@@ -38,6 +38,10 @@ export { RenamedMyIterable } from "./RenamedMyIterable.mjs"
 
 export { RenamedMyIterator } from "./RenamedMyIterator.mjs"
 
+export { RenamedNested } from "./RenamedNested.mjs"
+
+export { RenamedNested2 } from "./RenamedNested2.mjs"
+
 export { RenamedOpaqueIterable } from "./RenamedOpaqueIterable.mjs"
 
 export { RenamedOpaqueIterator } from "./RenamedOpaqueIterator.mjs"
diff --git a/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested.kt b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested.kt
new file mode 100644
index 000000000..04e4a49d1
--- /dev/null
+++ b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested.kt
@@ -0,0 +1,31 @@
+package dev.diplomattest.somelib;
+import com.sun.jna.Callback
+import com.sun.jna.Library
+import com.sun.jna.Native
+import com.sun.jna.Pointer
+import com.sun.jna.Structure
+
+
+internal interface NestedLib: Library {
+    fun namespace_Nested_destroy(handle: Pointer)
+}
+
+class Nested internal constructor (
+    internal val handle: Pointer,
+    // These ensure that anything that is borrowed is kept alive and not cleaned
+    // up by the garbage collector.
+    internal val selfEdges: List<Any>,
+)  {
+
+    internal class NestedCleaner(val handle: Pointer, val lib: NestedLib) : Runnable {
+        override fun run() {
+            lib.namespace_Nested_destroy(handle)
+        }
+    }
+
+    companion object {
+        internal val libClass: Class<NestedLib> = NestedLib::class.java
+        internal val lib: NestedLib = Native.load("somelib", libClass)
+    }
+
+}
diff --git a/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested2.kt b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested2.kt
new file mode 100644
index 000000000..7ff17e3a1
--- /dev/null
+++ b/feature_tests/kotlin/somelib/src/main/kotlin/dev/diplomattest/somelib/Nested2.kt
@@ -0,0 +1,31 @@
+package dev.diplomattest.somelib;
+import com.sun.jna.Callback
+import com.sun.jna.Library
+import com.sun.jna.Native
+import com.sun.jna.Pointer
+import com.sun.jna.Structure
+
+
+internal interface Nested2Lib: Library {
+    fun namespace_Nested2_destroy(handle: Pointer)
+}
+
+class Nested2 internal constructor (
+    internal val handle: Pointer,
+    // These ensure that anything that is borrowed is kept alive and not cleaned
+    // up by the garbage collector.
+    internal val selfEdges: List<Any>,
+)  {
+
+    internal class Nested2Cleaner(val handle: Pointer, val lib: Nested2Lib) : Runnable {
+        override fun run() {
+            lib.namespace_Nested2_destroy(handle)
+        }
+    }
+
+    companion object {
+        internal val libClass: Class<Nested2Lib> = Nested2Lib::class.java
+        internal val lib: Nested2Lib = Native.load("somelib", libClass)
+    }
+
+}
diff --git a/feature_tests/src/attrs.rs b/feature_tests/src/attrs.rs
index 89c0c8950..8f01fe8a5 100644
--- a/feature_tests/src/attrs.rs
+++ b/feature_tests/src/attrs.rs
@@ -62,12 +62,12 @@ pub mod ffi {
 
     #[diplomat::opaque]
     #[diplomat::attr(auto, namespace = "nested::ns")]
-    #[diplomat::attr(not(kotlin), rename = "Nested")]
+    #[diplomat::attr(supports = namespacing, rename = "Nested")]
     pub struct Nested;
 
     #[diplomat::opaque]
     #[diplomat::attr(auto, namespace = "nested::ns2")]
-    #[diplomat::attr(not(kotlin), rename = "Nested")]
+    #[diplomat::attr(supports = namespacing, rename = "Nested")]
     pub struct Nested2;
 
     #[diplomat::opaque]
diff --git a/tool/src/cpp/header.rs b/tool/src/cpp/header.rs
index 7ea5e6bd0..642357867 100644
--- a/tool/src/cpp/header.rs
+++ b/tool/src/cpp/header.rs
@@ -181,11 +181,11 @@ fn path_diff<'a>(base: &'a str, path: &'a str) -> Cow<'a, str> {
     }
 
     // Base has run out without a mismatch, the relative path is a strict subset of path & can be borrowed
-    if base_ns.len() == 0 {
-        return path.split_at(matching_chars).1.into();
+    if base_ns.is_empty() {
+        path.split_at(matching_chars).1.into()
     } else {
         let up_dirs = base_ns.matches('/').count();
-        return ("../".repeat(up_dirs) + path_ns + file).into();
+        ("../".repeat(up_dirs) + path_ns + file).into()
     }
 }