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

Separate Error and Display derives #295

Closed
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
4 changes: 4 additions & 0 deletions Cargo.toml
Expand Up @@ -11,6 +11,10 @@ license = "MIT OR Apache-2.0"
repository = "https://github.com/dtolnay/thiserror"
rust-version = "1.56"

[features]
default = ["std"]
std = []

[dependencies]
thiserror-impl = { version = "=1.0.58", path = "impl" }

Expand Down
97 changes: 65 additions & 32 deletions impl/src/expand.rs
Expand Up @@ -7,52 +7,73 @@ 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(input: &DeriveInput) -> TokenStream {
match try_expand(input) {
#[derive(Clone, Copy)]
pub enum DeriveType {
Error,
Display,
}

pub fn derive(input: &DeriveInput, derive_type: DeriveType) -> TokenStream {
match try_expand(input, derive_type) {
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),
Err(error) => fallback(input, error, derive_type),
}
}

fn try_expand(input: &DeriveInput) -> Result<TokenStream> {
fn try_expand(input: &DeriveInput, derive_type: DeriveType) -> 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),
Input::Struct(input) => impl_struct(input, derive_type),
Input::Enum(input) => impl_enum(input, derive_type),
})
}

fn fallback(input: &DeriveInput, error: syn::Error) -> TokenStream {
fn fallback(input: &DeriveInput, error: syn::Error, derive_type: DeriveType) -> 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

let error_impl = quote! {
#[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!()
let display_impl = quote! { #[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!()
}
}};

match derive_type {
DeriveType::Error => quote! {
#error

#error_impl

#display_impl
},
DeriveType::Display => {
quote! {
#error

#display_impl
}
}
}
}

fn impl_struct(input: Struct) -> TokenStream {
fn impl_struct(input: Struct, derive_type: DeriveType) -> TokenStream {
let ty = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut error_inferred_bounds = InferredBounds::new();
Expand Down Expand Up @@ -209,18 +230,24 @@ fn impl_struct(input: Struct) -> TokenStream {
}
let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);

quote! {
#[allow(unused_qualifications)]
impl #impl_generics std::error::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
#display_impl
#from_impl
match derive_type {
DeriveType::Error => quote! {
#[allow(unused_qualifications)]
impl #impl_generics std::error::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
#display_impl
#from_impl
},
DeriveType::Display => quote! {
#display_impl
#from_impl
},
}
}

fn impl_enum(input: Enum) -> TokenStream {
fn impl_enum(input: Enum, derive_type: DeriveType) -> TokenStream {
let ty = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut error_inferred_bounds = InferredBounds::new();
Expand Down Expand Up @@ -465,14 +492,20 @@ fn impl_enum(input: Enum) -> TokenStream {
}
let error_where_clause = error_inferred_bounds.augment_where_clause(input.generics);

quote! {
#[allow(unused_qualifications)]
impl #impl_generics std::error::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
#display_impl
#(#from_impls)*
match derive_type {
DeriveType::Error => quote! {
#[allow(unused_qualifications)]
impl #impl_generics std::error::Error for #ty #ty_generics #error_where_clause {
#source_method
#provide_method
}
#display_impl
#(#from_impls)*
},
DeriveType::Display => quote! {
#display_impl
#(#from_impls)*
},
}
}

Expand Down
8 changes: 7 additions & 1 deletion impl/src/lib.rs
Expand Up @@ -32,5 +32,11 @@ 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).into()
expand::derive(&input, expand::DeriveType::Error).into()
}

#[proc_macro_derive(Display, attributes(backtrace, error, from, source))]
pub fn derive_display(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand::derive(&input, expand::DeriveType::Display).into()
}
5 changes: 4 additions & 1 deletion src/display.rs
@@ -1,4 +1,5 @@
use std::fmt::Display;
use core::fmt::Display;
#[cfg(feature = "std")]
use std::path::{self, Path, PathBuf};

#[doc(hidden)]
Expand All @@ -21,6 +22,7 @@ where
}
}

#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for Path {
type Target = path::Display<'a>;

Expand All @@ -30,6 +32,7 @@ impl<'a> AsDisplay<'a> for Path {
}
}

#[cfg(feature = "std")]
impl<'a> AsDisplay<'a> for PathBuf {
type Target = path::Display<'a>;

Expand Down
10 changes: 8 additions & 2 deletions src/lib.rs
Expand Up @@ -228,6 +228,7 @@
//!
//! [`anyhow`]: https://github.com/dtolnay/anyhow

#![no_std]
#![doc(html_root_url = "https://docs.rs/thiserror/1.0.58")]
#![allow(
clippy::module_name_repetitions,
Expand All @@ -240,21 +241,26 @@
#[cfg(all(thiserror_nightly_testing, not(error_generic_member_access)))]
compile_error!("Build script probe failed to compile.");

#[cfg(feature = "std")]
extern crate std;

#[cfg(feature = "std")]
mod aserror;
mod display;
#[cfg(error_generic_member_access)]
#[cfg(all(error_generic_member_access, feature = "std"))]
mod provide;

pub use thiserror_impl::*;

// Not public API.
#[doc(hidden)]
pub mod __private {
#[cfg(feature = "std")]
#[doc(hidden)]
pub use crate::aserror::AsDynError;
#[doc(hidden)]
pub use crate::display::AsDisplay;
#[cfg(error_generic_member_access)]
#[cfg(all(error_generic_member_access, feature = "std"))]
#[doc(hidden)]
pub use crate::provide::ThiserrorProvide;
}