Repository: dtolnay/thiserror (Rust)

Issue:
Generate From impls
Error types commonly provide `From` impls so that they can be constructed by `?`.

I don't want to become a fully general `From` derive, but I would be okay with providing a convenient `From` impl *just for sources*. Any `From` impl other than from the source should be handwritten for visibility or using something like [derive_more](https://github.com/JelteF/derive_more).

```rust
#[derive(Error, Debug)]
enum EnumError {
    Example(#[from] io::Error),
}
```


Discussion:
For the case of lots of variants that all want a `From` impl there could be an enum-level attribute to opt in to all of them.

```rust
#[derive(Error, Debug)]
#[error(from_source)]
pub enum E {
    E0(#[source] io::Error),
    E1(#[source] serde_json::Error),
    E2(#[source] regex::Error),
    E3(#[source] hyper::Error),
    E4(#[source] anyhow::Error),
}
```
Relevant thread:
https://www.reddit.com/r/rust/comments/dfkwfo/announcing_thiserror_a_convenient_modern/f34fo4h/?context=1
I've been poking at this a little; for structs, should this only work if there's one field (which is `source`)? Also curious if you're open to a PR for this.
The behavior of the attributes (AFAICT) is that "first found wins", so the first found with the attribute or with the right name would be found as the `source` bit.
"First found wins" is not intended behavior, I filed https://github.com/dtolnay/thiserror/issues/6 to fix it. The `From` should work if there is exactly one field which is the source, or two fields source and backtrace in which case a backtrace should be captured from within the `From` impl.
Thanks for the clarification @dtolnay. I know there's some issues to triage, but I do plan to try my hand at implementing some of these fixes anyhow to learn more about macros, so let me know if I can be of help.

@mathstuf my question above was not directed as much at knowing which field to to use; but rather about how in the `From` impl it's hard to know how to fill in all the other non-source (and non-backtrace) fields.
Oh, that's a good point. Maybe `From` should only be implemented for `source`-as-only-field. Probably easier for `enum` than `struct` too, but you have to be careful that there's only a single instance of that source type in any variant. That's another reason to not do `From` by default. I'd be surprised if this kind of stuff doesn't end up as another case study in https://github.com/dtolnay/case-studies in all the ways Rust code can tickle assumptions for macro writers ;) .
Just a note that there may be multiple enum variants with the same `source` error type. In this case, I guess the ergonomic thing to do is:
- when doing per-variant opt-in, double check all variant `source` types to make sure there's no overlap.
- when doing a 'global' opt-in for all variants, have an additional attr that opts-out (`#[no_from]`), or just error out.

Or, the derive could just report an error and ask the user to manually impl `From`

It looks like `err-derive` tends towards allowing more macro machinery to work it out ergonomically. https://old.reddit.com/r/rust/comments/dfkwfo/announcing_thiserror_a_convenient_modern/f34n4hs/

It seems like you're trying to keep `thiserror` minimal, so I'm curious to know if adding all this logic is part of the project's scope.
> - when doing per-variant opt-in, double check all variant `source` types to make sure there's no overlap.

+1, it should produce a targeted error underneath one of the pair of overlapping source types.

> - when doing a 'global' opt-in for all variants, have an additional attr that opts-out (`#[no_from]`), or just error out.

I would lean toward erroring out. I don't want any no\_from attribute for now.
@dtolnay, two questions:

1) I'm having some trouble working out how to do the `#[source, from]` syntax. Unless I'm missing something, you can't automatically squash multiple attributes together. Can you point me in the right direction?

2) Would you want to use trybuild now? I've added some new test structs to `tests/test_error.rs`, and taken a peek using cargo-expand, but I'm starting to think about how to test some of the failure modes.
1. Regarding `#[source, from]`, you are right, it doesn't seem possible to parse this with rustc currently. I think since we plan to only support From for sources, this can be written as just `#[from]`, i.e. `#[from]` will always imply `#[source]` implicitly.

    ```rust
    #[derive(Error, Debug)]
    pub struct Error(#[from] io::Error); // io::Error is implicitly the source
    ```

2. Yes absolutely. I filed https://github.com/dtolnay/thiserror/issues/11 to track this as it can happen independently.
If we want `#[source, from]`, how about `#[from_source]`/`#[source_from]` to keep `#[from]` unused for possible better use case in the future?
I'm the author of derive_more. I saw this crate popping up on reddit and I like the way this crate derives the Error trait. Then I saw this issue and I was thinking that another solution would be to combine forces on this. The current master branch of derive_more supports the following `From` derive:

