Repository: dtolnay/cxx (language: rust)

Issue:
Confusing elided-lifetimes-in-paths lint in bridge
Building the demo with `-W elided-lifetimes-in-paths` results in the following:

```
warning: hidden lifetime parameters in types are deprecated
  --> demo/src/main.rs:22:63
   |
22 |         fn new_blobstore_client() -> UniquePtr<BlobstoreClient>;
   |                                                               ^- help: indicate the anonymous lifetime: `<'_>`
   |
   = note: requested on the command line with `-W elided-lifetimes-in-paths`

warning: 1 warning emitted
```

However, neither `cxx::UniquePtr` nor `BlobstoreClient` have a lifetime parameter, so of course trying to add one is an error (and of course the lint still triggers too):

```
warning: hidden lifetime parameters in types are deprecated
  --> demo/src/main.rs:22:66
   |
22 |         fn new_blobstore_client() -> UniquePtr<BlobstoreClient<'_>>;
   |                                                                  ^^- help: indicate the anonymous lifetime: `<'_>`
   |
   = note: requested on the command line with `-W elided-lifetimes-in-paths`

error[E0106]: missing lifetime specifier
  --> demo/src/main.rs:22:64
   |
22 |         fn new_blobstore_client() -> UniquePtr<BlobstoreClient<'_>>;
   |                                                                ^^ 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
   |
22 |         fn new_blobstore_client() -> UniquePtr<BlobstoreClient<'static>>;
   |                                                                ^^^^^^^

error[E0107]: this struct takes 0 lifetime arguments but 1 lifetime argument was supplied
  --> demo/src/main.rs:22:48
   |
22 |         fn new_blobstore_client() -> UniquePtr<BlobstoreClient<'_>>;
   |                                                ^^^^^^^^^^^^^^^---- help: remove these generics
   |                                                |
   |                                                expected 0 lifetime arguments
   |
note: struct defined here, with 0 lifetime parameters
  --> demo/src/main.rs:20:14
   |
20 |         type BlobstoreClient;
   |              ^^^^^^^^^^^^^^^

