Repository: dtolnay/cxx (language: rust)

Issue:
Fails to compile unique ptr containing C++ type with lifetime that is elided
```cpp
// include/header.h

#pragma once
#include <memory>
#include <string>

struct Borrowed {};
std::unique_ptr<Borrowed> f(const std::string &x);
```

```rust
// src/lib.rs

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("example/include/example.h");
        type Borrowed<'a>;
        fn f(x: &CxxString) -> UniquePtr<Borrowed>;
    }
}
```

```console
error: expected one of `!`, `(`, `+`, `::`, `<`, `where`, or `{`, found `'a`
 --> src/lib.rs:5:23
  |
5 |         type Borrowed<'a>;
  |                       ^^ unexpected token
6 |         fn f(x: &CxxString) -> UniquePtr<Borrowed>;
  |                                                  - expected one of 7 possible tokens
```

The generated code contains a chunk that looks like `unsafe impl < 'a > :: cxx :: private :: UniquePtrTarget for Borrowed 'a { /*...*/ }`, which is not valid Rust syntax.

This is a regression in 1.0.39.


Discussion / hints:

Fix patch:
diff --git a/macro/src/generics.rs b/macro/src/generics.rs
index 6c391fc72..3de0330ba 100644
--- a/macro/src/generics.rs
+++ b/macro/src/generics.rs
@@ -3,6 +3,7 @@ use crate::syntax::resolve::Resolution;
 use crate::syntax::Impl;
 use proc_macro2::TokenStream;
 use quote::ToTokens;
+use syn::Token;
 
 pub struct ImplGenerics<'a> {
     explicit_impl: Option<&'a Impl>,
@@ -46,10 +47,17 @@ impl<'a> ToTokens for TyGenerics<'a> {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         if let Some(imp) = self.explicit_impl {
             imp.ty_generics.to_tokens(tokens);
-        } else {
-            self.key.lt_token.to_tokens(tokens);
+        } else if !self.resolve.generics.lifetimes.is_empty() {
+            let span = self.key.rust.span();
+            self.key
+                .lt_token
+                .unwrap_or_else(|| Token![<](span))
+                .to_tokens(tokens);
             self.resolve.generics.lifetimes.to_tokens(tokens);
-            self.key.gt_token.to_tokens(tokens);
+            self.key
+                .gt_token
+                .unwrap_or_else(|| Token![>](span))
+                .to_tokens(tokens);
         }
     }
 }


Test patch:
diff --git a/tests/ui/expected_named.rs b/tests/ui/expected_named.rs
new file mode 100644
index 000000000..31626d1f6
--- /dev/null
+++ b/tests/ui/expected_named.rs
@@ -0,0 +1,9 @@
+#[cxx::bridge]
+mod ffi {
+    unsafe extern "C++" {
+        type Borrowed<'a>;
+        fn borrowed() -> UniquePtr<Borrowed>;
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/expected_named.stderr b/tests/ui/expected_named.stderr
new file mode 100644
index 000000000..46764014d
--- /dev/null
+++ b/tests/ui/expected_named.stderr
@@ -0,0 +1,11 @@
+error[E0106]: missing lifetime specifier
+ --> $DIR/expected_named.rs:5:36
+  |
+5 |         fn borrowed() -> UniquePtr<Borrowed>;
+  |                                    ^^^^^^^^ expected named lifetime parameter
+  |
+  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
+help: consider using the `'static` lifetime
+  |
+5 |         fn borrowed() -> UniquePtr<Borrowed<'static>>;
+  |                                    ^^^^^^^^^^^^^^^^^