```rust
// Default not from
#[derive(From, Debug)]
enum EnumError {
    #[from] 
    Example(io::Error),
    NotFromable(i32),
}

// Default from
#[derive(From, Debug)]
enum EnumError {
    Example(io::Error),
    #[from(ignore)]
    NotFromable(i32),
}
```
and together with the `Display` derive from the crate
```rust
#[derive(From, Display)]
#[display(fmt="E prefix: {}")
pub enum E {
    #[display(fmt='io error')]
    E0(io::Error),
    E1(serde_json::Error),
    E2(regex::Error),
    E3(hyper::Error),
    E4(anyhow::Error),
    #[from(ignore)]
    #[display(fmt="wrong count {}", count)]
    SomeCustomError{count: i32},
}
```

I think it would be very nice if we could combine the good parts of this library and `derive_more`.
```rust
#[derive(From, Display, Error)]
#[display("E prefix: {}")
pub enum E {
    #[display('io error')]
    E0(io::Error),
    E1(serde_json::Error),
    E2(regex::Error),
    E3(hyper::Error),
    E4(anyhow::Error),
    #[from(ignore)]
    #[source(ignore)] // maybe #[error(ignore)]
    #[display("wrong count {count}")]
    SomeCustomError{count: i32, backtrace: Backtrace},
}
```
@dtolnay what do you think?

@JelteF that does not sound promising to me because error objects require a pretty error-specific impl of From when it comes to dealing with backtraces.
I'm not entirely sure what you mean, wouldn't you want to simply forward the backtrace call to the source error?
The From impl needs to capture a backtrace if the source error doesn't already have one.
That's definitely a good point. Some options I can think of:
```rust
// Specific solution for error, that does auto detection of backtrace field
#[derive(From, Debug)]
enum EnumError {
    #[from(error)] 
    Example{error: io::Error, backtrace: Backtrace),
    NotFromable(i32),
}
```

```rust
// More general, but more verbose
#[derive(From, Debug)]
enum EnumError {
    Example{
       error: io::Error, 
       #[from(default="Backtrace::capture()")] 
       backtrace: Backtrace,
    },
    NotFromable(i32),
}
```

Because of the way I've implemented attribute handling, the first one would also work easily for the big struct by putting the from attribute on the enum:
```rust
#[derive(From, Display, Error)]
#[display("E prefix: {}")
#[from(error)]
pub enum E {
    #[display('io error')]
    E0{source: io::Error, backtrace: Backtrace},
    E1{source: serde_json::Error, backtrace: Backtrace},
    E2{source: regex::Error, backtrace: Backtrace},
    E3{source: hyper::Error, backtrace: Backtrace},
    E4{source: anyhow::Error, backtrace: Backtrace},
    #[from(ignore)]
    #[source(ignore)] // maybe #[error(ignore)]
    #[display("wrong count {count}")]
    SomeCustomError{count: i32, backtrace: Backtrace},
}
```

I'll think a bit more about this, but I actually like the option above quite a bit.
FYI to the people with :+1: reaction above -- @drrlvn @Alexendoo @hwchen @joseluis @CAD97 @NAlexPear @jplatte @mitchmindtree @twistedfall @Jezza @MikailBag @MOZGIII

`From` impls have been implemented in thiserror 1.0.2!
Here are the release notes: https://github.com/dtolnay/thiserror/releases/tag/1.0.2



Patch:
diff --git a/impl/src/attr.rs b/impl/src/attr.rs
index efd3e659..7a290bb7 100644
--- a/impl/src/attr.rs
+++ b/impl/src/attr.rs
@@ -11,6 +11,7 @@ pub struct Attrs<'a> {
     pub display: Option<Display<'a>>,
     pub source: Option<&'a Attribute>,
     pub backtrace: Option<&'a Attribute>,
+    pub from: Option<&'a Attribute>,
 }
 
 #[derive(Clone)]
@@ -26,6 +27,7 @@ pub fn get(input: &[Attribute]) -> Result<Attrs> {
         display: None,
         source: None,
         backtrace: None,
+        from: None,
     };
 
     for attr in input {
@@ -50,6 +52,15 @@ pub fn get(input: &[Attribute]) -> Result<Attrs> {
                 return Err(Error::new_spanned(attr, "duplicate #[backtrace] attribute"));
             }
             attrs.backtrace = Some(attr);
+        } else if attr.path.is_ident("from") {
+            if !attr.tokens.is_empty() {
+                // Assume this is meant for derive_more crate or something.
+                continue;
+            }
+            if attrs.from.is_some() {
+                return Err(Error::new_spanned(attr, "duplicate #[from] attribute"));
+            }
+            attrs.from = Some(attr);
         }
     }
 