error: aborting due to 2 previous errors; 1 warning emitted
```


Looking at the expanded output with `cargo expand`, I don't see any clues as to what missing anonymous lifetime the compiler might be intending to flag here. It's unclear to me if this is a lint bug, if the compiler diagnostic is just confusing due to the `cxx::bridge` proc macro, or something else.

This is an issue for us because our codebase uses both `-W rust_2018_idioms` and `-D warnings` by default. Worse, `elided-lifetimes-in-paths` can only be allowed at the crate level (https://github.com/rust-lang/rust/issues/71957) so we can't just allow it for the `cxx::bridge` module.


Discussion / hints:
Ahhhh, I see what it's trying to indicate now. It's complaining about the generated `UniquePtrTarget for BlobstoreClient` impl, which includes a `std::fmt::Formatter` type that is missing the anonymous lifetime. Ok, that should be easily fixable.
I'm running into the same issue and wondering if there is any workaround for this?

@sbrocket
How did you go about fixing it?

Fix patch:
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
index 241d6deaa..afe85d1c2 100644
--- a/macro/src/derive.rs
+++ b/macro/src/derive.rs
@@ -140,7 +140,7 @@ fn struct_debug(strct: &Struct, span: Span) -> TokenStream {
 
     quote_spanned! {span=>
         impl #generics ::cxx::core::fmt::Debug for #ident #generics {
-            fn fmt(&self, formatter: &mut ::cxx::core::fmt::Formatter) -> ::cxx::core::fmt::Result {
+            fn fmt(&self, formatter: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
                 formatter.debug_struct(#struct_name)
                     #(.field(#field_names, &self.#fields))*
                     .finish()
@@ -251,7 +251,7 @@ fn enum_debug(enm: &Enum, span: Span) -> TokenStream {
 
     quote_spanned! {span=>
         impl ::cxx::core::fmt::Debug for #ident {
-            fn fmt(&self, formatter: &mut ::cxx::core::fmt::Formatter) -> ::cxx::core::fmt::Result {
+            fn fmt(&self, formatter: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
                 match *self {
                     #(#variants)*
                     _ => ::cxx::core::write!(formatter, #fallback, self.repr),
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 087150995..1e3c8da67 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -53,7 +53,7 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types)
     for api in apis {
         if let Api::RustType(ety) = api {
             expanded.extend(expand_rust_type_import(ety));
-            hidden.extend(expand_rust_type_assert_unpin(ety));
+            hidden.extend(expand_rust_type_assert_unpin(ety, types));
         }
     }
 
@@ -70,7 +70,7 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types)
                 let ident = &ety.name.rust;
                 if !types.structs.contains_key(ident) && !types.enums.contains_key(ident) {
                     expanded.extend(expand_cxx_type(ety));
-                    hidden.extend(expand_cxx_type_assert_pinned(ety));
+                    hidden.extend(expand_cxx_type_assert_pinned(ety, types));
                 }
             }
             Api::CxxFunction(efn) => {
@@ -78,7 +78,7 @@ fn expand(ffi: Module, doc: Doc, attrs: OtherAttrs, apis: &[Api], types: &Types)
             }
             Api::RustType(ety) => {
                 expanded.extend(expand_rust_type_impl(ety));
-                hidden.extend(expand_rust_type_layout(ety));
+                hidden.extend(expand_rust_type_layout(ety, types));
             }
             Api::RustFunction(efn) => hidden.extend(expand_rust_function_shim(efn, types)),
             Api::TypeAlias(alias) => {
@@ -186,6 +186,7 @@ fn expand_struct(strct: &Struct) -> TokenStream {
 
 fn expand_struct_operators(strct: &Struct) -> TokenStream {
     let ident = &strct.name.rust;
+    let generics = &strct.generics;
     let mut operators = TokenStream::new();
 
     for derive in &strct.derives {
@@ -198,7 +199,7 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                 operators.extend(quote_spanned! {span=>
                     #[doc(hidden)]
                     #[export_name = #link_name]
-                    extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                    extern "C" fn #local_name #generics(lhs: &#ident #generics, rhs: &#ident #generics) -> bool {
                         let __fn = concat!("<", module_path!(), #prevent_unwind_label);
                         ::cxx::private::prevent_unwind(__fn, || *lhs == *rhs)
                     }
@@ -211,7 +212,7 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                     operators.extend(quote_spanned! {span=>
                         #[doc(hidden)]
                         #[export_name = #link_name]
-                        extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                        extern "C" fn #local_name #generics(lhs: &#ident #generics, rhs: &#ident #generics) -> bool {
                             let __fn = concat!("<", module_path!(), #prevent_unwind_label);
                             ::cxx::private::prevent_unwind(__fn, || *lhs != *rhs)
                         }
@@ -225,7 +226,7 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                 operators.extend(quote_spanned! {span=>
                     #[doc(hidden)]
                     #[export_name = #link_name]
-                    extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                    extern "C" fn #local_name #generics(lhs: &#ident #generics, rhs: &#ident #generics) -> bool {
                         let __fn = concat!("<", module_path!(), #prevent_unwind_label);
                         ::cxx::private::prevent_unwind(__fn, || *lhs < *rhs)
                     }
@@ -237,7 +238,7 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                 operators.extend(quote_spanned! {span=>
                     #[doc(hidden)]
                     #[export_name = #link_name]
-                    extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                    extern "C" fn #local_name #generics(lhs: &#ident #generics, rhs: &#ident #generics) -> bool {
                         let __fn = concat!("<", module_path!(), #prevent_unwind_label);
                         ::cxx::private::prevent_unwind(__fn, || *lhs <= *rhs)
                     }
@@ -250,7 +251,7 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                     operators.extend(quote_spanned! {span=>
                         #[doc(hidden)]
                         #[export_name = #link_name]
-                        extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                        extern "C" fn #local_name #generics(lhs: &#ident #generics, rhs: &#ident #generics) -> bool {
                             let __fn = concat!("<", module_path!(), #prevent_unwind_label);
                             ::cxx::private::prevent_unwind(__fn, || *lhs > *rhs)
                         }
@@ -262,7 +263,7 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                     operators.extend(quote_spanned! {span=>
                         #[doc(hidden)]
                         #[export_name = #link_name]
-                        extern "C" fn #local_name(lhs: &#ident, rhs: &#ident) -> bool {
+                        extern "C" fn #local_name #generics(lhs: &#ident #generics, rhs: &#ident #generics) -> bool {
                             let __fn = concat!("<", module_path!(), #prevent_unwind_label);
                             ::cxx::private::prevent_unwind(__fn, || *lhs >= *rhs)
                         }
@@ -277,7 +278,7 @@ fn expand_struct_operators(strct: &Struct) -> TokenStream {
                     #[doc(hidden)]
                     #[export_name = #link_name]
                     #[allow(clippy::cast_possible_truncation)]
-                    extern "C" fn #local_name(this: &#ident) -> usize {
+                    extern "C" fn #local_name #generics(this: &#ident #generics) -> usize {
                         let __fn = concat!("<", module_path!(), #prevent_unwind_label);
                         ::cxx::private::prevent_unwind(__fn, || ::cxx::private::hash(this))
                     }
@@ -396,10 +397,13 @@ fn expand_cxx_type(ety: &ExternType) -> TokenStream {
     }
 }
 
-fn expand_cxx_type_assert_pinned(ety: &ExternType) -> TokenStream {
+fn expand_cxx_type_assert_pinned(ety: &ExternType, types: &Types) -> TokenStream {
     let ident = &ety.name.rust;
     let infer = Token![_](ident.span());
 
+    let resolve = types.resolve(ident);
+    let lifetimes = resolve.generics.to_underscore_lifetimes();
+
     quote! {
         let _: fn() = {
             // Derived from https://github.com/nvzqz/static-assertions-rs.
@@ -424,7 +428,7 @@ fn expand_cxx_type_assert_pinned(ety: &ExternType) -> TokenStream {
             // `_` can be resolved and this can compile. Fails to compile if
             // user has added a manual Unpin impl for their opaque C++ type as
             // then `__AmbiguousIfImpl<__Invalid>` also exists.
-            <#ident as __AmbiguousIfImpl<#infer>>::infer
+            <#ident #lifetimes as __AmbiguousIfImpl<#infer>>::infer
         };
     }
 }
@@ -833,21 +837,25 @@ fn expand_rust_type_impl(ety: &ExternType) -> TokenStream {
     impls
 }
 
-fn expand_rust_type_assert_unpin(ety: &ExternType) -> TokenStream {
+fn expand_rust_type_assert_unpin(ety: &ExternType, types: &Types) -> TokenStream {
     let ident = &ety.name.rust;
     let begin_span = Token![::](ety.type_token.span);
     let unpin = quote_spanned! {ety.semi_token.span=>
         #begin_span cxx::core::marker::Unpin
     };
+
+    let resolve = types.resolve(ident);
+    let lifetimes = resolve.generics.to_underscore_lifetimes();
+
     quote_spanned! {ident.span()=>
         let _ = {
             fn __AssertUnpin<T: ?::cxx::core::marker::Sized + #unpin>() {}
-            __AssertUnpin::<#ident>
+            __AssertUnpin::<#ident #lifetimes>
         };
     }
 }
 
-fn expand_rust_type_layout(ety: &ExternType) -> TokenStream {
+fn expand_rust_type_layout(ety: &ExternType, types: &Types) -> TokenStream {
     // Rustc will render as follows if not sized:
     //
     //     type TheirType;
@@ -868,6 +876,9 @@ fn expand_rust_type_layout(ety: &ExternType) -> TokenStream {
     let local_sizeof = format_ident!("__sizeof_{}", ety.name.rust);
     let local_alignof = format_ident!("__alignof_{}", ety.name.rust);
 
+    let resolve = types.resolve(ident);
+    let lifetimes = resolve.generics.to_underscore_lifetimes();
+
     quote_spanned! {ident.span()=>
         {
             #[doc(hidden)]
@@ -877,12 +888,12 @@ fn expand_rust_type_layout(ety: &ExternType) -> TokenStream {
             #[doc(hidden)]
             #[export_name = #link_sizeof]
             extern "C" fn #local_sizeof() -> usize {
-                __AssertSized::<#ident>().size()
+                __AssertSized::<#ident #lifetimes>().size()
             }
             #[doc(hidden)]
             #[export_name = #link_alignof]
             extern "C" fn #local_alignof() -> usize {
-                __AssertSized::<#ident>().align()
+                __AssertSized::<#ident #lifetimes>().align()
             }
         }
     }
@@ -1393,7 +1404,7 @@ fn expand_unique_ptr(
     quote_spanned! {end_span=>
         #unsafe_token impl #impl_generics ::cxx::private::UniquePtrTarget for #ident #ty_generics {
             #[doc(hidden)]
-            fn __typename(f: &mut ::cxx::core::fmt::Formatter) -> ::cxx::core::fmt::Result {
+            fn __typename(f: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
                 f.write_str(#name)
             }
             #[doc(hidden)]
@@ -1485,7 +1496,7 @@ fn expand_shared_ptr(
     quote_spanned! {end_span=>
         #unsafe_token impl #impl_generics ::cxx::private::SharedPtrTarget for #ident #ty_generics {
             #[doc(hidden)]
-            fn __typename(f: &mut ::cxx::core::fmt::Formatter) -> ::cxx::core::fmt::Result {
+            fn __typename(f: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
                 f.write_str(#name)
             }
             #[doc(hidden)]
@@ -1545,7 +1556,7 @@ fn expand_weak_ptr(key: NamedImplKey, types: &Types, explicit_impl: Option<&Impl
     quote_spanned! {end_span=>
         #unsafe_token impl #impl_generics ::cxx::private::WeakPtrTarget for #ident #ty_generics {
             #[doc(hidden)]
-            fn __typename(f: &mut ::cxx::core::fmt::Formatter) -> ::cxx::core::fmt::Result {
+            fn __typename(f: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
                 f.write_str(#name)
             }
             #[doc(hidden)]
@@ -1660,7 +1671,7 @@ fn expand_cxx_vector(
     quote_spanned! {end_span=>
         #unsafe_token impl #impl_generics ::cxx::private::VectorElement for #elem #ty_generics {
             #[doc(hidden)]
-            fn __typename(f: &mut ::cxx::core::fmt::Formatter) -> ::cxx::core::fmt::Result {
+            fn __typename(f: &mut ::cxx::core::fmt::Formatter<'_>) -> ::cxx::core::fmt::Result {
                 f.write_str(#name)
             }
             #[doc(hidden)]
diff --git a/macro/src/generics.rs b/macro/src/generics.rs
index 3de0330ba..7862536d0 100644
--- a/macro/src/generics.rs
+++ b/macro/src/generics.rs
@@ -1,9 +1,9 @@
 use crate::syntax::instantiate::NamedImplKey;
 use crate::syntax::resolve::Resolution;
-use crate::syntax::Impl;
+use crate::syntax::{Impl, Lifetimes};
 use proc_macro2::TokenStream;
 use quote::ToTokens;
-use syn::Token;
+use syn::{Lifetime, Token};
 
 pub struct ImplGenerics<'a> {
     explicit_impl: Option<&'a Impl>,
@@ -61,3 +61,31 @@ impl<'a> ToTokens for TyGenerics<'a> {
         }
     }
 }
+
+pub struct UnderscoreLifetimes<'a> {
+    generics: &'a Lifetimes,
+}
+
+impl Lifetimes {
+    pub fn to_underscore_lifetimes(&self) -> UnderscoreLifetimes {
+        UnderscoreLifetimes { generics: self }
+    }
+}
+
+impl<'a> ToTokens for UnderscoreLifetimes<'a> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Lifetimes {
+            lt_token,
+            lifetimes,
+            gt_token,
+        } = self.generics;
+        lt_token.to_tokens(tokens);
+        for pair in lifetimes.pairs() {
+            let (lifetime, punct) = pair.into_tuple();
+            let lifetime = Lifetime::new("'_", lifetime.span());
+            lifetime.to_tokens(tokens);
+            punct.to_tokens(tokens);
+        }
+        gt_token.to_tokens(tokens);
+    }
+}


Test patch:
diff --git a/tests/ui/deny_elided_lifetimes.rs b/tests/ui/deny_elided_lifetimes.rs
new file mode 100644
index 000000000..0ab3f750a
--- /dev/null
+++ b/tests/ui/deny_elided_lifetimes.rs
@@ -0,0 +1,27 @@
+#![deny(elided_lifetimes_in_paths)]
+
+#[cxx::bridge]
+mod ffi {
+    #[derive(PartialEq, PartialOrd, Hash)]
+    struct Struct<'a> {
+        reference: &'a i32,
+    }
+
+    extern "Rust" {
+        type Rust<'a>;
+    }
+
+    unsafe extern "C++" {
+        type Cpp<'a>;
+
+        fn lifetime_named<'a>(s: &'a i32) -> UniquePtr<Cpp<'a>>;
+
+        fn lifetime_underscore(s: &i32) -> UniquePtr<Cpp<'_>>;
+
+        fn lifetime_elided(s: &i32) -> UniquePtr<Cpp>;
+    }
+}
+
+pub struct Rust<'a>(&'a i32);
+
+fn main() {}
diff --git a/tests/ui/deny_elided_lifetimes.stderr b/tests/ui/deny_elided_lifetimes.stderr
new file mode 100644
index 000000000..8b965af36
--- /dev/null
+++ b/tests/ui/deny_elided_lifetimes.stderr
@@ -0,0 +1,15 @@
+error: hidden lifetime parameters in types are deprecated
+  --> tests/ui/deny_elided_lifetimes.rs:21:50
+   |
+21 |         fn lifetime_elided(s: &i32) -> UniquePtr<Cpp>;
+   |                                                  ^^^ expected named lifetime parameter
+   |
+note: the lint level is defined here
+  --> tests/ui/deny_elided_lifetimes.rs:1:9
+   |
+1  | #![deny(elided_lifetimes_in_paths)]
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^
+help: consider using the `'_` lifetime
+   |
+21 |         fn lifetime_elided(s: &i32) -> UniquePtr<Cpp<'_>>;
+   |                                                  ~~~~~~~