Repository: dtolnay/cxx (language: rust)

Issue:
Undeclared lifetime when function pointer argument contains lifetime from extern signature
Repro:

```rust
// src/main.rs

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("example/include/header.h");

        fn call_back<'a>(i: &'a i32, f: fn(&'a i32));
    }
}

fn main() {
    let i = 0i32;
    ffi::call_back(&i, |_| {});
}
```

```cpp
// include/header.h

#pragma once
#include "rust/cxx.h"

inline void call_back(const int32_t &i, rust::Fn<void(const int32_t &)> f) {
  f(i);
}
```

```console
error[E0261]: use of undeclared lifetime name `'a`
 --> src/main.rs:6:45
  |
1 | #[cxx::bridge]
  |               - lifetime `'a` is missing in item created through this procedural macro
...
6 |         fn call_back<'a>(i: &'a i32, f: fn(&'a i32));
  |                                             ^^ undeclared lifetime
  |
  = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
  = help: if you want to experiment with in-band lifetime bindings, add `#![feature(in_band_lifetimes)]` to the crate attributes
help: consider making the type lifetime-generic with a new `'a` lifetime
  |
6 |         fn call_back<'a>(i: &'a i32, f: for<'a> fn(&'a i32));
  |                                         ^^^^^^^
```


Discussion / hints:
The code being generated looks like:

```rust
pub fn call_back<'a>(i: &'a i32, f: fn(arg0: &'a i32)) {
    extern "C" {
        #[link_name = "cxxbridge1$call_back"]
        fn __call_back<'a>(i: &'a i32, f: ::cxx::private::FatFunction);
    }
    let f = ::cxx::private::FatFunction {
        trampoline: {
            extern "C" {
                #[link_name = "cxxbridge1$call_back$f$0"]
                fn trampoline();
            }
            #[doc(hidden)]
            #[export_name = "cxxbridge1$call_back$f$1"]
            unsafe extern "C" fn __(arg0: &'a i32, __extern: *const ()) {
                let __fn = "example::ffi::call_back::f";
                ::cxx::private::catch_unwind(__fn, move || {
                    ::std::mem::transmute::<*const (), fn(arg0: &'a i32)>(__extern)(arg0)
                })
            }
            trampoline as usize as *const ()
        },
        ptr: f as usize as *const (),
    };
    unsafe { __call_back(i, f) }
}
```

where the bug is that `'a` is undeclared on the line starting with `unsafe extern "C" fn`.

Fix patch:
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index f6aeb9493..e2baef52e 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -14,7 +14,7 @@ use crate::{derive, generics};
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote, quote_spanned, ToTokens};
 use std::mem;
-use syn::{parse_quote, punctuated, Lifetime, Result, Token};
+use syn::{parse_quote, punctuated, Generics, Lifetime, Result, Token};
 
 pub fn bridge(mut ffi: Module) -> Result<TokenStream> {
     let ref mut errors = Errors::new();
@@ -760,6 +760,7 @@ fn expand_function_pointer_trampoline(
         local_name,
         catch_unwind_label,
         None,
+        Some(&efn.generics),
         body_span,
     );
     let var = &var.rust;
@@ -901,6 +902,7 @@ fn expand_rust_function_shim(efn: &ExternFn, types: &Types) -> TokenStream {
         local_name,
         catch_unwind_label,
         invoke,
+        None,
         body_span,
     )
 }
@@ -912,9 +914,10 @@ fn expand_rust_function_shim_impl(
     local_name: Ident,
     catch_unwind_label: String,
     invoke: Option<&Ident>,
+    outer_generics: Option<&Generics>,
     body_span: Span,
 ) -> TokenStream {
-    let generics = &sig.generics;
+    let generics = outer_generics.unwrap_or(&sig.generics);
     let receiver_var = sig
         .receiver
         .as_ref()


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index 4b37b20e0..eb889e052 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -158,6 +158,8 @@ pub mod ffi {
         fn c_take_ref_shared_string(s: &SharedString) -> &SharedString;
         fn c_take_callback(callback: fn(String) -> usize);
         fn c_take_callback_ref(callback: fn(&String));
+        #[cxx_name = "c_take_callback_ref"]
+        fn c_take_callback_ref_lifetime<'a>(callback: fn(&'a String));
         fn c_take_callback_mut(callback: fn(&mut String));
         fn c_take_enum(e: Enum);
         fn c_take_ns_enum(e: AEnum);
diff --git a/tests/test.rs b/tests/test.rs
index bb545bfa4..c8204ce05 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -219,6 +219,7 @@ fn test_c_callback() {
 
     check!(ffi::c_take_callback(callback));
     check!(ffi::c_take_callback_ref(callback_ref));
+    check!(ffi::c_take_callback_ref_lifetime(callback_ref));
     check!(ffi::c_take_callback_mut(callback_mut));
 }