Skip to content
Merged
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
129 changes: 102 additions & 27 deletions masterror-derive/src/from_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use proc_macro2::TokenStream;
use quote::quote;
use syn::Error;

use crate::input::{ErrorData, ErrorInput, Field, Fields, StructData, VariantData};
use crate::input::{
ErrorData, ErrorInput, Field, Fields, StructData, VariantData, is_option_type
};

pub fn expand(input: &ErrorInput) -> Result<Vec<TokenStream>, Error> {
let mut impls = Vec::new();
Expand Down Expand Up @@ -34,19 +36,7 @@ fn struct_from_impl(
let ty = &field.ty;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

let constructor = match &data.fields {
Fields::Named(_) => {
let field_ident = field.ident.clone().expect("named field");
quote! { Self { #field_ident: value } }
}
Fields::Unnamed(_) => quote! { Self(value) },
Fields::Unit => {
return Err(Error::new(
field.span,
"#[from] is not supported on unit structs"
));
}
};
let constructor = struct_constructor(&data.fields, field)?;

Ok(quote! {
impl #impl_generics core::convert::From<#ty> for #ident #ty_generics #where_clause {
Expand All @@ -67,19 +57,7 @@ fn enum_from_impl(
let variant_ident = &variant.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

let constructor = match &variant.fields {
Fields::Named(_) => {
let field_ident = field.ident.clone().expect("named field");
quote! { Self::#variant_ident { #field_ident: value } }
}
Fields::Unnamed(_) => quote! { Self::#variant_ident(value) },
Fields::Unit => {
return Err(Error::new(
field.span,
"#[from] is not supported on unit variants"
));
}
};
let constructor = variant_constructor(variant_ident, &variant.fields, field)?;

Ok(quote! {
impl #impl_generics core::convert::From<#ty> for #ident #ty_generics #where_clause {
Expand All @@ -89,3 +67,100 @@ fn enum_from_impl(
}
})
}

fn struct_constructor(fields: &Fields, from_field: &Field) -> Result<TokenStream, Error> {
match fields {
Fields::Named(named) => {
let mut initializers = Vec::new();
for field in named {
let field_ident = field.ident.clone().expect("named field");
let value = field_value_expr(field, from_field)?;
initializers.push(quote! { #field_ident: #value });
}
Ok(quote! { Self { #(#initializers),* } })
}
Fields::Unnamed(unnamed) => {
let mut values = Vec::new();
for field in unnamed {
values.push(field_value_expr(field, from_field)?);
}
Ok(quote! { Self(#(#values),*) })
}
Fields::Unit => Err(Error::new(
from_field.span,
"#[from] is not supported on unit structs"
))
}
}

fn variant_constructor(
variant_ident: &syn::Ident,
fields: &Fields,
from_field: &Field
) -> Result<TokenStream, Error> {
match fields {
Fields::Named(named) => {
let mut initializers = Vec::new();
for field in named {
let field_ident = field.ident.clone().expect("named field");
let value = field_value_expr(field, from_field)?;
initializers.push(quote! { #field_ident: #value });
}
Ok(quote! { Self::#variant_ident { #(#initializers),* } })
}
Fields::Unnamed(unnamed) => {
let mut values = Vec::new();
for field in unnamed {
values.push(field_value_expr(field, from_field)?);
}
Ok(quote! { Self::#variant_ident(#(#values),*) })
}
Fields::Unit => Err(Error::new(
from_field.span,
"#[from] is not supported on unit variants"
))
}
}

fn field_value_expr(field: &Field, from_field: &Field) -> Result<TokenStream, Error> {
if field.index == from_field.index {
return Ok(quote! { value });
}

if field.attrs.backtrace.is_some() {
return Ok(backtrace_initializer(field));
}

if field.attrs.source.is_some() && field.attrs.from.is_none() {
return source_initializer(field);
}

Err(Error::new(
field.span,
"deriving From requires no fields other than source and backtrace"
))
}

fn source_initializer(field: &Field) -> Result<TokenStream, Error> {
if is_option_type(&field.ty) {
Ok(quote! { ::core::option::Option::None })
} else {
Err(Error::new(
field.span,
"additional #[source] fields used with #[from] must be Option<_>"
))
}
}

fn backtrace_initializer(field: &Field) -> TokenStream {
let capture = quote! { ::std::backtrace::Backtrace::capture() };
if is_option_type(&field.ty) {
quote! {
::core::option::Option::Some(::core::convert::From::from(#capture))
}
} else {
quote! {
::core::convert::From::from(#capture)
}
}
}
27 changes: 24 additions & 3 deletions masterror-derive/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,30 @@ fn validate_from_usage(fields: &Fields, display: &DisplaySpec, errors: &mut Vec<
return;
}

if fields.len() > 1
&& let Some(attr) = &field.attrs.from
{
let mut has_unexpected_companions = false;
for companion in fields.iter() {
if companion.index == field.index {
continue;
}

if companion.attrs.backtrace.is_some() {
continue;
}

if let Some(attr) = &companion.attrs.source {
if companion.attrs.from.is_none() && !is_option_type(&companion.ty) {
errors.push(Error::new_spanned(
attr,
"additional #[source] fields used with #[from] must be Option<_>"
));
}
continue;
}

has_unexpected_companions = true;
}

if has_unexpected_companions && let Some(attr) = &field.attrs.from {
errors.push(Error::new_spanned(
attr,
"deriving From requires no fields other than source and backtrace"
Expand Down
48 changes: 48 additions & 0 deletions tests/error_derive.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(unused_variables, non_shorthand_field_patterns)]

use std::error::Error as StdError;

use masterror::Error;
Expand Down Expand Up @@ -105,6 +107,26 @@ enum TransparentEnum {
TransparentVariant(#[from] TransparentInner)
}

#[derive(Debug, Error)]
#[error("{source:?}")]
struct StructFromWithBacktrace {
#[from]
source: LeafError,
#[backtrace]
trace: Option<std::backtrace::Backtrace>
}

#[derive(Debug, Error)]
enum VariantFromWithBacktrace {
#[error("{source:?}")]
WithTrace {
#[from]
source: LeafError,
#[backtrace]
trace: Option<std::backtrace::Backtrace>
}
}

#[test]
fn named_struct_display_and_source() {
let err = NamedError {
Expand Down Expand Up @@ -220,3 +242,29 @@ fn transparent_enum_variant_from_impl() {
Some(String::from("leaf failure"))
);
}

#[test]
fn struct_from_with_backtrace_field_captures_trace() {
let err = StructFromWithBacktrace::from(LeafError);
assert!(err.trace.is_some());
assert_eq!(
StdError::source(&err).map(|err| err.to_string()),
Some(String::from("leaf failure"))
);
}

#[test]
fn enum_from_with_backtrace_field_captures_trace() {
let err = VariantFromWithBacktrace::from(LeafError);
match &err {
VariantFromWithBacktrace::WithTrace {
trace, ..
} => {
assert!(trace.is_some());
}
}
assert_eq!(
StdError::source(&err).map(|err| err.to_string()),
Some(String::from("leaf failure"))
);
}
2 changes: 2 additions & 0 deletions tests/ui/from/struct_multiple_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use masterror::Error;
struct BadStruct {
#[from]
left: DummyError,
#[backtrace]
trace: Option<std::backtrace::Backtrace>,
right: DummyError,
}

Expand Down
11 changes: 8 additions & 3 deletions tests/ui/from/variant_multiple_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ use masterror::Error;

#[derive(Debug, Error)]
enum BadEnum {
#[error("{0} - {1}")]
#[from]
Two(#[source] DummyError, DummyError),
#[error("{source:?} - {extra:?}")]
WithExtra {
#[from]
source: DummyError,
#[backtrace]
trace: Option<std::backtrace::Backtrace>,
extra: DummyError
}
}

#[derive(Debug, Error)]
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/from/variant_multiple_fields.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: not expected here; the #[from] attribute belongs on a specific field
--> tests/ui/from/variant_multiple_fields.rs:6:5
error: deriving From requires no fields other than source and backtrace
--> tests/ui/from/variant_multiple_fields.rs:7:9
|
6 | #[from]
| ^^^^^^^
7 | #[from]
| ^^^^^^^
Loading