Repository: dtolnay/cxx (language: rust)

Issue:
Diagnostic for invalid use of 'static as a lifetime parameter name
As observed in https://github.com/dtolnay/cxx/pull/779#issuecomment-815027747 -- `'static` is not supposed to be a name that you can give to a lifetime parameter. Rustc catches this anyway later in the compilation as a reserved lifetime name, but for our C++ code generator prior to rustc getting invoked, it would be good to add a dedicated diagnostic too catch this there too.

```rust
use std::pin::Pin;

struct Logger {}

fn logger<'static>() -> Pin<&'static mut Logger> {
    unimplemented!()
}
```

```console
error[E0262]: invalid lifetime parameter name: `'static`
 --> src/main.rs:5:11
  |
5 | fn logger<'static>() -> Pin<&'static mut Logger> {
  |           ^^^^^^^ 'static is a reserved lifetime name
```


Discussion / hints:

Fix patch:
diff --git a/gen/src/check.rs b/gen/src/check.rs
index 35929ad34..15add20aa 100644
--- a/gen/src/check.rs
+++ b/gen/src/check.rs
@@ -4,7 +4,7 @@ use crate::syntax::{error, Api};
 use quote::{quote, quote_spanned};
 use std::path::{Component, Path};
 
-pub(super) use crate::syntax::check::typecheck;
+pub(super) use crate::syntax::check::{typecheck, Generator};
 
 pub(super) fn precheck(cx: &mut Errors, apis: &[Api], opt: &Opt) {
     if !opt.allow_dot_includes {
diff --git a/gen/src/mod.rs b/gen/src/mod.rs
index 3d12c71d2..d8b90d0d9 100644
--- a/gen/src/mod.rs
+++ b/gen/src/mod.rs
@@ -130,7 +130,8 @@ pub(super) fn generate(syntax: File, opt: &Opt) -> Result<GeneratedCode> {
     let ref types = Types::collect(errors, apis);
     check::precheck(errors, apis, opt);
     errors.propagate()?;
-    check::typecheck(errors, apis, types);
+    let generator = check::Generator::Build;
+    check::typecheck(errors, apis, types, generator);
     errors.propagate()?;
 
     // Some callers may wish to generate both header and implementation from the
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 12e4fe08a..1acf9afb2 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -35,7 +35,8 @@ pub fn bridge(mut ffi: Module) -> Result<TokenStream> {
     let ref apis = syntax::parse_items(errors, content, trusted, namespace);
     let ref types = Types::collect(errors, apis);
     errors.propagate()?;
-    check::typecheck(errors, apis, types);
+    let generator = check::Generator::Macro;
+    check::typecheck(errors, apis, types, generator);
     errors.propagate()?;
 
     Ok(expand(ffi, doc, attrs, apis, types))
diff --git a/syntax/check.rs b/syntax/check.rs
index bab2dec5a..124aaa975 100644
--- a/syntax/check.rs
+++ b/syntax/check.rs
@@ -2,24 +2,40 @@ use crate::syntax::atom::Atom::{self, *};
 use crate::syntax::report::Errors;
 use crate::syntax::visit::{self, Visit};
 use crate::syntax::{
-    error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, NamedType, Ptr,
-    Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types,
+    error, ident, trivial, Api, Array, Enum, ExternFn, ExternType, Impl, Lang, Lifetimes,
+    NamedType, Ptr, Receiver, Ref, Signature, SliceRef, Struct, Trait, Ty1, Type, TypeAlias, Types,
 };
 use proc_macro2::{Delimiter, Group, Ident, TokenStream};
 use quote::{quote, ToTokens};
 use std::fmt::Display;
+use syn::{GenericParam, Generics, Lifetime};
 
 pub(crate) struct Check<'a> {
     apis: &'a [Api],
     types: &'a Types<'a>,
     errors: &'a mut Errors,
+    generator: Generator,
 }
 
-pub(crate) fn typecheck(cx: &mut Errors, apis: &[Api], types: &Types) {
+pub(crate) enum Generator {
+    // cxx-build crate, cxxbridge cli, cxx-gen.
+    #[allow(dead_code)]
+    Build,
+    // cxxbridge-macro. This is relevant in that the macro output is going to
+    // get fed straight to rustc, so for errors that rustc already contains
+    // logic to catch (probably with a better diagnostic than what the proc
+    // macro API is able to produce), we avoid duplicating them in our own
+    // diagnostics.
+    #[allow(dead_code)]
+    Macro,
+}
+
+pub(crate) fn typecheck(cx: &mut Errors, apis: &[Api], types: &Types, generator: Generator) {
     do_typecheck(&mut Check {
         apis,
         types,
         errors: cx,
+        generator,
     });
 }
 
@@ -294,6 +310,7 @@ fn check_type_fn(cx: &mut Check, ty: &Signature) {
 fn check_api_struct(cx: &mut Check, strct: &Struct) {
     let name = &strct.name;
     check_reserved_name(cx, &name.rust);
+    check_lifetimes(cx, &strct.generics);
 
     if strct.fields.is_empty() {
         let span = span_for_struct_error(strct);
@@ -330,6 +347,7 @@ fn check_api_struct(cx: &mut Check, strct: &Struct) {
 
 fn check_api_enum(cx: &mut Check, enm: &Enum) {
     check_reserved_name(cx, &enm.name.rust);
+    check_lifetimes(cx, &enm.generics);
 
     if enm.variants.is_empty() && !enm.explicit_repr {
         let span = span_for_enum_error(enm);
@@ -349,6 +367,7 @@ fn check_api_enum(cx: &mut Check, enm: &Enum) {
 
 fn check_api_type(cx: &mut Check, ety: &ExternType) {
     check_reserved_name(cx, &ety.name.rust);
+    check_lifetimes(cx, &ety.generics);
 
     for derive in &ety.derives {
         if derive.what == Trait::ExternType && ety.lang == Lang::Rust {
@@ -400,6 +419,8 @@ fn check_api_fn(cx: &mut Check, efn: &ExternFn) {
         }
     }
 
+    check_generics(cx, &efn.sig.generics);
+
     if let Some(receiver) = &efn.receiver {
         let ref span = span_for_receiver_error(receiver);
 
@@ -472,6 +493,8 @@ fn check_api_fn(cx: &mut Check, efn: &ExternFn) {
 }
 
 fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) {
+    check_lifetimes(cx, &alias.generics);
+
     for derive in &alias.derives {
         let msg = format!("derive({}) on extern type alias is not supported", derive);
         cx.error(derive, msg);
@@ -481,6 +504,8 @@ fn check_api_type_alias(cx: &mut Check, alias: &TypeAlias) {
 fn check_api_impl(cx: &mut Check, imp: &Impl) {
     let ty = &imp.ty;
 
+    check_lifetimes(cx, &imp.impl_generics);
+
     if let Some(negative) = imp.negative_token {
         let span = quote!(#negative #ty);
         cx.error(span, "negative impl is not supported yet");
@@ -583,6 +608,31 @@ fn check_reserved_name(cx: &mut Check, ident: &Ident) {
     }
 }
 
+fn check_reserved_lifetime(cx: &mut Check, lifetime: &Lifetime) {
+    if lifetime.ident == "static" {
+        match cx.generator {
+            Generator::Macro => { /* rustc already reports this */ }
+            Generator::Build => {
+                cx.error(lifetime, error::RESERVED_LIFETIME);
+            }
+        }
+    }
+}
+
+fn check_lifetimes(cx: &mut Check, generics: &Lifetimes) {
+    for lifetime in &generics.lifetimes {
+        check_reserved_lifetime(cx, lifetime);
+    }
+}
+
+fn check_generics(cx: &mut Check, generics: &Generics) {
+    for generic_param in &generics.params {
+        if let GenericParam::Lifetime(def) = generic_param {
+            check_reserved_lifetime(cx, &def.lifetime);
+        }
+    }
+}
+
 fn is_unsized(cx: &mut Check, ty: &Type) -> bool {
     match ty {
         Type::Ident(ident) => {
diff --git a/syntax/error.rs b/syntax/error.rs
index a67232937..f40c4a8e9 100644
--- a/syntax/error.rs
+++ b/syntax/error.rs
@@ -21,6 +21,7 @@ pub static ERRORS: &[Error] = &[
     DISCRIMINANT_OVERFLOW,
     DOT_INCLUDE,
     DOUBLE_UNDERSCORE,
+    RESERVED_LIFETIME,
     RUST_TYPE_BY_VALUE,
     UNSUPPORTED_TYPE,
     USE_NOT_ALLOWED,
@@ -68,6 +69,12 @@ pub static DOUBLE_UNDERSCORE: Error = Error {
     note: Some("identifiers containing double underscore are reserved in C++"),
 };
 
+pub static RESERVED_LIFETIME: Error = Error {
+    msg: "invalid lifetime parameter name: `'static`",
+    label: Some("'static is a reserved lifetime name"),
+    note: None,
+};
+
 pub static RUST_TYPE_BY_VALUE: Error = Error {
     msg: "opaque Rust type by value is not supported",
     label: None,


Test patch:
diff --git a/tests/ui/reserved_lifetime.rs b/tests/ui/reserved_lifetime.rs
new file mode 100644
index 000000000..179a4db54
--- /dev/null
+++ b/tests/ui/reserved_lifetime.rs
@@ -0,0 +1,10 @@
+#[cxx::bridge]
+mod ffi {
+    unsafe extern "C++" {
+        type Logger;
+
+        fn logger<'static>() -> Pin<&'static Logger>;
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/reserved_lifetime.stderr b/tests/ui/reserved_lifetime.stderr
new file mode 100644
index 000000000..723572d93
--- /dev/null
+++ b/tests/ui/reserved_lifetime.stderr
@@ -0,0 +1,5 @@
+error[E0262]: invalid lifetime parameter name: `'static`
+ --> $DIR/reserved_lifetime.rs:6:19
+  |
+6 |         fn logger<'static>() -> Pin<&'static Logger>;
+  |                   ^^^^^^^ 'static is a reserved lifetime name