Repository: dtolnay/thiserror
Pull request #121 (2021-02-18 16:51:19)

Consistently use `quote!` when emitting 'source'
When a macro generates part of the derive input, the call-site hygiene
may be different than the hygiene of a field. Therefore, we need to
be sure to use the same hygiene information for any identifiers we
generate, instead of relying on the hygiene from a particular span
via `quote_spanned!`


Patch:
diff --git a/impl/src/expand.rs b/impl/src/expand.rs
index d712146..0f9d5ed 100644
--- a/impl/src/expand.rs
+++ b/impl/src/expand.rs
@@ -171,9 +171,10 @@ fn impl_enum(input: Enum) -> TokenStream {
                 } else {
                     None
                 };
-                let dyn_error = quote_spanned!(source.span()=> source #asref.as_dyn_error());
+                let source_literal = quote! { source };
+                let dyn_error = quote_spanned!(source.span()=> #source_literal #asref.as_dyn_error());
                 quote! {
-                    #ty::#ident {#source: source, ..} => std::option::Option::Some(#dyn_error),
+                    #ty::#ident {#source: #source_literal, ..} => std::option::Option::Some(#dyn_error),
                 }
             } else {
                 quote! {
@@ -203,13 +204,14 @@ fn impl_enum(input: Enum) -> TokenStream {
                 {
                     let backtrace = &backtrace_field.member;
                     let source = &source_field.member;
+                    let source_literal = quote! { source };
                     let source_backtrace = if type_is_option(source_field.ty) {
                         quote_spanned! {source.span()=>
-                            source.as_ref().and_then(|source| source.as_dyn_error().backtrace())
+                            #source_literal.as_ref().and_then(|source| #source_literal.as_dyn_error().backtrace())
                         }
                     } else {
                         quote_spanned! {source.span()=>
-                            source.as_dyn_error().backtrace()
+                            #source_literal.as_dyn_error().backtrace()
                         }
                     };
                     let combinator = if type_is_option(backtrace_field.ty) {
@@ -224,7 +226,7 @@ fn impl_enum(input: Enum) -> TokenStream {
                     quote! {
                         #ty::#ident {
                             #backtrace: backtrace,
-                            #source: source,
+                            #source: #source_literal,
                             ..
                         } => {
                             use thiserror::private::AsDynError;


Test patch:
diff --git a/tests/test_source.rs b/tests/test_source.rs
index 66d9dad..860e727 100644
--- a/tests/test_source.rs
+++ b/tests/test_source.rs
@@ -48,3 +48,16 @@ fn test_boxed_source() {
     let error = BoxedSource { source };
     error.source().unwrap().downcast_ref::<io::Error>().unwrap();
 }
+
+macro_rules! error_from_macro {
+    ($($variants:tt)*) => {
+        #[derive(Error)]
+        #[derive(Debug)]
+        pub enum MacroSource {
+            $($variants)*
+        }
+    }
+}
+
+// Test that we generate impls with the proper hygiene
+error_from_macro!(#[error("Something")] Variant(#[from] io::Error));