diff --git a/impl/src/expand.rs b/impl/src/expand.rs
index 42c0e865..94b55924 100644
--- a/impl/src/expand.rs
+++ b/impl/src/expand.rs
@@ -89,12 +89,25 @@ fn impl_struct(input: Struct) -> TokenStream {
         }
     });
 
+    let from_impl = input.from_field().map(|from_field| {
+        let member = &from_field.member;
+        let from = from_field.ty;
+        quote! {
+            impl #impl_generics std::convert::From<#from> for #ty #ty_generics #where_clause {
+                fn from(source: #from) -> Self {
+                    #ty { #member: source }
+                }
+            }
+        }
+    });
+
     quote! {
         impl #impl_generics std::error::Error for #ty #ty_generics #where_clause {
             #source_method
             #backtrace_method
         }
         #display_impl
+        #from_impl
     }
 }
 
@@ -228,12 +241,27 @@ fn impl_enum(input: Enum) -> TokenStream {
         None
     };
 
+    let from_impls = input.variants.iter().filter_map(|variant| {
+        let from_field = variant.from_field()?;
+        let variant = &variant.ident;
+        let member = &from_field.member;
+        let from = from_field.ty;
+        Some(quote! {
+            impl #impl_generics std::convert::From<#from> for #ty #ty_generics #where_clause {
+                fn from(source: #from) -> Self {
+                    #ty::#variant { #member: source }
+                }
+            }
+        })
+    });
+
     quote! {
         impl #impl_generics std::error::Error for #ty #ty_generics #where_clause {
             #source_method
             #backtrace_method
         }
         #display_impl
+        #(#from_impls)*
     }
 }
 
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index 2ea966bc..119e2438 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -10,7 +10,7 @@ mod valid;
 use proc_macro::TokenStream;
 use syn::{parse_macro_input, DeriveInput};
 
-#[proc_macro_derive(Error, attributes(backtrace, error, source))]
+#[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)
diff --git a/impl/src/prop.rs b/impl/src/prop.rs
index 56b6108c..3e40539c 100644
--- a/impl/src/prop.rs
+++ b/impl/src/prop.rs
@@ -2,6 +2,10 @@ use crate::ast::{Enum, Field, Struct, Variant};
 use syn::{Member, Type};
 
 impl Struct<'_> {
+    pub(crate) fn from_field(&self) -> Option<&Field> {
+        from_field(&self.fields)
+    }
+
     pub(crate) fn source_field(&self) -> Option<&Field> {
         source_field(&self.fields)
     }
@@ -34,6 +38,10 @@ impl Enum<'_> {
 }
 
 impl Variant<'_> {
+    pub(crate) fn from_field(&self) -> Option<&Field> {
+        from_field(&self.fields)
+    }
+
     pub(crate) fn source_field(&self) -> Option<&Field> {
         source_field(&self.fields)
     }
@@ -43,9 +51,18 @@ impl Variant<'_> {
     }
 }
 
+fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
+    for field in fields {
+        if field.attrs.from.is_some() {
+            return Some(&field);
+        }
+    }
+    None
+}
+
 fn source_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
     for field in fields {
-        if field.attrs.source.is_some() {
+        if field.attrs.from.is_some() || field.attrs.source.is_some() {
             return Some(&field);
         }
     }
diff --git a/impl/src/valid.rs b/impl/src/valid.rs
index 5c0fd583..d52882d0 100644
--- a/impl/src/valid.rs
+++ b/impl/src/valid.rs
@@ -1,6 +1,8 @@
 use crate::ast::{Enum, Field, Input, Struct, Variant};
 use crate::attr::Attrs;
-use syn::{Error, Result};
+use quote::ToTokens;
+use std::collections::BTreeSet as Set;
+use syn::{Error, Member, Result};
 
 pub(crate) const CHECKED: &str = "checked in validation";
 
@@ -37,6 +39,18 @@ impl Enum<'_> {
                 ));
             }
         }
+        let mut from_types = Set::new();
+        for variant in &self.variants {
+            if let Some(from_field) = variant.from_field() {
+                let repr = from_field.ty.to_token_stream().to_string();
+                if !from_types.insert(repr) {
+                    return Err(Error::new_spanned(
+                        from_field.original,
+                        "cannot derive From because another variant has the same source type",
+                    ));
+                }
+            }
+        }
         Ok(())
     }
 }
