Support Option<$source>

This commit is contained in:
David Tolnay 2019-10-12 15:31:51 -07:00
parent c2865642b0
commit f18a2a605d
No known key found for this signature in database
GPG Key ID: F9BA143B95FF6D82
2 changed files with 68 additions and 21 deletions

View File

@ -3,7 +3,7 @@ use crate::valid;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned}; use quote::{format_ident, quote, quote_spanned};
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::{DeriveInput, Member, Result}; use syn::{DeriveInput, Member, PathArguments, Result, Type};
pub fn derive(node: &DeriveInput) -> Result<TokenStream> { pub fn derive(node: &DeriveInput) -> Result<TokenStream> {
let input = Input::from_syn(node)?; let input = Input::from_syn(node)?;
@ -18,8 +18,14 @@ fn impl_struct(input: Struct) -> TokenStream {
let ty = &input.ident; let ty = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let source_method = input.source_member().map(|source| { let source_method = input.source_field().map(|source_field| {
let dyn_error = quote_spanned!(source.span()=> self.#source.as_dyn_error()); let source = &source_field.member;
let asref = if type_is_option(source_field.ty) {
Some(quote_spanned!(source.span()=> .as_ref()?))
} else {
None
};
let dyn_error = quote_spanned!(source.span()=> self.#source #asref.as_dyn_error());
quote! { quote! {
fn source(&self) -> std::option::Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> std::option::Option<&(dyn std::error::Error + 'static)> {
use thiserror::private::AsDynError; use thiserror::private::AsDynError;
@ -30,11 +36,20 @@ fn impl_struct(input: Struct) -> TokenStream {
let backtrace_method = input.backtrace_field().map(|backtrace| { let backtrace_method = input.backtrace_field().map(|backtrace| {
let backtrace = &backtrace.member; let backtrace = &backtrace.member;
let body = if let Some(source) = input.source_member() { let body = if let Some(source_field) = input.source_field() {
let dyn_error = quote_spanned!(source.span()=> self.#source.as_dyn_error()); let source = &source_field.member;
let source_backtrace = if type_is_option(source_field.ty) {
quote_spanned! {source.span()=>
self.#source.as_ref().and_then(|source| source.as_dyn_error().backtrace())
}
} else {
quote_spanned! {source.span()=>
self.#source.as_dyn_error().backtrace()
}
};
quote!({ quote!({
use thiserror::private::AsDynError; use thiserror::private::AsDynError;
#dyn_error.backtrace().unwrap_or(&self.#backtrace) #source_backtrace.unwrap_or(&self.#backtrace)
}) })
} else { } else {
quote! { quote! {
@ -77,9 +92,15 @@ fn impl_enum(input: Enum) -> TokenStream {
let source_method = if input.has_source() { let source_method = if input.has_source() {
let arms = input.variants.iter().map(|variant| { let arms = input.variants.iter().map(|variant| {
let ident = &variant.ident; let ident = &variant.ident;
match variant.source_member() { match variant.source_field() {
Some(source) => { Some(source_field) => {
let dyn_error = quote_spanned!(source.span()=> source.as_dyn_error()); let source = &source_field.member;
let asref = if type_is_option(source_field.ty) {
Some(quote_spanned!(source.span()=> .as_ref()?))
} else {
None
};
let dyn_error = quote_spanned!(source.span()=> source #asref.as_dyn_error());
quote! { quote! {
#ty::#ident {#source: source, ..} => std::option::Option::Some(#dyn_error), #ty::#ident {#source: source, ..} => std::option::Option::Some(#dyn_error),
} }
@ -104,10 +125,19 @@ fn impl_enum(input: Enum) -> TokenStream {
let backtrace_method = if input.has_backtrace() { let backtrace_method = if input.has_backtrace() {
let arms = input.variants.iter().map(|variant| { let arms = input.variants.iter().map(|variant| {
let ident = &variant.ident; let ident = &variant.ident;
match (variant.backtrace_field(), variant.source_member()) { match (variant.backtrace_field(), variant.source_field()) {
(Some(backtrace), Some(source)) if backtrace.attrs.backtrace.is_none() => { (Some(backtrace), Some(source_field)) if backtrace.attrs.backtrace.is_none() => {
let backtrace = &backtrace.member; let backtrace = &backtrace.member;
let dyn_error = quote_spanned!(source.span()=> source.as_dyn_error()); let source = &source_field.member;
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())
}
} else {
quote_spanned! {source.span()=>
source.as_dyn_error().backtrace()
}
};
quote! { quote! {
#ty::#ident { #ty::#ident {
#backtrace: backtrace, #backtrace: backtrace,
@ -115,7 +145,7 @@ fn impl_enum(input: Enum) -> TokenStream {
.. ..
} => std::option::Option::Some({ } => std::option::Option::Some({
use thiserror::private::AsDynError; use thiserror::private::AsDynError;
#dyn_error.backtrace().unwrap_or(backtrace) #source_backtrace.unwrap_or(backtrace)
}), }),
} }
} }
@ -192,3 +222,20 @@ fn fields_pat(fields: &[Field]) -> TokenStream {
None => quote!({}), None => quote!({}),
} }
} }
fn type_is_option(ty: &Type) -> bool {
let path = match ty {
Type::Path(ty) => &ty.path,
_ => return false,
};
let last = path.segments.last().unwrap();
if last.ident != "Option" {
return false;
}
match &last.arguments {
PathArguments::AngleBracketed(bracketed) => bracketed.args.len() == 1,
_ => false,
}
}

View File

@ -2,8 +2,8 @@ use crate::ast::{Enum, Field, Struct, Variant};
use syn::{Member, Type}; use syn::{Member, Type};
impl Struct<'_> { impl Struct<'_> {
pub(crate) fn source_member(&self) -> Option<&Member> { pub(crate) fn source_field(&self) -> Option<&Field> {
source_member(&self.fields) source_field(&self.fields)
} }
pub(crate) fn backtrace_field(&self) -> Option<&Field> { pub(crate) fn backtrace_field(&self) -> Option<&Field> {
@ -15,7 +15,7 @@ impl Enum<'_> {
pub(crate) fn has_source(&self) -> bool { pub(crate) fn has_source(&self) -> bool {
self.variants self.variants
.iter() .iter()
.any(|variant| variant.source_member().is_some()) .any(|variant| variant.source_field().is_some())
} }
pub(crate) fn has_backtrace(&self) -> bool { pub(crate) fn has_backtrace(&self) -> bool {
@ -34,8 +34,8 @@ impl Enum<'_> {
} }
impl Variant<'_> { impl Variant<'_> {
pub(crate) fn source_member(&self) -> Option<&Member> { pub(crate) fn source_field(&self) -> Option<&Field> {
source_member(&self.fields) source_field(&self.fields)
} }
pub(crate) fn backtrace_field(&self) -> Option<&Field> { pub(crate) fn backtrace_field(&self) -> Option<&Field> {
@ -43,15 +43,15 @@ impl Variant<'_> {
} }
} }
fn source_member<'a>(fields: &'a [Field]) -> Option<&'a Member> { fn source_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
for field in fields { for field in fields {
if field.attrs.source.is_some() { if field.attrs.source.is_some() {
return Some(&field.member); return Some(&field);
} }
} }
for field in fields { for field in fields {
match &field.member { match &field.member {
Member::Named(ident) if ident == "source" => return Some(&field.member), Member::Named(ident) if ident == "source" => return Some(&field),
_ => {} _ => {}
} }
} }