diff --git a/impl/src/expand.rs b/impl/src/expand.rs index 2cf0967..4ce2c0b 100644 --- a/impl/src/expand.rs +++ b/impl/src/expand.rs @@ -7,8 +7,18 @@ use quote::{format_ident, quote, quote_spanned, ToTokens}; use std::collections::BTreeSet as Set; use syn::{DeriveInput, GenericArgument, Member, PathArguments, Result, Token, Type}; -pub fn derive(node: &DeriveInput) -> Result { - let input = Input::from_syn(node)?; +pub fn derive(input: &DeriveInput) -> TokenStream { + match try_expand(input) { + Ok(expanded) => expanded, + // If there are invalid attributes in the input, expand to an Error impl + // anyway to minimize spurious knock-on errors in other code that uses + // this type as an Error. + Err(error) => fallback(input, error), + } +} + +fn try_expand(input: &DeriveInput) -> Result { + let input = Input::from_syn(input)?; input.validate()?; Ok(match input { Input::Struct(input) => impl_struct(input), @@ -16,6 +26,32 @@ pub fn derive(node: &DeriveInput) -> Result { }) } +fn fallback(input: &DeriveInput, error: syn::Error) -> TokenStream { + let ty = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let error = error.to_compile_error(); + + quote! { + #error + + #[allow(unused_qualifications)] + impl #impl_generics std::error::Error for #ty #ty_generics #where_clause + where + // Work around trivial bounds being unstable. + // https://github.com/rust-lang/rust/issues/48214 + for<'workaround> #ty #ty_generics: ::core::fmt::Debug, + {} + + #[allow(unused_qualifications)] + impl #impl_generics ::core::fmt::Display for #ty #ty_generics #where_clause { + fn fmt(&self, __formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + ::core::unreachable!() + } + } + } +} + fn impl_struct(input: Struct) -> TokenStream { let ty = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 3995178..b6471ff 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -33,7 +33,5 @@ use syn::{parse_macro_input, DeriveInput}; #[proc_macro_derive(Error, attributes(backtrace, error, from, source))] pub fn derive_error(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); - expand::derive(&input) - .unwrap_or_else(|err| err.to_compile_error()) - .into() + expand::derive(&input).into() } diff --git a/tests/ui/invalid-input-impl-anyway.rs b/tests/ui/invalid-input-impl-anyway.rs index f9a10d8..0a0bcbe 100644 --- a/tests/ui/invalid-input-impl-anyway.rs +++ b/tests/ui/invalid-input-impl-anyway.rs @@ -5,7 +5,7 @@ use thiserror::Error; pub struct MyError; fn main() { - // FIXME: there should be no error on the following line. Thiserror should - // emit an Error impl regardless of the bad attribute. + // No error on the following line. Thiserror emits an Error impl despite the + // bad attribute. _ = &MyError as &dyn std::error::Error; } diff --git a/tests/ui/invalid-input-impl-anyway.stderr b/tests/ui/invalid-input-impl-anyway.stderr index 297b100..b98c31e 100644 --- a/tests/ui/invalid-input-impl-anyway.stderr +++ b/tests/ui/invalid-input-impl-anyway.stderr @@ -3,11 +3,3 @@ error: expected attribute arguments in parentheses: #[error(...)] | 4 | #[error] | ^^^^^ - -error[E0277]: the trait bound `MyError: std::error::Error` is not satisfied - --> tests/ui/invalid-input-impl-anyway.rs:10:9 - | -10 | _ = &MyError as &dyn std::error::Error; - | ^^^^^^^^ the trait `std::error::Error` is not implemented for `MyError` - | - = note: required for the cast from `&MyError` to `&dyn std::error::Error`