From db7d30691ca8d0fdad54df708d9570702fff0e64 Mon Sep 17 00:00:00 2001 From: TakaakiFuruse Date: Fri, 15 May 2020 07:23:31 +0900 Subject: [PATCH 1/6] made displaydoc works with thiserror --- Cargo.toml | 2 ++ src/expand.rs | 17 ++++++--- src/input.rs | 41 ++++++++++++++++++++++ src/lib.rs | 3 +- tests/with_thiserror_tests.rs | 66 +++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/input.rs create mode 100644 tests/with_thiserror_tests.rs diff --git a/Cargo.toml b/Cargo.toml index d47c2f5..82d1406 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,3 +31,5 @@ static_assertions = "0.3.4" libc = { version = "0.2", default-features = false } rustversion = "1.0.0" pretty_assertions = "0.6.1" +thiserror = "1.0.17" +anyhow = "1.0.31" diff --git a/src/expand.rs b/src/expand.rs index a49cfa7..51e4dd1 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1,14 +1,21 @@ use crate::attr; +use crate::input::DisplayDocInput; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Result}; pub fn derive(input: &DeriveInput) -> Result { - let impls = match &input.data { - Data::Struct(data) => impl_struct(input, data), - Data::Enum(data) => impl_enum(input, data), - Data::Union(_) => Err(Error::new_spanned(input, "Unions are not supported")), - }?; + let dd_inputs = DisplayDocInput::new(&input); + + let impls = if let Some(true) = dd_inputs.with_thiserror { + quote! {} + } else { + match &input.data { + Data::Struct(data) => impl_struct(input, data), + Data::Enum(data) => impl_enum(input, data), + Data::Union(_) => Err(Error::new_spanned(input, "Unions are not supported")), + }? + }; let helpers = specialization(); let dummy_const = format_ident!("_DERIVE_Display_FOR_{}", input.ident); diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..3096be0 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,41 @@ +use syn::{DeriveInput, Meta, NestedMeta}; + +pub struct DisplayDocInput { + pub with_thiserror: Option, +} + +impl DisplayDocInput { + pub fn new(input: &DeriveInput) -> DisplayDocInput { + let mut with_thiserror = None; + + let meta = input.attrs.iter().find_map(|attr| match attr.parse_meta() { + Ok(m) => { + if m.path().is_ident("displaydoc") { + Some(m) + } else { + None + } + } + Err(e) => panic!("unable to parse attribute: {}", e), + }); + + if let Some(syn::Meta::List(inner)) = meta { + for item in inner.nested { + if let NestedMeta::Meta(Meta::NameValue(ref pair)) = item { + if pair.path.is_ident("with_thiserror") { + if let syn::Lit::Bool(ref s) = pair.lit { + with_thiserror = Some(s.value); + } else { + panic!("with_thiserror arg must be boolean"); + } + } else { + if let Some(ident) = pair.path.get_ident() { + panic!("Attribute {:?} is not supported", ident.to_string()) + }; + } + }; + } + }; + DisplayDocInput { with_thiserror } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4f699c9..c54f6d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,12 @@ extern crate proc_macro; mod attr; mod expand; mod fmt; +mod input; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; -#[proc_macro_derive(Display)] +#[proc_macro_derive(Display, attributes(displaydoc))] pub fn derive_error(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); expand::derive(&input) diff --git a/tests/with_thiserror_tests.rs b/tests/with_thiserror_tests.rs new file mode 100644 index 0000000..dc13b93 --- /dev/null +++ b/tests/with_thiserror_tests.rs @@ -0,0 +1,66 @@ +use anyhow::anyhow; +use displaydoc::Display; +use std::error::Error as _; + +use thiserror::Error; + +fn assert_display(input: T, expected: &'static str) { + let out = format!("{}", input); + assert_eq!(expected, out); +} + +#[test] +fn prioritize_thiserror_transparent_for_enum() { + #[displaydoc(with_thiserror = true)] + #[derive(Display, Error, Debug)] + enum MyError { + /// Doc for Variant. + #[error(transparent)] + Variant(anyhow::Error), + } + + let var = MyError::Variant(anyhow!("inner").context("outer")); + assert_display(&var, "outer"); + assert_eq!(var.source().unwrap().to_string(), "inner") +} + +#[test] +fn prioritize_thiserror_transparent_for_struct() { + #[displaydoc(with_thiserror = true)] + #[derive(Display, Error, Debug)] + #[error(transparent)] + struct MyError { + /// Doc for Variant. + variant: anyhow::Error, + } + + let var = MyError { + variant: anyhow!("inner").context("outer"), + }; + assert_display(&var, "outer"); + assert_eq!(var.source().unwrap().to_string(), "inner") +} + +#[test] +fn prioritize_thiserror_errordoc_for_enum() { + #[displaydoc(with_thiserror = true)] + #[derive(Display, Error, Debug)] + enum MyError { + /// I'm not a doc for Variant + #[error("I'm a doc for Variant")] + Variant, + } + assert_display(MyError::Variant, "I'm a doc for Variant"); +} + +#[test] +fn prioritize_thiserror_errordoc_for_struct() { + #[displaydoc(with_thiserror = true)] + #[derive(Display, Error, Debug)] + #[error("I'm a doc for MyError")] + struct MyError { + /// I'm not a doc for Variant + variant: u8, + } + assert_display(MyError { variant: 42 }, "I'm a doc for MyError"); +} From 6767b518f0afbe66233868b2b840e414790ce265 Mon Sep 17 00:00:00 2001 From: TakaakiFuruse Date: Tue, 19 May 2020 11:29:07 +0900 Subject: [PATCH 2/6] downgraded dependency versions, fixed styles according to clippy suggestion --- Cargo.toml | 4 ++-- src/input.rs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 82d1406..f386fa1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,5 +31,5 @@ static_assertions = "0.3.4" libc = { version = "0.2", default-features = false } rustversion = "1.0.0" pretty_assertions = "0.6.1" -thiserror = "1.0.17" -anyhow = "1.0.31" +thiserror = "1.0" +anyhow = "1.0" diff --git a/src/input.rs b/src/input.rs index 3096be0..f59e5f0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -28,10 +28,8 @@ impl DisplayDocInput { } else { panic!("with_thiserror arg must be boolean"); } - } else { - if let Some(ident) = pair.path.get_ident() { - panic!("Attribute {:?} is not supported", ident.to_string()) - }; + } else if let Some(ident) = pair.path.get_ident() { + panic!("Attribute {:?} is not supported", ident.to_string()) } }; } From 167686fede56dc85c6ecd2d721729b345e6d0423 Mon Sep 17 00:00:00 2001 From: TakaakiFuruse Date: Sun, 24 May 2020 11:35:44 +0900 Subject: [PATCH 3/6] re-wrote tests, applied @yaaahc's code --- Cargo.toml | 5 ++-- src/attr.rs | 47 +++++++++++++++++++++----------- src/expand.rs | 18 ++++--------- src/fmt.rs | 1 - src/input.rs | 39 --------------------------- src/lib.rs | 3 +-- tests/with_thiserror_tests.rs | 50 +++++++++++++++++++---------------- 7 files changed, 67 insertions(+), 96 deletions(-) delete mode 100644 src/input.rs diff --git a/Cargo.toml b/Cargo.toml index f386fa1..515d89f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,5 +31,6 @@ static_assertions = "0.3.4" libc = { version = "0.2", default-features = false } rustversion = "1.0.0" pretty_assertions = "0.6.1" -thiserror = "1.0" -anyhow = "1.0" +thiserror = "1.0.18" +anyhow = "1.0.31" + diff --git a/src/attr.rs b/src/attr.rs index 8c69a76..3a9fd86 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -1,6 +1,9 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use syn::{Attribute, LitStr, Meta, Result}; +use syn::{ + spanned::Spanned, Attribute, Lit::Str, LitStr, Meta, MetaList, MetaNameValue, NestedMeta, + Result, +}; pub struct Display { pub fmt: LitStr, @@ -18,27 +21,39 @@ impl ToTokens for Display { } pub fn display(attrs: &[Attribute]) -> Result> { - for attr in attrs { - if attr.path.is_ident("doc") { - let meta = attr.parse_meta()?; - let lit = match meta { - Meta::NameValue(syn::MetaNameValue { - lit: syn::Lit::Str(lit), - .. - }) => lit, - _ => unimplemented!(), - }; - - let lit = LitStr::new(lit.value().trim(), lit.span()); - + let attr = attrs + .iter() + .find(|attr| attr.path.is_ident("display")) + .or_else(|| attrs.iter().find(|attr| attr.path.is_ident("doc"))); + if let Some(attr) = attr { + let meta = attr.parse_meta()?; + let lit = match meta { + Meta::NameValue(MetaNameValue { lit: Str(lit), .. }) => Some(lit), + Meta::NameValue(_) => unimplemented!("namevalue"), + Meta::Path(_) => unimplemented!("path"), + Meta::List(MetaList { nested, .. }) => { + nested.iter().find_map(|nested_attr| match nested_attr { + NestedMeta::Meta(Meta::Path(path)) => { + if path.is_ident("transparent") { + Some(LitStr::new("{0}", attr.span())) + } else { + unimplemented!() + } + } + NestedMeta::Lit(Str(lit)) => Some(lit.clone()), + _ => unimplemented!(), + }) + } + }; + if let Some(l) = lit { let mut display = Display { - fmt: lit, + fmt: LitStr::new(l.value().trim(), l.span()), args: TokenStream::new(), }; display.expand_shorthand(); return Ok(Some(display)); - } + }; } Ok(None) diff --git a/src/expand.rs b/src/expand.rs index 51e4dd1..1d5f245 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1,21 +1,14 @@ use crate::attr; -use crate::input::DisplayDocInput; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use syn::{Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Result}; pub fn derive(input: &DeriveInput) -> Result { - let dd_inputs = DisplayDocInput::new(&input); - - let impls = if let Some(true) = dd_inputs.with_thiserror { - quote! {} - } else { - match &input.data { - Data::Struct(data) => impl_struct(input, data), - Data::Enum(data) => impl_enum(input, data), - Data::Union(_) => Err(Error::new_spanned(input, "Unions are not supported")), - }? - }; + let impls = match &input.data { + Data::Struct(data) => impl_struct(input, data), + Data::Enum(data) => impl_enum(input, data), + Data::Union(_) => Err(Error::new_spanned(input, "Unions are not supported")), + }?; let helpers = specialization(); let dummy_const = format_ident!("_DERIVE_Display_FOR_{}", input.ident); @@ -67,7 +60,6 @@ fn specialization() -> TokenStream { fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result { let ty = &input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let display = attr::display(&input.attrs)?.map(|display| { let pat = match &data.fields { Fields::Named(fields) => { diff --git a/src/fmt.rs b/src/fmt.rs index 407bf10..7abc2bf 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -20,7 +20,6 @@ impl Display { let mut read = fmt.as_str(); let mut out = String::new(); let mut args = TokenStream::new(); - while let Some(brace) = read.find('{') { out += &read[..=brace]; read = &read[brace + 1..]; diff --git a/src/input.rs b/src/input.rs deleted file mode 100644 index f59e5f0..0000000 --- a/src/input.rs +++ /dev/null @@ -1,39 +0,0 @@ -use syn::{DeriveInput, Meta, NestedMeta}; - -pub struct DisplayDocInput { - pub with_thiserror: Option, -} - -impl DisplayDocInput { - pub fn new(input: &DeriveInput) -> DisplayDocInput { - let mut with_thiserror = None; - - let meta = input.attrs.iter().find_map(|attr| match attr.parse_meta() { - Ok(m) => { - if m.path().is_ident("displaydoc") { - Some(m) - } else { - None - } - } - Err(e) => panic!("unable to parse attribute: {}", e), - }); - - if let Some(syn::Meta::List(inner)) = meta { - for item in inner.nested { - if let NestedMeta::Meta(Meta::NameValue(ref pair)) = item { - if pair.path.is_ident("with_thiserror") { - if let syn::Lit::Bool(ref s) = pair.lit { - with_thiserror = Some(s.value); - } else { - panic!("with_thiserror arg must be boolean"); - } - } else if let Some(ident) = pair.path.get_ident() { - panic!("Attribute {:?} is not supported", ident.to_string()) - } - }; - } - }; - DisplayDocInput { with_thiserror } - } -} diff --git a/src/lib.rs b/src/lib.rs index c54f6d7..eda8989 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,11 @@ extern crate proc_macro; mod attr; mod expand; mod fmt; -mod input; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; -#[proc_macro_derive(Display, attributes(displaydoc))] +#[proc_macro_derive(Display, attributes(display))] pub fn derive_error(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); expand::derive(&input) diff --git a/tests/with_thiserror_tests.rs b/tests/with_thiserror_tests.rs index dc13b93..0a9ab11 100644 --- a/tests/with_thiserror_tests.rs +++ b/tests/with_thiserror_tests.rs @@ -1,8 +1,8 @@ use anyhow::anyhow; use displaydoc::Display; use std::error::Error as _; - use thiserror::Error; +use std::io; fn assert_display(input: T, expected: &'static str) { let out = format!("{}", input); @@ -10,56 +10,60 @@ fn assert_display(input: T, expected: &'static str) { } #[test] -fn prioritize_thiserror_transparent_for_enum() { - #[displaydoc(with_thiserror = true)] +fn test_transparent_for_enum() { #[derive(Display, Error, Debug)] enum MyError { /// Doc for Variant. - #[error(transparent)] + #[display(transparent)] Variant(anyhow::Error), } let var = MyError::Variant(anyhow!("inner").context("outer")); assert_display(&var, "outer"); - assert_eq!(var.source().unwrap().to_string(), "inner") + // assert_eq!(var.source().unwrap().to_string(), "inner") } #[test] -fn prioritize_thiserror_transparent_for_struct() { - #[displaydoc(with_thiserror = true)] +fn test_transparent_for_struct() { #[derive(Display, Error, Debug)] - #[error(transparent)] - struct MyError { - /// Doc for Variant. - variant: anyhow::Error, + #[display(transparent)] + struct Error(ErrorKind); + + #[derive(Display, Error, Debug)] + enum ErrorKind { + #[display("E0")] + E0, + #[display("E1")] + E1(#[from] io::Error), } - let var = MyError { - variant: anyhow!("inner").context("outer"), - }; - assert_display(&var, "outer"); - assert_eq!(var.source().unwrap().to_string(), "inner") + let error = Error(ErrorKind::E0); + assert_eq!("E0", error.to_string()); + assert!(error.source().is_none()); + + let io = io::Error::new(io::ErrorKind::Other, "oh no!"); + let error = Error(ErrorKind::from(io)); + assert_eq!("E1", error.to_string()); + // error.source().unwrap().downcast_ref::().unwrap(); } #[test] -fn prioritize_thiserror_errordoc_for_enum() { - #[displaydoc(with_thiserror = true)] +fn test_errordoc_for_enum() { #[derive(Display, Error, Debug)] enum MyError { /// I'm not a doc for Variant - #[error("I'm a doc for Variant")] + #[display("I'm a doc for Variant")] Variant, } assert_display(MyError::Variant, "I'm a doc for Variant"); } #[test] -fn prioritize_thiserror_errordoc_for_struct() { - #[displaydoc(with_thiserror = true)] +fn test_errordoc_for_struct() { #[derive(Display, Error, Debug)] - #[error("I'm a doc for MyError")] + #[display("I'm a doc for MyError")] struct MyError { - /// I'm not a doc for Variant + /// I'm not a doc for MyError variant: u8, } assert_display(MyError { variant: 42 }, "I'm a doc for MyError"); From fcea5e5588e728f50fab166bbd1e66ddf4d1864a Mon Sep 17 00:00:00 2001 From: TakaakiFuruse Date: Sun, 24 May 2020 20:46:40 +0900 Subject: [PATCH 4/6] format fix --- tests/with_thiserror_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/with_thiserror_tests.rs b/tests/with_thiserror_tests.rs index 0a9ab11..12d772d 100644 --- a/tests/with_thiserror_tests.rs +++ b/tests/with_thiserror_tests.rs @@ -1,8 +1,8 @@ use anyhow::anyhow; use displaydoc::Display; use std::error::Error as _; -use thiserror::Error; use std::io; +use thiserror::Error; fn assert_display(input: T, expected: &'static str) { let out = format!("{}", input); From a3aa1409b24798f8444569ce5a8b96a7749d9439 Mon Sep 17 00:00:00 2001 From: TakaakiFuruse Date: Sun, 31 May 2020 12:05:19 +0900 Subject: [PATCH 5/6] added test cases for #[source] attribute --- tests/with_thiserror_tests.rs | 36 +++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/tests/with_thiserror_tests.rs b/tests/with_thiserror_tests.rs index 12d772d..4cbcc58 100644 --- a/tests/with_thiserror_tests.rs +++ b/tests/with_thiserror_tests.rs @@ -1,6 +1,6 @@ use anyhow::anyhow; use displaydoc::Display; -use std::error::Error as _; +use std::error::Error as StdError; use std::io; use thiserror::Error; @@ -20,13 +20,13 @@ fn test_transparent_for_enum() { let var = MyError::Variant(anyhow!("inner").context("outer")); assert_display(&var, "outer"); - // assert_eq!(var.source().unwrap().to_string(), "inner") + assert_eq!(var.source().unwrap().to_string(), "inner") } #[test] fn test_transparent_for_struct() { #[derive(Display, Error, Debug)] - #[display(transparent)] + #[error(transparent)] struct Error(ErrorKind); #[derive(Display, Error, Debug)] @@ -44,7 +44,7 @@ fn test_transparent_for_struct() { let io = io::Error::new(io::ErrorKind::Other, "oh no!"); let error = Error(ErrorKind::from(io)); assert_eq!("E1", error.to_string()); - // error.source().unwrap().downcast_ref::().unwrap(); + error.source().unwrap().downcast_ref::().unwrap(); } #[test] @@ -68,3 +68,31 @@ fn test_errordoc_for_struct() { } assert_display(MyError { variant: 42 }, "I'm a doc for MyError"); } + +#[test] +fn test_thiserror_implicit_and_source_works() { + #[derive(Display, Error, Debug)] + #[error("implicit source")] + struct ImplicitSource { + source: io::Error, + } + + #[derive(Display, Error, Debug)] + #[error("explicit source")] + struct ExplicitSource { + source: String, + #[source] + io: io::Error, + } + + let io = io::Error::new(io::ErrorKind::Other, "oh no!"); + let error = ImplicitSource { source: io }; + error.source().unwrap().downcast_ref::().unwrap(); + + let io = io::Error::new(io::ErrorKind::Other, "oh no!"); + let error = ExplicitSource { + source: String::new(), + io, + }; + error.source().unwrap().downcast_ref::().unwrap(); +} From 3c90a1ba20506a1825d44a3b34a34886772dbc2a Mon Sep 17 00:00:00 2001 From: TakaakiFuruse Date: Thu, 4 Jun 2020 12:57:56 +0900 Subject: [PATCH 6/6] [WIP] --- Cargo.toml | 6 ++--- src/attr.rs | 19 +++++++------- src/expand.rs | 1 - src/fmt.rs | 1 - src/lib.rs | 2 +- tests/with_thiserror_tests.rs | 48 ++++++++++++++++++----------------- 6 files changed, 38 insertions(+), 39 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 515d89f..3f6556a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,9 +21,10 @@ default = ["std"] std = [] [dependencies] -syn = "1.0" +syn = { version = "1.0", features = ["extra-traits"] } quote = "1.0" proc-macro2 = "1.0" +cargo-expand = "0.6.0" [dev-dependencies] trybuild = "1.0" @@ -32,5 +33,4 @@ libc = { version = "0.2", default-features = false } rustversion = "1.0.0" pretty_assertions = "0.6.1" thiserror = "1.0.18" -anyhow = "1.0.31" - +anyhow = "1.0.31" \ No newline at end of file diff --git a/src/attr.rs b/src/attr.rs index 3a9fd86..84ccf4f 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -1,8 +1,8 @@ use proc_macro2::TokenStream; use quote::{quote, ToTokens}; use syn::{ - spanned::Spanned, Attribute, Lit::Str, LitStr, Meta, MetaList, MetaNameValue, NestedMeta, - Result, + spanned::Spanned, Attribute, Error, Lit::Str, LitStr, Meta, MetaList, MetaNameValue, + NestedMeta, Result, }; pub struct Display { @@ -28,24 +28,23 @@ pub fn display(attrs: &[Attribute]) -> Result> { if let Some(attr) = attr { let meta = attr.parse_meta()?; let lit = match meta { - Meta::NameValue(MetaNameValue { lit: Str(lit), .. }) => Some(lit), - Meta::NameValue(_) => unimplemented!("namevalue"), - Meta::Path(_) => unimplemented!("path"), + Meta::NameValue(MetaNameValue { lit: Str(lit), .. }) => Some(Ok(lit)), Meta::List(MetaList { nested, .. }) => { nested.iter().find_map(|nested_attr| match nested_attr { NestedMeta::Meta(Meta::Path(path)) => { if path.is_ident("transparent") { - Some(LitStr::new("{0}", attr.span())) + Some(Ok(LitStr::new("{0}", attr.span()))) } else { - unimplemented!() + Some(Err(Error::new_spanned(attr, "attr error"))) } } - NestedMeta::Lit(Str(lit)) => Some(lit.clone()), - _ => unimplemented!(), + NestedMeta::Lit(Str(lit)) => Some(Ok(lit.clone())), + _ => Some(Err(Error::new_spanned(attr, "cant accept the type"))), }) } + _ => Some(Err(Error::new_spanned(attr, "namevalue or meta"))), }; - if let Some(l) = lit { + if let Some(Ok(l)) = lit { let mut display = Display { fmt: LitStr::new(l.value().trim(), l.span()), args: TokenStream::new(), diff --git a/src/expand.rs b/src/expand.rs index 1d5f245..72bdea0 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -95,7 +95,6 @@ fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result { .iter() .map(|variant| attr::display(&variant.attrs)) .collect::>>()?; - if displays.iter().any(Option::is_some) { let arms = data .variants diff --git a/src/fmt.rs b/src/fmt.rs index 7abc2bf..00ac52c 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -42,7 +42,6 @@ impl Display { let ident = Ident::new(&var, span); let next = peek_next!(read); - let arg = if cfg!(feature = "std") && next == '}' { quote_spanned!(span=> , #ident.__displaydoc_display()) } else { diff --git a/src/lib.rs b/src/lib.rs index eda8989..027cb37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ mod fmt; use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; -#[proc_macro_derive(Display, attributes(display))] +#[proc_macro_derive(Display, attributes(display, source))] pub fn derive_error(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); expand::derive(&input) diff --git a/tests/with_thiserror_tests.rs b/tests/with_thiserror_tests.rs index 4cbcc58..9db547f 100644 --- a/tests/with_thiserror_tests.rs +++ b/tests/with_thiserror_tests.rs @@ -9,12 +9,13 @@ fn assert_display(input: T, expected: &'static str) { assert_eq!(expected, out); } +#[ignore] #[test] fn test_transparent_for_enum() { #[derive(Display, Error, Debug)] enum MyError { - /// Doc for Variant. #[display(transparent)] + /// Doc for Variant. Variant(anyhow::Error), } @@ -70,29 +71,30 @@ fn test_errordoc_for_struct() { } #[test] -fn test_thiserror_implicit_and_source_works() { - #[derive(Display, Error, Debug)] - #[error("implicit source")] - struct ImplicitSource { - source: io::Error, - } - - #[derive(Display, Error, Debug)] - #[error("explicit source")] - struct ExplicitSource { - source: String, - #[source] - io: io::Error, +fn test_thiserror_implicit_source_works() { + #[derive(Debug, Display, Error)] + enum SourceError { + #[display(transparent)] + ImplicitSource { source: anyhow::Error }, + #[display(transparent)] + ExplicitSource { + source: String, + #[source] + io: anyhow::Error, + }, + /// There isn't really a {source} + DocSourceless { source: String }, } - let io = io::Error::new(io::ErrorKind::Other, "oh no!"); - let error = ImplicitSource { source: io }; - error.source().unwrap().downcast_ref::().unwrap(); - - let io = io::Error::new(io::ErrorKind::Other, "oh no!"); - let error = ExplicitSource { - source: String::new(), - io, + let implicit_source = SourceError::ImplicitSource { + source: anyhow!("inner").context("outer"), }; - error.source().unwrap().downcast_ref::().unwrap(); + let explicit_source = SourceError::ExplicitSource { + source: "Error!!".to_string(), + io: anyhow!("inner").context("outer"), + }; + let docsource_less = SourceError::DocSourceless { source: "ERROR!!".to_string()}; + assert_display(&implicit_source, "outer"); + // assert_display(&explicit_source, "outer"); + // assert_display(&docsource_less, "ERROR!!"); }