Repository: dtolnay/thiserror (rust)

Issue:
`allow`s are not propagated to auto-generated `impl`s
# Problem statement

Built-in derive macros (e.g., `Debug`) behave differently than `thiserror::Error` wrt propagation of `#[allow(...)]` attributes.


### Built-in macros

`allow` attributes are propagated to auto-generated `impl`s.

### `thiserror::Error`

`allow` attributes are **not** propagated to auto-generated `impl`s.

### Example

```rust
#[deprecated]
#[allow(deprecated, never_type_fallback_flowing_into_unsafe)]
#[derive(Debug, Error)]
#[error("B")]
pub struct B;
```

gets expanded into (formatted for readability):

```rust
#[deprecated]
#[allow(deprecated, never_type_fallback_flowing_into_unsafe)]
#[error("B")]
pub struct B;

#[automatically_derived]
#[allow(deprecated, never_type_fallback_flowing_into_unsafe)]
impl ::core::fmt::Debug for B {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "B")
    }
}

#[allow(unused_qualifications)]
#[automatically_derived]
impl ::thiserror::__private::Error for B {}

#[allow(unused_qualifications)]
#[automatically_derived]
impl ::core::fmt::Display for B {
    #[allow(clippy::used_underscore_binding)]
    fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        #[allow(unused_variables, deprecated)]
        let Self {} = self;
        __formatter.write_str("B")
    }
}
```

# Proposed solution

Do propagate `allow`s to `impl`s generated by `thiserror::Error`.


Pull request:
Prevent deprecation warning on generated impl for deprecated type
Closes #392.

Patch:
diff --git a/impl/src/expand.rs b/impl/src/expand.rs
index 23e77b6..52782e3 100644
--- a/impl/src/expand.rs
+++ b/impl/src/expand.rs
@@ -2,7 +2,7 @@ use crate::ast::{Enum, Field, Input, Struct};
 use crate::attr::Trait;
 use crate::generics::InferredBounds;
 use crate::unraw::MemberUnraw;
-use proc_macro2::{Ident, TokenStream};
+use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote, quote_spanned, ToTokens};
 use std::collections::BTreeSet as Set;
 use syn::{DeriveInput, GenericArgument, PathArguments, Result, Token, Type};
@@ -27,7 +27,7 @@ fn try_expand(input: &DeriveInput) -> Result<TokenStream> {
 }
 
 fn fallback(input: &DeriveInput, error: syn::Error) -> TokenStream {
-    let ty = &input.ident;
+    let ty = call_site_ident(&input.ident);
     let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
 
     let error = error.to_compile_error();
@@ -55,7 +55,7 @@ fn fallback(input: &DeriveInput, error: syn::Error) -> TokenStream {
 }
 
 fn impl_struct(input: Struct) -> TokenStream {
-    let ty = &input.ident;
+    let ty = call_site_ident(&input.ident);
     let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
     let mut error_inferred_bounds = InferredBounds::new();
 
@@ -228,7 +228,7 @@ fn impl_struct(input: Struct) -> TokenStream {
 }
 
 fn impl_enum(input: Enum) -> TokenStream {
-    let ty = &input.ident;
+    let ty = call_site_ident(&input.ident);
     let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
     let mut error_inferred_bounds = InferredBounds::new();
 
@@ -492,6 +492,14 @@ fn impl_enum(input: Enum) -> TokenStream {
     }
 }
 
+// Create an ident with which we can expand `impl Trait for #ident {}` on a
+// deprecated type without triggering deprecation warning on the generated impl.
+fn call_site_ident(ident: &Ident) -> Ident {
+    let mut ident = ident.clone();
+    ident.set_span(ident.span().resolved_at(Span::call_site()));
+    ident
+}
+
 fn fields_pat(fields: &[Field]) -> TokenStream {
     let mut members = fields.iter().map(|field| &field.member).peekable();
     match members.peek() {


Test patch:
diff --git a/tests/test_lints.rs b/tests/test_lints.rs
index 6c39fba..b46a391 100644
--- a/tests/test_lints.rs
+++ b/tests/test_lints.rs
@@ -39,12 +39,37 @@ fn test_deprecated() {
     #![deny(deprecated)]
 
     #[derive(Error, Debug)]
-    pub enum MyError {
+    #[deprecated]
+    #[error("...")]
+    pub struct DeprecatedStruct;
+
+    #[derive(Error, Debug)]
+    #[error("{message} {}", .message)]
+    pub struct DeprecatedStructField {
+        #[deprecated]
+        message: String,
+    }
+
+    #[derive(Error, Debug)]
+    #[deprecated]
+    pub enum DeprecatedEnum {
+        #[error("...")]
+        Variant,
+    }
+
+    #[derive(Error, Debug)]
+    pub enum DeprecatedVariant {
         #[deprecated]
         #[error("...")]
-        Deprecated,
+        Variant,
     }
 
     #[allow(deprecated)]
-    let _ = MyError::Deprecated;
+    let _: DeprecatedStruct;
+    #[allow(deprecated)]
+    let _: DeprecatedStructField;
+    #[allow(deprecated)]
+    let _ = DeprecatedEnum::Variant;
+    #[allow(deprecated)]
+    let _ = DeprecatedVariant::Variant;
 }
diff --git a/tests/ui/missing-display.stderr b/tests/ui/missing-display.stderr
index 48c9ded..f7a044b 100644
--- a/tests/ui/missing-display.stderr
+++ b/tests/ui/missing-display.stderr
@@ -1,6 +1,8 @@
 error[E0277]: `MyError` doesn't implement `std::fmt::Display`
  --> tests/ui/missing-display.rs:4:10
   |
+3 | #[derive(Error, Debug)]
+  |          ----- in this derive macro expansion
 4 | pub enum MyError {
   |          ^^^^^^^ `MyError` cannot be formatted with the default formatter
   |
@@ -11,3 +13,4 @@ note: required by a bound in `std::error::Error`
   |
   | pub trait Error: Debug + Display {
   |                          ^^^^^^^ required by this bound in `Error`
+  = note: this error originates in the derive macro `Error` (in Nightly builds, run with -Z macro-backtrace for more info)