Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit an Error impl even in the presence of bad attributes #266

Merged
merged 4 commits into from Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 38 additions & 2 deletions impl/src/expand.rs
Expand Up @@ -7,15 +7,51 @@ 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<TokenStream> {
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<TokenStream> {
let input = Input::from_syn(input)?;
input.validate()?;
Ok(match input {
Input::Struct(input) => impl_struct(input),
Input::Enum(input) => impl_enum(input),
})
}

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();
Expand Down
4 changes: 1 addition & 3 deletions impl/src/lib.rs
Expand Up @@ -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()
}
4 changes: 2 additions & 2 deletions tests/ui/invalid-input-impl-anyway.rs
Expand Up @@ -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;
}
8 changes: 0 additions & 8 deletions tests/ui/invalid-input-impl-anyway.stderr
Expand Up @@ -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`