Repository: dtolnay/cxx (language: rust)

Issue:
Lifetimes on an opaque C++ type
For example suppose we have a C++ type which stores references:

```cpp
class Resource;

class Holder {
  Holder(const Resource &resource) : resource(resource) {}
  const Resource &resource;
};

std::shared_ptr<Holder> makeHolder(const Resource &resource) {
  return std::make_shared<Holder>(resource);
}
```

It would be most appropriate to describe this in Rust with a lifetime as:

```rust
#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        type Resource;
        type Holder<'a>;

        fn makeHolder<'a>(resource: &'a Resource) -> SharedPtr<Holder<'a>>;

        // or with lifetime elision
        fn makeHolder(resource: &Resource) -> SharedPtr<Holder>;
    }
}
```

Currently CXX doesn't handle the lifetimes on an extern type:

```console
  error[cxxbridge]: expected `=` or `:`
    ┌─ src/main.rs:5:20
    │
  5 │         type Holder<'a>;
    │                    ^ expected `=` or `:`
```


Discussion / hints:
Hit an ICE while working on this: https://github.com/rust-lang/rust/issues/80468. I think it happens only in otherwise erroneous code so it shouldn't be a blocker, but it's possible it would come up often when using this feature.

Fix patch:
diff --git a/syntax/check.rs b/syntax/check.rs
index b315ae724..9a8881f25 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -71,11 +71,6 @@ fn check_type_ident(cx: &mut Check, name: &NamedType) {
     {
         let msg = format!("unsupported type: {}", ident);
         cx.error(ident, &msg);
-        return;
-    }
-
-    if !name.generics.lifetimes.is_empty() {
-        cx.error(name, "type with lifetime parameter is not supported yet");
     }
 }
 
@@ -341,7 +336,10 @@ fn check_api_type(cx: &mut Check, ety: &ExternType) {
     }
 
     if let Some(lifetime) = ety.generics.lifetimes.first() {
-        cx.error(lifetime, "extern type with lifetimes is not supported yet");
+        if ety.lang == Lang::Rust {
+            let msg = "extern Rust type with lifetimes is not supported yet";
+            cx.error(lifetime, msg);
+        }
     }
 
     if !ety.bounds.is_empty() {
@@ -447,10 +445,6 @@ fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) {
         let msg = format!("derive({}) on extern type alias is not supported", derive);
         cx.error(derive, msg);
     }
-
-    if let Some(lifetime) = alias.generics.lifetimes.first() {
-        cx.error(lifetime, "extern type with lifetimes is not supported yet");
-    }
 }
 
 fn check_api_impl(cx: &mut Check, imp: &Impl) {


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 9e7b35145..ba28f500a 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -210,6 +210,15 @@ pub mod ffi {
         type Job = crate::module::ffi::Job;
     }
 
+    unsafe extern "C++" {
+        type Borrow<'a>;
+
+        fn c_return_borrow<'a>(s: &'a CxxString) -> UniquePtr<Borrow<'a>>;
+
+        #[rust_name = "c_return_borrow_elided"]
+        fn c_return_borrow(s: &CxxString) -> UniquePtr<Borrow>;
+    }
+
     #[repr(u32)]
     #[derive(Hash)]
     enum COwnedEnum {
diff --git a/tests/ffi/tests.cc b/tests/ffi/tests.cc
index 9c3c62aef..57c48f5ae 100644
--- a/tests/ffi/tests.cc
+++ b/tests/ffi/tests.cc
@@ -207,6 +207,12 @@ ::A::B::ABEnum c_return_nested_ns_enum(uint16_t n) {
   }
 }
 
+Borrow::Borrow(const std::string &s) : s(s) {}
+
+std::unique_ptr<Borrow> c_return_borrow(const std::string &s) {
+  return std::unique_ptr<Borrow>(new Borrow(s));
+}
+
 void c_take_primitive(size_t n) {
   if (n == 2020) {
     cxx_test_suite_set_correct();
diff --git a/tests/ffi/tests.h b/tests/ffi/tests.h
index 8235f5f8d..44922862f 100644
--- a/tests/ffi/tests.h
+++ b/tests/ffi/tests.h
@@ -77,6 +77,11 @@ enum COwnedEnum {
   CVAL2,
 };
 
+struct Borrow {
+  Borrow(const std::string &s);
+  const std::string &s;
+};
+
 size_t c_return_primitive();
 Shared c_return_shared();
 ::A::AShared c_return_ns_shared();
@@ -110,6 +115,7 @@ size_t c_return_sum(size_t n1, size_t n2);
 Enum c_return_enum(uint16_t n);
 ::A::AEnum c_return_ns_enum(uint16_t n);
 ::A::B::ABEnum c_return_nested_ns_enum(uint16_t n);
+std::unique_ptr<Borrow> c_return_borrow(const std::string &s);
 
 void c_take_primitive(size_t n);
 void c_take_shared(Shared shared);
diff --git a/tests/ui/extern_type_lifetime.rs b/tests/ui/extern_type_lifetime.rs
deleted file mode 100644
index 4e0e2f7f1..000000000
--- a/tests/ui/extern_type_lifetime.rs
+++ /dev/null
@@ -1,8 +0,0 @@
-#[cxx::bridge]
-mod ffi {
-    extern "C++" {
-        type Complex<'a, 'b>;
-    }
-}
-
-fn main() {}
diff --git a/tests/ui/extern_type_lifetime.stderr b/tests/ui/extern_type_lifetime.stderr
deleted file mode 100644
index 85a5d3962..000000000
--- a/tests/ui/extern_type_lifetime.stderr
+++ /dev/null
@@ -1,5 +0,0 @@
-error: extern type with lifetimes is not supported yet
- --> $DIR/extern_type_lifetime.rs:4:22
-  |
-4 |         type Complex<'a, 'b>;
-  |                      ^^