@@ -65,6 +79,12 @@ impl Field<'_> {
 }
 
 fn check_non_field_attrs(attrs: &Attrs) -> Result<()> {
+    if let Some(from) = &attrs.from {
+        return Err(Error::new_spanned(
+            from,
+            "not expected here; the #[from] attribute belongs on a specific field",
+        ));
+    }
     if let Some(source) = &attrs.source {
         return Err(Error::new_spanned(
             source,
@@ -81,24 +101,55 @@ fn check_non_field_attrs(attrs: &Attrs) -> Result<()> {
 }
 
 fn check_field_attrs(fields: &[Field]) -> Result<()> {
-    let mut has_source = false;
-    let mut has_backtrace = false;
+    let mut from_field = None;
+    let mut source_field = None;
+    let mut backtrace_field = None;
     for field in fields {
-        if let Some(source) = &field.attrs.source {
-            if has_source {
+        if let Some(from) = field.attrs.from {
+            if from_field.is_some() {
+                return Err(Error::new_spanned(from, "duplicate #[from] attribute"));
+            }
+            from_field = Some(field);
+        }
+        if let Some(source) = field.attrs.source {
+            if source_field.is_some() {
                 return Err(Error::new_spanned(source, "duplicate #[source] attribute"));
             }
-            has_source = true;
+            source_field = Some(field);
         }
-        if let Some(backtrace) = &field.attrs.backtrace {
-            if has_backtrace {
+        if let Some(backtrace) = field.attrs.backtrace {
+            if backtrace_field.is_some() {
                 return Err(Error::new_spanned(
                     backtrace,
                     "duplicate #[backtrace] attribute",
                 ));
             }
-            has_backtrace = true;
+            backtrace_field = Some(field);
+        }
+    }
+    if let (Some(from_field), Some(source_field)) = (from_field, source_field) {
+        if !same_member(from_field, source_field) {
+            return Err(Error::new_spanned(
+                from_field.attrs.from,
+                "#[from] is only supported on the source field, not any other field",
+            ));
+        }
+    }
+    if let Some(from_field) = from_field {
+        if fields.len() > 1 {
+            return Err(Error::new_spanned(
+                from_field.attrs.from,
+                "deriving From requires no fields other than source",
+            ));
         }
     }
     Ok(())
 }
+
+fn same_member(one: &Field, two: &Field) -> bool {
+    match (&one.member, &two.member) {
+        (Member::Named(one), Member::Named(two)) => one == two,
+        (Member::Unnamed(one), Member::Unnamed(two)) => one.index == two.index,
+        _ => unreachable!(),
+    }
+}


Test patch:
diff --git a/tests/test_from.rs b/tests/test_from.rs
new file mode 100644
index 00000000..f1755fb8
--- /dev/null
+++ b/tests/test_from.rs
@@ -0,0 +1,39 @@
+use std::io;
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+#[error("...")]
+pub struct ErrorStruct {
+    #[from]
+    source: io::Error,
+}
+
+#[derive(Error, Debug)]
+#[error("...")]
+pub struct ErrorTuple(#[from] io::Error);
+
+#[derive(Error, Debug)]
+#[error("...")]
+pub enum ErrorEnum {
+    Test {
+        #[from]
+        source: io::Error,
+    },
+}
+
+#[derive(Error, Debug)]
+#[error("...")]
+pub enum Many {
+    Any(#[from] anyhow::Error),
+    Io(#[from] io::Error),
+}
+
+fn assert_impl<T: From<io::Error>>() {}
+
+#[test]
+fn test_from() {
+    assert_impl::<ErrorStruct>();
+    assert_impl::<ErrorTuple>();
+    assert_impl::<ErrorEnum>();
+    assert_impl::<Many>();
+}
diff --git a/tests/ui/from-not-source.rs b/tests/ui/from-not-source.rs
new file mode 100644
index 00000000..d1855bec
--- /dev/null
+++ b/tests/ui/from-not-source.rs
@@ -0,0 +1,11 @@
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub struct Error {
+    #[source]
+    source: std::io::Error,
+    #[from]
+    other: anyhow::Error,
+}
+
+fn main() {}
diff --git a/tests/ui/from-not-source.stderr b/tests/ui/from-not-source.stderr
new file mode 100644
index 00000000..b656c8ed
--- /dev/null
+++ b/tests/ui/from-not-source.stderr
@@ -0,0 +1,5 @@
+error: #[from] is only supported on the source field, not any other field
+ --> $DIR/from-not-source.rs:7:5
+  |
+7 |     #[from]
+  |     ^^^^^^^