Skip to content
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

[Feature] std::panic::Location support #291

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions impl/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct Attrs<'a> {
pub display: Option<Display<'a>>,
pub source: Option<Source<'a>>,
pub backtrace: Option<&'a Attribute>,
pub location: Option<&'a Attribute>,
pub from: Option<From<'a>>,
pub transparent: Option<Transparent<'a>>,
pub fmt: Option<Fmt<'a>>,
Expand Down Expand Up @@ -71,6 +72,7 @@ pub fn get(input: &[Attribute]) -> Result<Attrs> {
display: None,
source: None,
backtrace: None,
location: None,
from: None,
transparent: None,
fmt: None,
Expand All @@ -97,6 +99,12 @@ 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("location") {
attr.meta.require_path_only()?;
if attrs.location.is_some() {
return Err(Error::new_spanned(attr, "duplicate #[location] attribute"));
}
attrs.location = Some(attr);
} else if attr.path().is_ident("from") {
match attr.meta {
Meta::Path(_) => {}
Expand Down
175 changes: 116 additions & 59 deletions impl/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,28 @@ fn impl_struct(input: Struct) -> TokenStream {
#request.provide_ref::<::thiserror::__private::Backtrace>(&self.#backtrace);
})
};
let location_provide = if let Some(location_field) = input.location_field() {
let location = &location_field.member;

if type_is_option(location_field.ty) {
Some(quote! {
if let ::core::option::Option::Some(location) = &self.#location {
#request.provide_ref::<::core::panic::Location>(location);
}
})
} else {
Some(quote! {
#request.provide_ref::<::core::panic::Location>(&self.#location);
})
}
} else {
None
};
quote! {
use ::thiserror::__private::ThiserrorProvide as _;
#source_provide
#self_provide
#location_provide
}
} else if type_is_option(backtrace_field.ty) {
quote! {
Expand Down Expand Up @@ -167,9 +185,16 @@ fn impl_struct(input: Struct) -> TokenStream {
let span = from_field.attrs.from.unwrap().span;
let backtrace_field = input.distinct_backtrace_field();
let from = unoptional_type(from_field.ty);
let track_caller = input.location_field().map(|_| quote!(#[track_caller]));
let source_var = Ident::new("source", span);
let body = from_initializer(from_field, backtrace_field, &source_var);
let body = from_initializer(
from_field,
backtrace_field,
&source_var,
input.location_field(),
);
let from_function = quote! {
#track_caller
fn from(#source_var: #from) -> Self {
#ty #body
}
Expand Down Expand Up @@ -266,25 +291,43 @@ fn impl_enum(input: Enum) -> TokenStream {
let request = quote!(request);
let arms = input.variants.iter().map(|variant| {
let ident = &variant.ident;
match (variant.backtrace_field(), variant.source_field()) {
(Some(backtrace_field), Some(source_field))
if backtrace_field.attrs.backtrace.is_none() =>
{
let backtrace = &backtrace_field.member;
let source = &source_field.member;
let varsource = quote!(source);
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {source.span()=>
if let ::core::option::Option::Some(source) = #varsource {
source.thiserror_provide(#request);
}
}

let mut arm = vec![];
let mut head = vec![];

if let Some((source_field, backtrace_field)) = variant
.backtrace_field()
.and_then(|b| variant.source_field().take().map(|s| (s, b)))
.and_then(|(s, b)| {
// TODO: replace with take_if upon stabilization

if s.member == b.member {
Some((s, b))
} else {
quote_spanned! {source.span()=>
#varsource.thiserror_provide(#request);
None
}
})
{
let backtrace = &backtrace_field.member;
let varsource = quote!(source);
let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {backtrace.span()=>
if let ::core::option::Option::Some(source) = #varsource {
source.thiserror_provide(#request);
}
};
let self_provide = if type_is_option(backtrace_field.ty) {
}
} else {
quote_spanned! {backtrace.span()=>
#varsource.thiserror_provide(#request);
}
};

head.push(quote! { #backtrace: #varsource });
arm.push(quote! { #source_provide });
} else {
if let Some(backtrace_field) = variant.backtrace_field() {
let backtrace = &backtrace_field.member;
let body = if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = backtrace {
#request.provide_ref::<::thiserror::__private::Backtrace>(backtrace);
Expand All @@ -295,63 +338,59 @@ fn impl_enum(input: Enum) -> TokenStream {
#request.provide_ref::<::thiserror::__private::Backtrace>(backtrace);
}
};
quote! {
#ty::#ident {
#backtrace: backtrace,
#source: #varsource,
..
} => {
use ::thiserror::__private::ThiserrorProvide as _;
#source_provide
#self_provide
}
}

head.push(quote! { #backtrace: backtrace });
arm.push(quote! { #body });
}
(Some(backtrace_field), Some(source_field))
if backtrace_field.member == source_field.member =>
{
let backtrace = &backtrace_field.member;

if let Some(source_field) = variant.source_field() {
let source = &source_field.member;
let varsource = quote!(source);

let source_provide = if type_is_option(source_field.ty) {
quote_spanned! {backtrace.span()=>
quote_spanned! {source.span()=>
if let ::core::option::Option::Some(source) = #varsource {
source.thiserror_provide(#request);
}
}
} else {
quote_spanned! {backtrace.span()=>
quote_spanned! {source.span()=>
#varsource.thiserror_provide(#request);
}
};

head.push(quote! { #source: #varsource });
arm.push(quote! { #source_provide });
}
}

if let Some(location_field) = variant.location_field() {
let location = &location_field.member;

let location_provide = if type_is_option(location_field.ty) {
quote! {
#ty::#ident {#backtrace: #varsource, ..} => {
use ::thiserror::__private::ThiserrorProvide as _;
#source_provide
if let ::core::option::Option::Some(location) = location {
#request.provide_ref::<::core::panic::Location>(location);
}
}
}
(Some(backtrace_field), _) => {
let backtrace = &backtrace_field.member;
let body = if type_is_option(backtrace_field.ty) {
quote! {
if let ::core::option::Option::Some(backtrace) = backtrace {
#request.provide_ref::<::thiserror::__private::Backtrace>(backtrace);
}
}
} else {
quote! {
#request.provide_ref::<::thiserror::__private::Backtrace>(backtrace);
}
};
} else {
quote! {
#ty::#ident {#backtrace: backtrace, ..} => {
#body
}
#request.provide_ref::<::core::panic::Location>(location);
}
};

head.push(quote! { #location: location });
arm.push(quote! { #location_provide });
}

quote! {
#ty::#ident {
#(#head,)*
..
} => {
use thiserror::__private::ThiserrorProvide as _;
#(#arm)*
}
(None, _) => quote! {
#ty::#ident {..} => {}
},
}
});
Some(quote! {
Expand Down Expand Up @@ -435,11 +474,14 @@ fn impl_enum(input: Enum) -> TokenStream {
let from_field = variant.from_field()?;
let span = from_field.attrs.from.unwrap().span;
let backtrace_field = variant.distinct_backtrace_field();
let location_field = variant.location_field();
let variant = &variant.ident;
let from = unoptional_type(from_field.ty);
let source_var = Ident::new("source", span);
let body = from_initializer(from_field, backtrace_field, &source_var);
let body = from_initializer(from_field, backtrace_field, &source_var, location_field);
let track_caller = location_field.map(|_| quote!(#[track_caller]));
let from_function = quote! {
#track_caller
fn from(#source_var: #from) -> Self {
#ty::#variant #body
}
Expand Down Expand Up @@ -512,6 +554,7 @@ fn from_initializer(
from_field: &Field,
backtrace_field: Option<&Field>,
source_var: &Ident,
location_field: Option<&Field>,
) -> TokenStream {
let from_member = &from_field.member;
let some_source = if type_is_option(from_field.ty) {
Expand All @@ -531,9 +574,23 @@ fn from_initializer(
}
}
});
let location = location_field.map(|location_field| {
let location_member = &location_field.member;

if type_is_option(location_field.ty) {
quote! {
#location_member: ::core::option::Option::Some(::core::panic::Location::caller()),
}
} else {
quote! {
#location_member: ::core::convert::From::from(::core::panic::Location::caller()),
}
}
});
quote!({
#from_member: #some_source,
#backtrace
#location
})
}

Expand Down
2 changes: 1 addition & 1 deletion impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ mod valid;
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(Error, attributes(backtrace, error, from, source))]
#[proc_macro_derive(Error, attributes(backtrace, error, from, source, location))]
pub fn derive_error(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
expand::derive(&input).into()
Expand Down
56 changes: 56 additions & 0 deletions impl/src/prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use crate::ast::{Enum, Field, Struct, Variant};
use crate::unraw::MemberUnraw;
use proc_macro2::Span;
use syn::Type;
use syn::{
AngleBracketedGenericArguments, GenericArgument, Lifetime, PathArguments, TypeReference,
};

impl Struct<'_> {
pub(crate) fn from_field(&self) -> Option<&Field> {
Expand All @@ -16,6 +19,10 @@ impl Struct<'_> {
backtrace_field(&self.fields)
}

pub(crate) fn location_field(&self) -> Option<&Field> {
location_field(&self.fields)
}

pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> {
let backtrace_field = self.backtrace_field()?;
distinct_backtrace_field(backtrace_field, self.from_field())
Expand Down Expand Up @@ -63,6 +70,10 @@ impl Variant<'_> {
backtrace_field(&self.fields)
}

pub(crate) fn location_field(&self) -> Option<&Field> {
location_field(&self.fields)
}

pub(crate) fn distinct_backtrace_field(&self) -> Option<&Field> {
let backtrace_field = self.backtrace_field()?;
distinct_backtrace_field(backtrace_field, self.from_field())
Expand All @@ -74,6 +85,10 @@ impl Field<'_> {
type_is_backtrace(self.ty)
}

pub(crate) fn is_location(&self) -> bool {
type_is_location(self.ty)
}

pub(crate) fn source_span(&self) -> Span {
if let Some(source_attr) = &self.attrs.source {
source_attr.span
Expand Down Expand Up @@ -123,6 +138,20 @@ fn backtrace_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
None
}

fn location_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {
for field in fields {
if field.attrs.location.is_some() {
return Some(field);
}
}
for field in fields {
if field.is_location() {
return Some(field);
}
}
None
}

// The #[backtrace] field, if it is not the same as the #[from] field.
fn distinct_backtrace_field<'a, 'b>(
backtrace_field: &'a Field<'b>,
Expand All @@ -146,3 +175,30 @@ fn type_is_backtrace(ty: &Type) -> bool {
let last = path.segments.last().unwrap();
last.ident == "Backtrace" && last.arguments.is_empty()
}

fn type_is_location(ty: &Type) -> bool {
let path = match ty {
Type::Reference(TypeReference {
lifetime: Some(Lifetime { ident: ltident, .. }),
elem, // TODO: replace with `elem: box Type::Path(path)` once box_patterns stabalizes
..
}) if ltident == "static" => match &**elem {
Type::Path(ty) => &ty.path,
_ => return false,
},
_ => return false,
};

let last = path.segments.last().unwrap();

last.ident == "Location"
&& match &last.arguments {
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) => {
match args.first() {
Some(GenericArgument::Lifetime(Lifetime { ident, .. })) => ident == "static",
_ => false,
}
}
_ => false,
}
}
Loading