-
Notifications
You must be signed in to change notification settings - Fork 19
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
Added support for thiserror
#18
Conversation
I have no idea why this test is failing... |
I think I made a mistake marking this issue as easy. It looks like the plan I had isn't actually possible and needs to be adjusted. I had intended for you to just modify I think the right approach is to instead support attributes that are like thiserrors, while not actually accepting |
Thanks for uploading your tree, I will take a look at it and let me finish PR. |
I have applied your code refactored, and also re-wrote some tests. Mostly it's working but Here #[test]
fn test_transparent_for_enum() {
#[derive(Display, Error, Debug)]
enum MyError {
/// Doc for Variant.
#[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") // <====== HERE
} and here #[test]
fn test_transparent_for_struct() {
#[derive(Display, Error, Debug)]
#[display(transparent)]
struct Error(ErrorKind);
#[derive(Display, Error, Debug)]
enum ErrorKind {
#[display("E0")]
E0,
#[display("E1")]
E1(#[from] io::Error),
}
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::<io::Error>().unwrap(); // <====== HERE
} are the parts I couldn't implement. |
sorry for taking so long to review this, I just got a new job this week so its been a little hectic. I'm reviewing this now. |
Wow!! Congrats!! |
grr, it looks like the anyhow dev dependency breaks tests on 1.31.0, thats annoying |
src/attr.rs
Outdated
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())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is only partially complete. This covers the case where an error is implemented as a tuple, but thiserror also supports implying the source via a field named source on struct variants, and it supports manually marking a field as the source, so we need to add test cases for the following struct definitions:
struct Error {
source: OtherError,
}
struct Error2 {
#[source]
cause: OtherError,
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added tests to see the case you mentioned is working.
It's working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! I left a few comments, the main issue will be figuring out the source field and getting its name so we can generate the correct display impl when doing #[display(transparent)]
. Ultimately though I'm not sure how useful display transparent will be, given that it still wont impl error transparently, maybe theres a way to get thiserror to do transparent without implementing display...
src/attr.rs
Outdated
let meta = attr.parse_meta()?; | ||
let lit = match meta { | ||
Meta::NameValue(MetaNameValue { lit: Str(lit), .. }) => Some(lit), | ||
Meta::NameValue(_) => unimplemented!("namevalue"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all of these unimplementeds should probably be removed and replaced with a catch all match arm that reports that the attribute was invalid with compile_error!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't came up any good idea.
According to the rust doc the compile_error!
is supposed to use inside of macro. (I have never used this macro, so my interpretation might be wrong.)
If you just do
let lit = match meta {
Meta::NameValue(MetaNameValue { lit: Str(lit), .. }) => Some(lit),
Meta::NameValue(_) => complie_error!("ERROR!!),
and simply compile everything, it gives a compile error. I thought writing a macro only for this match statement sounds overkill... (It's a good idea to show an error on compile time.)
I have also tried to return Ok
and Err
instead of Some
and None
, but it might change current code a lot (for example, we can't use find_map
if we decide to return Ok and Err).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aah, so the way to return a compile_error! is to construct it with quote!
and insert it in the generated code, rather than invoking it directly in the proc-macro. You might need to change the fn signature on this function to allow you to return a TokenStream.
src/lib.rs
Outdated
@@ -7,7 +7,7 @@ mod fmt; | |||
use proc_macro::TokenStream; | |||
use syn::{parse_macro_input, DeriveInput}; | |||
|
|||
#[proc_macro_derive(Display)] | |||
#[proc_macro_derive(Display, attributes(display))] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we probably need to add source
as one of the attributes this macro is allowed to see.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found out that source
was not necessary, since tests are passing without adding the attribute.
tests/with_thiserror_tests.rs
Outdated
#[test] | ||
fn test_transparent_for_struct() { | ||
#[derive(Display, Error, Debug)] | ||
#[display(transparent)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
error.source() will work here if you do #[error(transparent)]
and just let thiserror do the deriving for display.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I commented in this tests and made sure its working.
error.source().unwrap().downcast_ref::<io::Error>().unwrap();
Probably because I have changed #[display(transparent)]
#[error(transparent)]
you mentioned
@@ -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::<io::Error>().unwrap(); | |||
error.source().unwrap().downcast_ref::<io::Error>().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only test failing now.
For this, I have no idea how to fix it. So I left it. May be up or downgrade version ?? |
tests/with_thiserror_tests.rs
Outdated
#[test] | ||
fn test_thiserror_implicit_and_source_works() { | ||
#[derive(Display, Error, Debug)] | ||
#[error("implicit source")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are only passing because you're using error
to derive the display which causes thiserror
to do it.
Try doing an enum like this as a test case
#[derive(Debug, Display, Error)]
enum SourceError {
#[error(transparent)]
#[display(transparent)]
ImplicitSource {
source: io::Error,
},
#[error(transparent)]
#[display(transparent)]
ExplicitSource {
source: String,
#[source]
io: io::Error,
},
/// There isnt really a {source}
DocSourceless {
source: String,
},
}
Sorry for the late response, I was struggled a lot and had some problems. Here is the WIP commit. As you suggested
I have tried to return So, I just gave up returning Then I had another problem. #[derive(Debug, Display, Error)]
enum SourceError {
#[error(transparent)]
#[display(transparent)]
ImplicitSource {
source: io::Error,
},
#[error(transparent)]
#[display(transparent)]
ExplicitSource {
source: String,
#[source]
io: io::Error,
},
/// There isnt really a {source}
DocSourceless {
source: String,
},
} If you use cargo-expand, you would get following code for the newly added test case. impl core::fmt::Display for SourceError {
fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
#[allow(unused_variables)]
match self {
SourceError::ImplicitSource { source } => {
formatter.write_fmt(::core::fmt::Arguments::new_v1(
&[""],
&match (&_0.__displaydoc_display(),) {
(arg0,) => [::core::fmt::ArgumentV1::new(
arg0,
::core::fmt::Display::fmt,
)],
},
))
}
It's raelly hard to read but it opens The problem is this. NestedMeta::Meta(Meta::Path(path)) => {
if path.is_ident("transparent") {
Some(Ok(LitStr::new("{0}", attr.span()))) <===== HERE!!!
} else {
Some(Err(Error::new_spanned(attr, "attr error")))
}
} If attribute of derive were What should I do? |
Ah, yes, the test case was supposed to show that we can't always just return "{0}" as the display string for source errors, we have to look at the identifier of the member that is the source error, either one named |
quote = "1.0" | ||
proc-macro2 = "1.0" | ||
cargo-expand = "0.6.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you accidentally added cargo-expand as a dependency here
nested.iter().find_map(|nested_attr| match nested_attr { | ||
NestedMeta::Meta(Meta::Path(path)) => { | ||
if path.is_ident("transparent") { | ||
Some(Ok(LitStr::new("{0}", attr.span()))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think what we might want to do is modify Display
so instead of only taking a format string, it can take an enum:
enum DisplayFmt {
Provided(LitStr),
Transparent,
}
That way we can differentiate between cases where we've been given a format string and cases where we need to figure it out ourselves. Then when we are constructing the actual display implementation for the type if the format is Transparent
we inspect the struct to look at the members and look for one with a #[source] attribute, or a member named source
, or if its a unit struct we just insert "{0}"
.
Hey @TakaakiFuruse are you still interested in working on this PR? If not I think I'd like to take it over. |
@yaahc |
This is a pull request for Add support for
thiserror
attributes #15.
This enables
displaydoc
to work withthiserror
as shown bellow.It basically does almost nothing if it found out
thiserror
.I also referenced
djc/askama
's implementation here forsrc/input.rs
.The patch went a little diverted from the use case we have discussed here, but this is what I could done so far.
If you have any suggestions and advice, I would be very happy have them.