Skip to content

Commit

Permalink
Initial derive-Error proc-macro implementation (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
ffuugoo authored and JelteF committed Nov 18, 2019
1 parent 79d5010 commit cde058f
Show file tree
Hide file tree
Showing 7 changed files with 1,662 additions and 138 deletions.
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ constructor = []
deref = []
deref_mut = []
display = []
error = []
from = []
from_str = []
index = []
Expand All @@ -70,6 +71,7 @@ default = [
"deref",
"deref_mut",
"display",
"error",
"from",
"from_str",
"index",
Expand Down Expand Up @@ -129,6 +131,11 @@ name = "display"
path = "tests/display.rs"
required-features = ["display"]

[[test]]
name = "error"
path = "tests/error.rs"
required-features = ["error"]

[[test]]
name = "from"
path = "tests/from.rs"
Expand Down
77 changes: 77 additions & 0 deletions doc/error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
% What #[derive(Error)] generates

Deriving `Error` will generate an `Error` implementation, with a `source`
method that matches `self` and each of its variants. In the case of a struct, only
a single variant is available. In the case of an enum, each of its variants is matched.

For each matched variant, a *source-field* is returned, if present. This field can
either be inferred, or explicitly specified via `#[error(source)]` attribute.

**For struct-variants** any field named `source` is inferred as a source-field.

**For tuple-variants** source-field is inferred only if its the only field in the variant.

Any field can be explicitly specified as a source-field via `#[error(source)]` attribute.
And any field, that would have been inferred as a source-field otherwise, can be
explicitly specified as a non-source-field via `#[error(not(source))]` attribute.

# Example usage

```rust
# #[macro_use] extern crate derive_more;

# use std::error::Error as _;

// std::error::Error requires std::fmt::Debug and std::fmt::Display,
// so we can also use derive_more::Display for fully declarative
// error-type definitions.

#[derive(Default, Debug, Display, Error)]
struct Simple;

#[derive(Default, Debug, Display, Error)]
struct WithSource {
source: Simple,
}

#[derive(Default, Debug, Display, Error)]
struct WithExplicitSource {
#[error(source)]
explicit_source: Simple,
}

#[derive(Default, Debug, Display, Error)]
struct Tuple(Simple);

#[derive(Default, Debug, Display, Error)]
struct WithoutSource(#[error(not(source))] i32);

// derive_more::From fits nicely into this pattern as well
#[derive(Debug, Display, Error, From)]
enum CompoundError {
Simple,
WithSource {
source: Simple,
},
WithExplicitSource {
#[error(source)]
explicit_source: WithSource,
},
Tuple(WithExplicitSource),
WithoutSource(#[error(not(source))] Tuple),
}

fn main() {
assert!(Simple.source().is_none());
assert!(WithSource::default().source().is_some());
assert!(WithExplicitSource::default().source().is_some());
assert!(Tuple::default().source().is_some());
assert!(WithoutSource::default().source().is_none());

assert!(CompoundError::Simple.source().is_none());
assert!(CompoundError::from(Simple).source().is_some());
assert!(CompoundError::from(WithSource::default()).source().is_some());
assert!(CompoundError::from(WithExplicitSource::default()).source().is_some());
assert!(CompoundError::from(Tuple::default()).source().is_none());
}
```
111 changes: 30 additions & 81 deletions src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@ use std::{
collections::{HashMap, HashSet},
fmt::Display,
iter::FromIterator as _,
ops::Deref as _,
str::FromStr as _,
};

use crate::utils::add_extra_where_clauses;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{
parse::{Error, Parser as _, Result},
punctuated::Punctuated,
spanned::Spanned as _,
parse::Parser as _, punctuated::Punctuated, spanned::Spanned as _, Error, Result,
};

/// Provides the hook to expand `#[derive(Display)]` into an implementation of `Display`
use crate::utils;

/// Provides the hook to expand `#[derive(Display)]` into an implementation of `From`
pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result<TokenStream> {
let trait_name = trait_name.trim_end_matches("Custom");
let trait_ident = syn::Ident::new(trait_name, Span::call_site());
Expand Down Expand Up @@ -51,7 +49,7 @@ pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result<TokenStream>
})
.collect();
let where_clause = quote_spanned!(input.span()=> where #(#bounds),*);
add_extra_where_clauses(&input.generics, where_clause)
utils::add_extra_where_clauses(&input.generics, where_clause)
} else {
input.generics.clone()
};
Expand Down Expand Up @@ -620,18 +618,19 @@ impl<'a, 'b> State<'a, 'b> {
.iter()
.enumerate()
.filter_map(|(i, field)| {
self.get_type_param(&field.ty).map(|ty| {
(
field
.ident
.clone()
.unwrap_or_else(|| {
Ident::new(&format!("_{}", i), Span::call_site())
})
.into(),
ty,
)
})
utils::get_if_type_parameter_used_in_type(&self.type_params, &field.ty)
.map(|ty| {
(
field
.ident
.clone()
.unwrap_or_else(|| {
Ident::new(&format!("_{}", i), Span::call_site())
})
.into(),
ty,
)
})
})
.collect();
if fields_type_params.is_empty() {
Expand Down Expand Up @@ -708,71 +707,21 @@ impl<'a, 'b> State<'a, 'b> {
.iter()
.take(1)
.filter_map(|field| {
self.get_type_param(&field.ty).map(|ty| {
(
ty,
[trait_name_to_trait_bound(attribute_name_to_trait_name(
self.trait_attr,
))]
.iter()
.cloned()
.collect(),
)
})
utils::get_if_type_parameter_used_in_type(&self.type_params, &field.ty)
.map(|ty| {
(
ty,
[trait_name_to_trait_bound(attribute_name_to_trait_name(
self.trait_attr,
))]
.iter()
.cloned()
.collect(),
)
})
})
.collect()
}
fn get_type_param(&self, ty: &syn::Type) -> Option<syn::Type> {
if self.has_type_param_in(ty) {
match ty {
syn::Type::Reference(syn::TypeReference { elem: ty, .. }) => {
Some(ty.deref().clone())
}
ty => Some(ty.clone()),
}
} else {
None
}
}
fn has_type_param_in(&self, ty: &syn::Type) -> bool {
match ty {
syn::Type::Path(ty) => {
if let Some(qself) = &ty.qself {
if self.has_type_param_in(&qself.ty) {
return true;
}
}

if let Some(segment) = ty.path.segments.first() {
if self.type_params.contains(&segment.ident) {
return true;
}
}

ty.path.segments.iter().any(|segment| {
if let syn::PathArguments::AngleBracketed(arguments) =
&segment.arguments
{
arguments.args.iter().any(|argument| match argument {
syn::GenericArgument::Type(ty) => {
self.has_type_param_in(ty)
}
syn::GenericArgument::Constraint(constraint) => {
self.type_params.contains(&constraint.ident)
}
_ => false,
})
} else {
false
}
})
}

syn::Type::Reference(ty) => self.has_type_param_in(&ty.elem),

_ => false,
}
}
}

/// Representation of formatting placeholder.
Expand Down
Loading

0 comments on commit cde058f

Please sign in to comment.