Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
//! Implementations of [`AsRef`]/[`AsMut`] derive macros.
pub(crate) mod r#mut;
pub(crate) mod r#ref;
use std::{borrow::Cow, iter};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_quote, spanned::Spanned, Token};
use crate::utils::{
attr::{self, ParseMultiple as _},
Either, GenericsSearch, Spanning,
};
/// Expands an [`AsRef`]/[`AsMut`] derive macro.
pub fn expand(
input: &syn::DeriveInput,
trait_info: ExpansionCtx<'_>,
) -> syn::Result<TokenStream> {
let (trait_ident, attr_name, _) = trait_info;
let data = match &input.data {
syn::Data::Struct(data) => Ok(data),
syn::Data::Enum(e) => Err(syn::Error::new(
e.enum_token.span(),
format!("`{trait_ident}` cannot be derived for enums"),
)),
syn::Data::Union(u) => Err(syn::Error::new(
u.union_token.span(),
format!("`{trait_ident}` cannot be derived for unions"),
)),
}?;
let expansions = if let Some(attr) =
StructAttribute::parse_attrs(&input.attrs, attr_name)?
{
if data.fields.len() != 1 {
return Err(syn::Error::new(
if data.fields.is_empty() {
data.struct_token.span
} else {
data.fields.span()
},
format!(
"`#[{attr_name}(...)]` attribute can only be placed on structs with exactly \
one field",
),
));
}
let field = data.fields.iter().next().unwrap();
if FieldAttribute::parse_attrs(&field.attrs, attr_name)?.is_some() {
return Err(syn::Error::new(
field.span(),
format!("`#[{attr_name}(...)]` cannot be placed on both struct and its field"),
));
}
vec![Expansion {
trait_info,
ident: &input.ident,
generics: &input.generics,
field,
field_index: 0,
conversions: Some(attr.into_inner()),
}]
} else {
let attrs = data
.fields
.iter()
.map(|field| FieldAttribute::parse_attrs(&field.attrs, attr_name))
.collect::<syn::Result<Vec<_>>>()?;
let present_attrs = attrs.iter().filter_map(Option::as_ref).collect::<Vec<_>>();
let all = present_attrs
.iter()
.all(|attr| matches!(attr.item, FieldAttribute::Skip(_)));
if !all {
if let Some(skip_attr) = present_attrs.iter().find_map(|attr| {
if let FieldAttribute::Skip(skip) = &attr.item {
Some(attr.as_ref().map(|_| skip))
} else {
None
}
}) {
return Err(syn::Error::new(
skip_attr.span(),
format!(
"`#[{attr_name}({})]` cannot be used in the same struct with other \
`#[{attr_name}(...)]` attributes",
skip_attr.name(),
),
));
}
}
if all {
data.fields
.iter()
.enumerate()
.zip(attrs)
.filter_map(|((i, field), attr)| {
attr.is_none().then_some(Expansion {
trait_info,
ident: &input.ident,
generics: &input.generics,
field,
field_index: i,
conversions: None,
})
})
.collect()
} else {
data.fields
.iter()
.enumerate()
.zip(attrs)
.filter_map(|((i, field), attr)| match attr.map(Spanning::into_inner) {
Some(
attr @ (FieldAttribute::Empty(_)
| FieldAttribute::Forward(_)
| FieldAttribute::Types(_)),
) => Some(Expansion {
trait_info,
ident: &input.ident,
generics: &input.generics,
field,
field_index: i,
conversions: attr.into(),
}),
Some(FieldAttribute::Skip(_)) => unreachable!(),
None => None,
})
.collect()
}
};
Ok(expansions
.into_iter()
.map(ToTokens::into_token_stream)
.collect())
}
/// Type alias for an expansion context:
/// - [`syn::Ident`] of the derived trait.
/// - [`syn::Ident`] of the derived trait method.
/// - Optional `mut` token indicating [`AsMut`] expansion.
///
/// [`syn::Ident`]: struct@syn::Ident
type ExpansionCtx<'a> = (&'a syn::Ident, &'a syn::Ident, Option<&'a Token![mut]>);
/// Expansion of a macro for generating [`AsRef`]/[`AsMut`] implementations for a single field of a
/// struct.
struct Expansion<'a> {
/// [`ExpansionCtx`] of the derived trait.
trait_info: ExpansionCtx<'a>,
/// [`syn::Ident`] of the struct.
///
/// [`syn::Ident`]: struct@syn::Ident
ident: &'a syn::Ident,
/// [`syn::Generics`] of the struct.
generics: &'a syn::Generics,
/// [`syn::Field`] of the struct.
field: &'a syn::Field,
/// Index of the [`syn::Field`].
field_index: usize,
/// Attribute specifying which conversions should be generated.
conversions: Option<attr::Conversion>,
}
impl ToTokens for Expansion<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let field_ty = &self.field.ty;
let field_ident = self.field.ident.as_ref().map_or_else(
|| Either::Right(syn::Index::from(self.field_index)),
Either::Left,
);
let (trait_ident, method_ident, mut_) = &self.trait_info;
let ty_ident = &self.ident;
let field_ref = quote! { & #mut_ self.#field_ident };
let generics_search = GenericsSearch::from(self.generics);
let field_contains_generics = generics_search.any_in(field_ty);
let is_blanket =
matches!(&self.conversions, Some(attr::Conversion::Forward(_)));
let return_tys = match &self.conversions {
Some(attr::Conversion::Forward(_)) => {
Either::Left(iter::once(Cow::Owned(parse_quote! { __AsT })))
}
Some(attr::Conversion::Types(tys)) => {
Either::Right(tys.0.iter().map(Cow::Borrowed))
}
None => Either::Left(iter::once(Cow::Borrowed(field_ty))),
};
for return_ty in return_tys {
/// Kind of a generated implementation, chosen based on attribute arguments.
enum ImplKind {
/// Returns a reference to a field.
Direct,
/// Forwards `as_ref`/`as_mut` call on a field.
Forwarded,
/// Uses autoref-based specialization to determine whether to use direct or
/// forwarded implementation, based on whether the field and the return type match.
///
/// Doesn't work when generics are involved.
Specialized,
}
let impl_kind = if is_blanket {
ImplKind::Forwarded
} else if field_ty == return_ty.as_ref() {
ImplKind::Direct
} else if field_contains_generics || generics_search.any_in(&return_ty) {
ImplKind::Forwarded
} else {
ImplKind::Specialized
};
let trait_ty = quote! {
derive_more::core::convert::#trait_ident <#return_ty>
};
let generics = match &impl_kind {
ImplKind::Forwarded => {
let mut generics = self.generics.clone();
generics
.make_where_clause()
.predicates
.push(parse_quote! { #field_ty: #trait_ty });
if is_blanket {
generics
.params
.push(parse_quote! { #return_ty: ?derive_more::core::marker::Sized });
}
Cow::Owned(generics)
}
ImplKind::Direct | ImplKind::Specialized => {
Cow::Borrowed(self.generics)
}
};
let (impl_gens, _, where_clause) = generics.split_for_impl();
let (_, ty_gens, _) = self.generics.split_for_impl();
let body = match &impl_kind {
ImplKind::Direct => Cow::Borrowed(&field_ref),
ImplKind::Forwarded => Cow::Owned(quote! {
<#field_ty as #trait_ty>::#method_ident(#field_ref)
}),
ImplKind::Specialized => Cow::Owned(quote! {
use derive_more::__private::ExtractRef as _;
let conv =
<derive_more::__private::Conv<& #mut_ #field_ty, #return_ty>
as derive_more::core::default::Default>::default();
(&&conv).__extract_ref(#field_ref)
}),
};
quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_gens #trait_ty for #ty_ident #ty_gens #where_clause {
#[inline]
fn #method_ident(& #mut_ self) -> & #mut_ #return_ty {
#body
}
}
}
.to_tokens(tokens);
}
}
}
/// Representation of an [`AsRef`]/[`AsMut`] derive macro struct container attribute.
///
/// ```rust,ignore
/// #[as_ref(forward)]
/// #[as_ref(<types>)]
/// ```
type StructAttribute = attr::Conversion;
/// Representation of an [`AsRef`]/[`AsMut`] derive macro field attribute.
///
/// ```rust,ignore
/// #[as_ref]
/// #[as_ref(skip)] #[as_ref(ignore)]
/// #[as_ref(forward)]
/// #[as_ref(<types>)]
/// ```
type FieldAttribute = attr::FieldConversion;
+17
View File
@@ -0,0 +1,17 @@
//! Implementation of an [`AsMut`] derive macro.
use proc_macro2::TokenStream;
use quote::format_ident;
use syn::Token;
/// Expands an [`AsMut`] derive macro.
pub(crate) fn expand(
input: &syn::DeriveInput,
trait_name: &'static str,
) -> syn::Result<TokenStream> {
let trait_ident = format_ident!("{trait_name}");
let method_ident = format_ident!("as_mut");
let mutability = <Token![mut]>::default();
super::expand(input, (&trait_ident, &method_ident, Some(&mutability)))
}
+15
View File
@@ -0,0 +1,15 @@
//! Implementation of an [`AsRef`] derive macro.
use proc_macro2::TokenStream;
use quote::format_ident;
/// Expands an [`AsRef`] derive macro.
pub(crate) fn expand(
input: &syn::DeriveInput,
trait_name: &'static str,
) -> syn::Result<TokenStream> {
let trait_ident = format_ident!("{trait_name}");
let method_ident = format_ident!("as_ref");
super::expand(input, (&trait_ident, &method_ident, None))
}
+142
View File
@@ -0,0 +1,142 @@
//! Implementation of an [`Eq`] derive macro.
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{parse_quote, spanned::Spanned as _};
use crate::utils::{
attr::{self, ParseMultiple as _},
structural_inclusion::TypeExt as _,
HashSet,
};
/// Expands an [`Eq`] derive macro.
pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
let attr_name = format_ident!("eq");
let secondary_attr_name = format_ident!("partial_eq");
let mut fields_types = HashSet::default();
match &input.data {
syn::Data::Struct(data) => {
let mut is_skipped = false;
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&input.attrs, attr_name)?.is_some() {
is_skipped = true;
break;
}
}
if !is_skipped {
'fields: for field in &data.fields {
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&field.attrs, attr_name)?.is_some() {
continue 'fields;
}
}
_ = fields_types.insert(&field.ty);
}
}
}
syn::Data::Enum(data) => {
'variants: for variant in &data.variants {
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&variant.attrs, attr_name)?.is_some() {
continue 'variants;
}
}
'fields: for field in &variant.fields {
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&field.attrs, attr_name)?.is_some() {
continue 'fields;
}
}
_ = fields_types.insert(&field.ty);
}
}
}
syn::Data::Union(data) => {
return Err(syn::Error::new(
data.union_token.span(),
"`Eq` cannot be derived for unions",
))
}
}
Ok(StructuralExpansion {
self_ty: (&input.ident, &input.generics),
fields_types,
}
.into_token_stream())
}
/// Expansion of a macro for generating a structural [`Eq`] implementation of an enum or a struct.
struct StructuralExpansion<'i> {
/// [`syn::Ident`] and [`syn::Generics`] of the enum/struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Type`]s of the enum/struct fields to be asserted for implementing [`Eq`].
fields_types: HashSet<&'i syn::Type>,
}
impl ToTokens for StructuralExpansion<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ty = self.self_ty.0;
let (_, ty_generics, _) = self.self_ty.1.split_for_impl();
let mut asserted_types = vec![];
let mut generics = self.self_ty.1.clone();
if !generics.params.is_empty() {
generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: derive_more::core::cmp::PartialEq });
}
{
let self_ty: syn::Type = parse_quote! { Self };
let implementor_ty: syn::Type = parse_quote! { #ty #ty_generics };
for field_ty in &self.fields_types {
if field_ty.contains_type_structurally(&self_ty)
|| field_ty.contains_type_structurally(&implementor_ty)
{
asserted_types.push(field_ty);
} else {
generics
.make_where_clause()
.predicates
.push(parse_quote! { #field_ty: derive_more::core::cmp::Eq });
}
}
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
// For the types containing the implementor type structurally, we should place a static
// assertion instead of generating a trait bound, because recursive trait bound will result
// in the `error[E0275]: overflow evaluating the requirement`.
// https://doc.rust-lang.org/stable/error_codes/E0275.html
let assert_eq_inherent_method = (!asserted_types.is_empty()).then(|| {
quote! {
#[allow(dead_code, private_bounds)]
#[automatically_derived]
#[doc(hidden)]
impl #impl_generics #ty #ty_generics #where_clause {
#[doc(hidden)]
const fn __derive_more_assert_eq() {
#(let _: derive_more::__private::AssertParamIsEq<#asserted_types>;)*
}
}
}
});
quote! {
#[allow(private_bounds)]
#[automatically_derived]
impl #impl_generics derive_more::core::cmp::Eq for #ty #ty_generics
#where_clause
{}
#assert_eq_inherent_method
}
.to_tokens(tokens);
}
}
+6
View File
@@ -0,0 +1,6 @@
//! Implementations of [`cmp`]-related derive macros.
//!
//! [`cmp`]: std::cmp
pub(crate) mod eq;
pub(crate) mod partial_eq;
@@ -0,0 +1,268 @@
//! Implementation of a [`PartialEq`] derive macro.
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{
parse_quote,
punctuated::{self, Punctuated},
spanned::Spanned as _,
};
use crate::utils::{
attr::{self, ParseMultiple as _},
pattern_matching::FieldsExt as _,
structural_inclusion::TypeExt as _,
GenericsSearch, HashSet,
};
/// Expands a [`PartialEq`] derive macro.
pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
let attr_name = format_ident!("partial_eq");
let secondary_attr_name = format_ident!("eq");
let mut has_skipped_variants = false;
let mut variants = vec![];
match &input.data {
syn::Data::Struct(data) => {
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&input.attrs, attr_name)?.is_some() {
has_skipped_variants = true;
break;
}
}
if !has_skipped_variants {
let mut skipped_fields = SkippedFields::default();
'fields: for (n, field) in data.fields.iter().enumerate() {
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&field.attrs, attr_name)?.is_some() {
_ = skipped_fields.insert(n);
continue 'fields;
}
}
}
variants.push((None, &data.fields, skipped_fields));
}
}
syn::Data::Enum(data) => {
'variants: for variant in &data.variants {
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&variant.attrs, attr_name)?.is_some() {
has_skipped_variants = true;
continue 'variants;
}
}
let mut skipped_fields = SkippedFields::default();
'fields: for (n, field) in variant.fields.iter().enumerate() {
for attr_name in [&attr_name, &secondary_attr_name] {
if attr::Skip::parse_attrs(&field.attrs, attr_name)?.is_some() {
_ = skipped_fields.insert(n);
continue 'fields;
}
}
}
variants.push((Some(&variant.ident), &variant.fields, skipped_fields));
}
}
syn::Data::Union(data) => {
return Err(syn::Error::new(
data.union_token.span(),
"`PartialEq` cannot be derived for unions",
))
}
}
Ok(StructuralExpansion {
self_ty: (&input.ident, &input.generics),
variants,
has_skipped_variants,
is_enum: matches!(input.data, syn::Data::Enum(_)),
}
.into_token_stream())
}
/// Indices of [`syn::Field`]s marked with an [`attr::Skip`].
type SkippedFields = HashSet<usize>;
/// Expansion of a macro for generating a structural [`PartialEq`] implementation of an enum or a
/// struct.
struct StructuralExpansion<'i> {
/// [`syn::Ident`] and [`syn::Generics`] of the enum/struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Fields`] of the enum/struct to be compared in this [`StructuralExpansion`].
variants: Vec<(Option<&'i syn::Ident>, &'i syn::Fields, SkippedFields)>,
/// Indicator whether some original enum variants where skipped with an [`attr::Skip`].
has_skipped_variants: bool,
/// Indicator whether this expansion is for an enum.
is_enum: bool,
}
impl StructuralExpansion<'_> {
/// Generates body of the [`PartialEq::eq()`]/[`PartialEq::ne()`] method implementation for this
/// [`StructuralExpansion`], if it's required.
fn body(&self, eq: bool) -> Option<TokenStream> {
// Special case: empty enum (also, no need for `ne()` method in this case).
if self.is_enum && self.variants.is_empty() && !self.has_skipped_variants {
return eq.then(|| quote! { match *self {} });
}
let no_fields_result = quote! { #eq };
// Special case: fully skipped struct (also, no need for `ne()` method in this case).
if !self.is_enum && self.variants.is_empty() && self.has_skipped_variants {
return eq.then_some(no_fields_result);
}
// Special case: no fields to compare in struct/single-variant enum (also, no need for
// `ne()` method in this case).
if !(self.is_enum && self.has_skipped_variants)
&& self.variants.len() == 1
&& (self.variants[0].1.is_empty()
|| self.variants[0].1.len() == self.variants[0].2.len())
{
return eq.then_some(no_fields_result);
}
let (cmp, chain) = if eq {
(quote! { == }, quote! { && })
} else {
(quote! { != }, quote! { || })
};
let discriminants_cmp = (self.variants.len() > 1 || self.has_skipped_variants)
.then(|| {
quote! {
derive_more::core::mem::discriminant(self) #cmp
derive_more::core::mem::discriminant(__other)
}
});
let match_arms = self
.variants
.iter()
.filter_map(|(variant, all_fields, skipped_fields)| {
if all_fields.is_empty() || skipped_fields.len() == all_fields.len() {
return None;
}
let variant = variant.map(|variant| quote! { :: #variant });
let self_pattern = all_fields
.non_exhaustive_arm_pattern("__self_", skipped_fields);
let other_pattern = all_fields
.non_exhaustive_arm_pattern("__other_", skipped_fields);
let mut val_eqs = (0..all_fields.len())
.filter(|num| !skipped_fields.contains(num))
.map(|num| {
let self_val = format_ident!("__self_{num}");
let other_val = format_ident!("__other_{num}");
punctuated::Pair::Punctuated(quote! { #self_val #cmp #other_val }, &chain)
})
.collect::<Punctuated<TokenStream, _>>();
_ = val_eqs.pop_punct();
Some(quote! {
(Self #variant #self_pattern, Self #variant #other_pattern) => { #val_eqs }
})
})
.collect::<Vec<_>>();
let match_expr = (!match_arms.is_empty()).then(|| {
let no_fields_arm = (match_arms.len() != self.variants.len()
|| self.has_skipped_variants)
.then(|| {
quote! { _ => #no_fields_result }
});
let unreachable_arm = (self.variants.len() > 1
&& no_fields_arm.is_none())
.then(|| {
quote! {
// SAFETY: This arm is never reachable due to `mem::discriminant()` comparison
// preceding the expanded `match (self, other)` expression, but is
// required by it when there is more than one variant.
_ => unsafe { derive_more::core::hint::unreachable_unchecked() },
}
});
quote! {
match (self, __other) {
#( #match_arms , )*
#no_fields_arm
#unreachable_arm
}
}
});
// If there is only `mem::discriminant()` comparison, there is no need to generate `ne()`
// method in the expansion, as its default implementation will do just fine.
if !eq && discriminants_cmp.is_some() && match_expr.is_none() {
return None;
}
let chain =
(discriminants_cmp.is_some() && match_expr.is_some()).then_some(chain);
Some(quote! {
#discriminants_cmp #chain #match_expr
})
}
}
impl ToTokens for StructuralExpansion<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let ty = self.self_ty.0;
let (_, ty_generics, _) = self.self_ty.1.split_for_impl();
let generics_search = GenericsSearch::from(self.self_ty.1);
let mut generics = self.self_ty.1.clone();
{
let self_ty: syn::Type = parse_quote! { Self };
let implementor_ty: syn::Type = parse_quote! { #ty #ty_generics };
for (_, all_fields, skipped_fields) in &self.variants {
for field_ty in
all_fields.iter().enumerate().filter_map(|(n, field)| {
(!skipped_fields.contains(&n)).then_some(&field.ty)
})
{
if generics_search.any_in(field_ty)
&& !field_ty.contains_type_structurally(&self_ty)
&& !field_ty.contains_type_structurally(&implementor_ty)
{
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty: derive_more::core::cmp::PartialEq
});
}
}
}
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let eq_method = self.body(true).map(|body| {
quote! {
#[inline]
fn eq(&self, __other: &Self) -> bool { #body }
}
});
let ne_method = self.body(false).map(|body| {
quote! {
#[inline]
fn ne(&self, __other: &Self) -> bool { #body }
}
});
quote! {
#[allow(private_bounds)]
#[automatically_derived]
impl #impl_generics derive_more::core::cmp::PartialEq for #ty #ty_generics
#where_clause
{
#eq_method
#ne_method
}
}
.to_tokens(tokens);
}
}
@@ -0,0 +1,60 @@
use crate::utils::{
field_idents, get_field_types, named_to_vec, numbered_vars, unnamed_to_vec,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Field, Fields, Ident};
/// Provides the hook to expand `#[derive(Constructor)]` into an implementation of `Constructor`
pub fn expand(input: &DeriveInput, _: &str) -> TokenStream {
let input_type = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let ((body, vars), fields) = match input.data {
Data::Struct(ref data_struct) => match data_struct.fields {
Fields::Unnamed(ref fields) => {
let field_vec = unnamed_to_vec(fields);
(tuple_body(input_type, &field_vec), field_vec)
}
Fields::Named(ref fields) => {
let field_vec = named_to_vec(fields);
(struct_body(input_type, &field_vec), field_vec)
}
Fields::Unit => (struct_body(input_type, &[]), vec![]),
},
_ => panic!("Only structs can derive a constructor"),
};
let inherited_lint_attrs = input.attrs.iter().filter(|attr| {
attr.path()
.get_ident()
.is_some_and(|name| name == "allow" || name == "expect")
});
let original_types = &get_field_types(&fields);
quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(missing_docs)]
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#(#inherited_lint_attrs)* // proxy-pass any `#[allow]`/`#[expect]` attributes
#[automatically_derived]
impl #impl_generics #input_type #ty_generics #where_clause {
#[inline]
pub const fn new(#(#vars: #original_types),*) -> #input_type #ty_generics {
#body
}
}
}
}
fn tuple_body(return_type: &Ident, fields: &[&Field]) -> (TokenStream, Vec<Ident>) {
let vars = &numbered_vars(fields.len(), "");
(quote! { #return_type(#(#vars),*) }, vars.clone())
}
fn struct_body(return_type: &Ident, fields: &[&Field]) -> (TokenStream, Vec<Ident>) {
let field_names: &Vec<Ident> =
&field_idents(fields).iter().map(|f| (**f).clone()).collect();
let vars = field_names;
let ret_vars = field_names.clone();
(quote! { #return_type{#(#field_names: #vars),*} }, ret_vars)
}
+140
View File
@@ -0,0 +1,140 @@
use crate::utils::{
add_extra_where_clauses, numbered_vars, panic_one_field, SingleFieldData, State,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::Result, Data, DeriveInput};
/// Provides the hook to expand `#[derive(Deref)]` into an implementation of `Deref`
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
match input.data {
Data::Struct(_) => expand_struct(input, trait_name),
Data::Enum(_) => expand_enum(input, trait_name),
_ => panic!("only structs and enums can use `derive({trait_name})`"),
}
}
fn expand_enum(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state = State::with_field_ignore_and_forward(
input,
trait_name,
trait_name.to_lowercase(),
)?;
let trait_path = &state.trait_path;
let enum_name = &input.ident;
let mut target = None;
let mut match_arms = vec![];
let mut predicates = vec![];
for variant_state in state.enabled_variant_data().variant_states.into_iter() {
let data = variant_state.enabled_fields_data();
if data.fields.len() != 1 {
panic_one_field(variant_state.trait_name, &variant_state.trait_attr);
};
let vars = numbered_vars(variant_state.fields.len(), "");
let matcher = data.matcher(&data.field_indexes, &vars);
let info = data.infos[0].clone();
let field_type = data.field_types[0];
let (target_, var) = if info.forward {
let casted_trait = data.casted_traits[0].clone();
predicates.push(quote! { #field_type: #trait_path });
(
quote! { #casted_trait::Target },
quote! { #casted_trait::deref(__0) },
)
} else {
(quote! { #field_type }, quote! { __0 })
};
if target.is_none() {
target = Some(target_);
}
match_arms.push(quote! {
#matcher => #var
});
}
let target = target.unwrap();
let generics = if predicates.is_empty() {
&input.generics
} else {
&add_extra_where_clauses(&input.generics, quote! { where #(#predicates),* })
};
let (imp_generics, type_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #imp_generics #trait_path for #enum_name #type_generics #where_clause {
type Target = #target;
#[inline]
fn deref(&self) -> &Self::Target {
match self {
#(#match_arms),*
}
}
}
})
}
fn expand_struct(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state = State::with_field_ignore_and_forward(
input,
trait_name,
trait_name.to_lowercase(),
)?;
let SingleFieldData {
input_type,
field_type,
trait_path,
casted_trait,
ty_generics,
member,
info,
..
} = state.assert_single_enabled_field();
let (target, body, generics) = if info.forward {
(
quote! { #casted_trait::Target },
quote! { #casted_trait::deref(&#member) },
add_extra_where_clauses(
&input.generics,
quote! {
where #field_type: #trait_path
},
),
)
} else {
(
quote! { #field_type },
quote! { &#member },
input.generics.clone(),
)
};
let (impl_generics, _, where_clause) = generics.split_for_impl();
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_generics #trait_path for #input_type #ty_generics #where_clause {
type Target = #target;
#[inline]
fn deref(&self) -> &Self::Target {
#body
}
}
})
}
+114
View File
@@ -0,0 +1,114 @@
use crate::utils::{
add_extra_where_clauses, numbered_vars, panic_one_field, SingleFieldData, State,
};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse::Result, Data, DeriveInput};
/// Provides the hook to expand `#[derive(DerefMut)]` into an implementation of `DerefMut`
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
match input.data {
Data::Struct(_) => expand_struct(input, trait_name),
Data::Enum(_) => expand_enum(input, trait_name),
_ => panic!("only structs and enums can use `derive({trait_name})`"),
}
}
fn expand_enum(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state =
State::with_field_ignore_and_forward(input, trait_name, "deref_mut".into())?;
let trait_path = &state.trait_path;
let enum_name = &input.ident;
let mut match_arms = vec![];
let mut predicates = vec![];
for variant_state in state.enabled_variant_data().variant_states.into_iter() {
let data = variant_state.enabled_fields_data();
if data.fields.len() != 1 {
panic_one_field(variant_state.trait_name, &variant_state.trait_attr);
};
let vars = numbered_vars(variant_state.fields.len(), "");
let matcher = data.matcher(&data.field_indexes, &vars);
let info = data.infos[0].clone();
let field_type = data.field_types[0];
let var = if info.forward {
let casted_trait = data.casted_traits[0].clone();
predicates.push(quote! { #field_type: #trait_path });
quote! { #casted_trait::deref_mut(__0) }
} else {
quote! { __0 }
};
match_arms.push(quote! {
#matcher => #var
});
}
let generics = if predicates.is_empty() {
&input.generics
} else {
&add_extra_where_clauses(&input.generics, quote! { where #(#predicates),* })
};
let (imp_generics, type_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #imp_generics #trait_path for #enum_name #type_generics #where_clause {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
#(#match_arms),*
}
}
}
})
}
fn expand_struct(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state =
State::with_field_ignore_and_forward(input, trait_name, "deref_mut".into())?;
let SingleFieldData {
input_type,
trait_path,
casted_trait,
ty_generics,
field_type,
member,
info,
..
} = state.assert_single_enabled_field();
let (body, generics) = if info.forward {
(
quote! { #casted_trait::deref_mut(&mut #member) },
add_extra_where_clauses(
&input.generics,
quote! {
where #field_type: #trait_path
},
),
)
} else {
(quote! { &mut #member }, input.generics.clone())
};
let (impl_generics, _, where_clause) = generics.split_for_impl();
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_generics #trait_path for #input_type #ty_generics #where_clause {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
#body
}
}
})
}
+561
View File
@@ -0,0 +1,561 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::{spanned::Spanned as _, Error, Result};
use crate::utils::{
self, AttrParams, DeriveType, FullMetaInfo, HashSet, MetaInfo, MultiFieldData,
State,
};
pub fn expand(
input: &syn::DeriveInput,
trait_name: &'static str,
) -> Result<TokenStream> {
let syn::DeriveInput {
ident, generics, ..
} = input;
let state = State::with_attr_params(
input,
trait_name,
trait_name.to_lowercase(),
allowed_attr_params(),
)?;
let type_params: HashSet<_> = generics
.params
.iter()
.filter_map(|generic| match generic {
syn::GenericParam::Type(ty) => Some(ty.ident.clone()),
_ => None,
})
.collect();
let (bounds, source, provide) = match state.derive_type {
DeriveType::Named | DeriveType::Unnamed => render_struct(&type_params, &state)?,
DeriveType::Enum => render_enum(&type_params, &state)?,
};
let source = source.map(|source| {
// Not using `#[inline]` here on purpose, since this is almost never part
// of a hot codepath.
quote! {
fn source(&self) -> Option<&(dyn derive_more::core::error::Error + 'static)> {
use derive_more::__private::AsDynError as _;
#source
}
}
});
let provide = provide.map(|provide| {
// Not using `#[inline]` here on purpose, since this is almost never part
// of a hot codepath.
quote! {
fn provide<'_request>(
&'_request self,
request: &mut derive_more::core::error::Request<'_request>,
) {
#provide
}
}
});
let mut generics = generics.clone();
if !type_params.is_empty() {
let (_, ty_generics, _) = generics.split_for_impl();
generics = utils::add_extra_where_clauses(
&generics,
quote! {
where
#ident #ty_generics: derive_more::core::fmt::Debug
+ derive_more::core::fmt::Display
},
);
}
if !bounds.is_empty() {
let bounds = bounds.iter();
generics = utils::add_extra_where_clauses(
&generics,
quote! {
where #(
#bounds: derive_more::core::fmt::Debug
+ derive_more::core::fmt::Display
+ derive_more::core::error::Error
+ 'static
),*
},
);
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let render = quote! {
#[automatically_derived]
impl #impl_generics derive_more::core::error::Error for #ident #ty_generics #where_clause {
#source
#provide
}
};
Ok(render)
}
fn render_struct(
type_params: &HashSet<syn::Ident>,
state: &State,
) -> Result<(HashSet<syn::Type>, Option<TokenStream>, Option<TokenStream>)> {
let parsed_fields = parse_fields(type_params, state)?;
let source = parsed_fields.render_source_as_struct();
let provide = cfg!(error_generic_member_access)
.then(|| parsed_fields.render_provide_as_struct())
.flatten();
Ok((parsed_fields.bounds, source, provide))
}
fn render_enum(
type_params: &HashSet<syn::Ident>,
state: &State,
) -> Result<(HashSet<syn::Type>, Option<TokenStream>, Option<TokenStream>)> {
let mut bounds = HashSet::default();
let mut source_match_arms = Vec::new();
let mut provide_match_arms = Vec::new();
for variant in state.enabled_variant_data().variants {
let default_info = FullMetaInfo {
enabled: true,
..FullMetaInfo::default()
};
let state = State::from_variant(
state.input,
state.trait_name,
state.trait_attr.clone(),
allowed_attr_params(),
variant,
default_info,
)?;
let parsed_fields = parse_fields(type_params, &state)?;
if let Some(expr) = parsed_fields.render_source_as_enum_variant_match_arm() {
source_match_arms.push(expr);
}
if let Some(expr) = parsed_fields.render_provide_as_enum_variant_match_arm() {
provide_match_arms.push(expr);
}
bounds.extend(parsed_fields.bounds.into_iter());
}
let render = |match_arms: &mut Vec<TokenStream>, unmatched| {
if !match_arms.is_empty() && match_arms.len() < state.variants.len() {
match_arms.push(quote! { _ => #unmatched });
}
(!match_arms.is_empty()).then(|| {
quote! {
match self {
#(#match_arms),*
}
}
})
};
let source = render(&mut source_match_arms, quote! { None });
let provide = render(&mut provide_match_arms, quote! { () });
Ok((bounds, source, provide))
}
fn allowed_attr_params() -> AttrParams {
AttrParams {
enum_: vec!["ignore"],
struct_: vec!["ignore"],
variant: vec!["ignore"],
field: vec!["ignore", "source", "optional", "backtrace"],
}
}
struct ParsedFields<'input, 'state> {
data: MultiFieldData<'input, 'state>,
source: Option<usize>,
backtrace: Option<usize>,
bounds: HashSet<syn::Type>,
}
impl<'input, 'state> ParsedFields<'input, 'state> {
fn new(data: MultiFieldData<'input, 'state>) -> Self {
Self {
data,
source: None,
backtrace: None,
bounds: HashSet::default(),
}
}
}
impl ParsedFields<'_, '_> {
fn render_source_as_struct(&self) -> Option<TokenStream> {
let source = self.source?;
let ident = &self.data.members[source];
let is_optional = self.data.infos[source].info.source_optional == Some(true)
|| self.data.field_types[source].is_option();
Some(render_some(quote! { (&#ident) }, is_optional))
}
fn render_source_as_enum_variant_match_arm(&self) -> Option<TokenStream> {
let source = self.source?;
let pattern = self.data.matcher(&[source], &[quote! { source }]);
let is_optional = self.data.infos[source].info.source_optional == Some(true)
|| self.data.field_types[source].is_option();
let expr = render_some(quote! { source }, is_optional);
Some(quote! { #pattern => #expr })
}
fn render_provide_as_struct(&self) -> Option<TokenStream> {
let backtrace = self.backtrace?;
let source_provider = self.source.map(|source| {
let source_expr = &self.data.members[source];
quote! {
derive_more::core::error::Error::provide(&#source_expr, request);
}
});
let backtrace_provider = self
.source
.filter(|source| *source == backtrace)
.is_none()
.then(|| {
let backtrace_expr = &self.data.members[backtrace];
quote! {
request.provide_ref::<::std::backtrace::Backtrace>(&#backtrace_expr);
}
});
(source_provider.is_some() || backtrace_provider.is_some()).then(|| {
quote! {
#backtrace_provider
#source_provider
}
})
}
fn render_provide_as_enum_variant_match_arm(&self) -> Option<TokenStream> {
let backtrace = self.backtrace?;
match self.source {
Some(source) if source == backtrace => {
let pattern = self.data.matcher(&[source], &[quote! { source }]);
Some(quote! {
#pattern => {
derive_more::core::error::Error::provide(source, request);
}
})
}
Some(source) => {
let pattern = self.data.matcher(
&[source, backtrace],
&[quote! { source }, quote! { backtrace }],
);
Some(quote! {
#pattern => {
request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
derive_more::core::error::Error::provide(source, request);
}
})
}
None => {
let pattern = self.data.matcher(&[backtrace], &[quote! { backtrace }]);
Some(quote! {
#pattern => {
request.provide_ref::<::std::backtrace::Backtrace>(backtrace);
}
})
}
}
}
}
fn render_some(mut expr: TokenStream, unpack: bool) -> TokenStream {
if unpack {
expr = quote! { derive_more::core::option::Option::as_ref(#expr)? }
}
quote! { Some(#expr.__derive_more_as_dyn_error()) }
}
fn parse_fields<'input, 'state>(
type_params: &HashSet<syn::Ident>,
state: &'state State<'input>,
) -> Result<ParsedFields<'input, 'state>> {
let mut parsed_fields = match state.derive_type {
DeriveType::Named => {
parse_fields_impl(state, |attr, field, _| {
// Unwrapping is safe, cause fields in named struct
// always have an ident
let ident = field.ident.as_ref().unwrap();
match attr {
"source" => ident == "source",
"backtrace" => {
ident == "backtrace"
|| is_type_path_ends_with_segment(&field.ty, "Backtrace")
}
_ => unreachable!(),
}
})
}
DeriveType::Unnamed => {
let mut parsed_fields =
parse_fields_impl(state, |attr, field, len| match attr {
"source" => {
len == 1
&& !is_type_path_ends_with_segment(&field.ty, "Backtrace")
}
"backtrace" => {
is_type_path_ends_with_segment(&field.ty, "Backtrace")
}
_ => unreachable!(),
})?;
parsed_fields.source = parsed_fields
.source
.or_else(|| infer_source_field(&state.fields, &parsed_fields));
Ok(parsed_fields)
}
_ => unreachable!(),
}?;
if let Some(source) = parsed_fields.source {
let is_optional = parsed_fields.data.infos[source].info.source_optional
== Some(true)
|| state.fields[source].ty.is_option();
add_bound_if_type_parameter_used_in_type(
&mut parsed_fields.bounds,
type_params,
&state.fields[source].ty,
is_optional,
);
}
Ok(parsed_fields)
}
/// Checks if `ty` is [`syn::Type::Path`] and ends with segment matching `tail`
/// and doesn't contain any generic parameters.
fn is_type_path_ends_with_segment(ty: &syn::Type, tail: &str) -> bool {
let syn::Type::Path(ty) = ty else {
return false;
};
// Unwrapping is safe, cause 'syn::TypePath.path.segments'
// have to have at least one segment
let segment = ty.path.segments.last().unwrap();
if !matches!(segment.arguments, syn::PathArguments::None) {
return false;
}
segment.ident == tail
}
fn infer_source_field(
fields: &[&syn::Field],
parsed_fields: &ParsedFields,
) -> Option<usize> {
// if we have exactly two fields
if fields.len() != 2 {
return None;
}
// no source field was specified/inferred
if parsed_fields.source.is_some() {
return None;
}
// but one of the fields was specified/inferred as backtrace field
if let Some(backtrace) = parsed_fields.backtrace {
// then infer *other field* as source field
let source = (backtrace + 1) % 2;
// unless it was explicitly marked as non-source
if parsed_fields.data.infos[source].info.source != Some(false) {
return Some(source);
}
}
None
}
fn parse_fields_impl<'input, 'state, P>(
state: &'state State<'input>,
is_valid_default_field_for_attr: P,
) -> Result<ParsedFields<'input, 'state>>
where
P: Fn(&str, &syn::Field, usize) -> bool,
{
let MultiFieldData { fields, infos, .. } = state.enabled_fields_data();
let iter = fields
.iter()
.zip(infos.iter().map(|info| &info.info))
.enumerate()
.map(|(index, (field, info))| (index, *field, info));
let source = parse_field_impl(
&is_valid_default_field_for_attr,
state.fields.len(),
iter.clone(),
"source",
|info| info.source,
)?;
let backtrace = parse_field_impl(
&is_valid_default_field_for_attr,
state.fields.len(),
iter.clone(),
"backtrace",
|info| info.backtrace,
)?;
let mut parsed_fields = ParsedFields::new(state.enabled_fields_data());
if let Some((index, _, _)) = source {
parsed_fields.source = Some(index);
}
if let Some((index, _, _)) = backtrace {
parsed_fields.backtrace = Some(index);
}
Ok(parsed_fields)
}
fn parse_field_impl<'a, P, V>(
is_valid_default_field_for_attr: &P,
len: usize,
iter: impl Iterator<Item = (usize, &'a syn::Field, &'a MetaInfo)> + Clone,
attr: &str,
value: V,
) -> Result<Option<(usize, &'a syn::Field, &'a MetaInfo)>>
where
P: Fn(&str, &syn::Field, usize) -> bool,
V: Fn(&MetaInfo) -> Option<bool>,
{
let explicit_fields = iter
.clone()
.filter(|(_, _, info)| matches!(value(info), Some(true)));
let inferred_fields = iter.filter(|(_, field, info)| match value(info) {
None => is_valid_default_field_for_attr(attr, field, len),
_ => false,
});
let field = assert_iter_contains_zero_or_one_item(
explicit_fields,
&format!(
"Multiple `{attr}` attributes specified. \
Single attribute per struct/enum variant allowed.",
),
)?;
let field = match field {
field @ Some(_) => field,
None => assert_iter_contains_zero_or_one_item(
inferred_fields,
"Conflicting fields found. Consider specifying some \
`#[error(...)]` attributes to resolve conflict.",
)?,
};
Ok(field)
}
fn assert_iter_contains_zero_or_one_item<'a>(
mut iter: impl Iterator<Item = (usize, &'a syn::Field, &'a MetaInfo)>,
error_msg: &str,
) -> Result<Option<(usize, &'a syn::Field, &'a MetaInfo)>> {
let Some(item) = iter.next() else {
return Ok(None);
};
if let Some((_, field, _)) = iter.next() {
return Err(Error::new(field.span(), error_msg));
}
Ok(Some(item))
}
fn add_bound_if_type_parameter_used_in_type(
bounds: &mut HashSet<syn::Type>,
type_params: &HashSet<syn::Ident>,
ty: &syn::Type,
unpack: bool,
) {
if let Some(ty) = utils::get_if_type_parameter_used_in_type(type_params, ty) {
bounds.insert(
unpack
.then(|| ty.get_inner())
.flatten()
.cloned()
.unwrap_or(ty),
);
}
}
/// Extension of a [`syn::Type`] used by this expansion.
trait TypeExt {
/// Checks syntactically whether this [`syn::Type`] represents an [`Option`].
fn is_option(&self) -> bool;
/// Returns the inner [`syn::Type`] if this one represents a wrapper.
///
/// `filter` filters out this [`syn::Type`] by its name.
fn get_inner_if(&self, filter: impl Fn(&syn::Ident) -> bool) -> Option<&Self>;
/// Returns the inner [`syn::Type`] if this one represents a wrapper.
fn get_inner(&self) -> Option<&Self> {
self.get_inner_if(|_| true)
}
/// Returns the inner [`syn::Type`] if this one represents an [`Option`].
fn get_option_inner(&self) -> Option<&Self> {
self.get_inner_if(|ident| ident == "Option")
}
}
impl TypeExt for syn::Type {
fn is_option(&self) -> bool {
self.get_option_inner().is_some()
}
fn get_inner_if(&self, filter: impl Fn(&syn::Ident) -> bool) -> Option<&Self> {
match self {
Self::Group(g) => g.elem.get_option_inner(),
Self::Paren(p) => p.elem.get_option_inner(),
Self::Path(p) => p
.path
.segments
.last()
.filter(|s| filter(&s.ident))
.and_then(|s| {
if let syn::PathArguments::AngleBracketed(a) = &s.arguments {
if let Some(syn::GenericArgument::Type(ty)) = a.args.first() {
return Some(ty);
}
}
None
}),
_ => None,
}
}
}
+420
View File
@@ -0,0 +1,420 @@
//! Implementation of a [`fmt::Debug`] derive macro.
//!
//! [`fmt::Debug`]: std::fmt::Debug
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned as _};
use crate::utils::{
attr::{self, ParseMultiple as _},
Either, Spanning,
};
use super::{
trait_name_to_attribute_name, ContainerAttributes, ContainsGenericsExt as _,
FmtAttribute,
};
/// Expands a [`fmt::Debug`] derive macro.
///
/// [`fmt::Debug`]: std::fmt::Debug
pub fn expand(input: &syn::DeriveInput, _: &str) -> syn::Result<TokenStream> {
let attr_name = format_ident!("{}", trait_name_to_attribute_name("Debug"));
let attrs = ContainerAttributes::parse_attrs(&input.attrs, &attr_name)?
.map(Spanning::into_inner)
.unwrap_or_default();
let ident = &input.ident;
let type_params = input
.generics
.params
.iter()
.filter_map(|p| match p {
syn::GenericParam::Type(t) => Some(&t.ident),
syn::GenericParam::Const(..) | syn::GenericParam::Lifetime(..) => None,
})
.collect::<Vec<_>>();
let (bounds, body) = match &input.data {
syn::Data::Struct(s) => {
expand_struct(attrs, ident, s, &type_params, &attr_name)
}
syn::Data::Enum(e) => expand_enum(attrs, e, &type_params, &attr_name),
syn::Data::Union(_) => {
return Err(syn::Error::new(
input.span(),
"`Debug` cannot be derived for unions",
));
}
}?;
let (impl_gens, ty_gens, where_clause) = {
let (impl_gens, ty_gens, where_clause) = input.generics.split_for_impl();
let mut where_clause = where_clause
.cloned()
.unwrap_or_else(|| parse_quote! { where });
where_clause.predicates.extend(bounds);
(impl_gens, ty_gens, where_clause)
};
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_gens derive_more::core::fmt::Debug for #ident #ty_gens #where_clause {
#[inline]
fn fmt(
&self, __derive_more_f: &mut derive_more::core::fmt::Formatter<'_>
) -> derive_more::core::fmt::Result {
#body
}
}
})
}
/// Expands a [`fmt::Debug`] derive macro for the provided struct.
///
/// [`fmt::Debug`]: std::fmt::Debug
fn expand_struct(
attrs: ContainerAttributes,
ident: &syn::Ident,
s: &syn::DataStruct,
type_params: &[&syn::Ident],
attr_name: &syn::Ident,
) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
let s = Expansion {
attr: &attrs,
fields: &s.fields,
type_params,
ident,
attr_name,
};
s.validate_attrs()?;
let bounds = s.generate_bounds()?;
let body = s.generate_body()?;
let vars = s.fields.iter().enumerate().map(|(i, f)| {
let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"));
let member = f
.ident
.clone()
.map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named);
quote! { let #var = &self.#member; }
});
let body = quote! {
#( #vars )*
#body
};
Ok((bounds, body))
}
/// Expands a [`fmt::Debug`] derive macro for the provided enum.
///
/// [`fmt::Debug`]: std::fmt::Debug
fn expand_enum(
mut attrs: ContainerAttributes,
e: &syn::DataEnum,
type_params: &[&syn::Ident],
attr_name: &syn::Ident,
) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
if let Some(enum_fmt) = attrs.fmt.as_ref() {
return Err(syn::Error::new_spanned(
enum_fmt,
format!(
"`#[{attr_name}(\"...\", ...)]` attribute is not allowed on enum, place it on its \
variants instead",
),
));
}
let (bounds, match_arms) = e.variants.iter().try_fold(
(Vec::new(), TokenStream::new()),
|(mut bounds, mut arms), variant| {
let ident = &variant.ident;
attrs.fmt = variant
.attrs
.iter()
.filter(|attr| attr.path().is_ident("debug"))
.try_fold(None, |mut attrs, attr| {
let attr = attr.parse_args::<FmtAttribute>()?;
attrs.replace(attr).map_or(Ok(()), |dup| {
Err(syn::Error::new(
dup.span(),
format!(
"multiple `#[{attr_name}(\"...\", ...)]` attributes aren't allowed",
),
))
})?;
Ok::<_, syn::Error>(attrs)
})?;
let v = Expansion {
attr: &attrs,
fields: &variant.fields,
type_params,
ident,
attr_name,
};
v.validate_attrs()?;
let arm_body = v.generate_body()?;
bounds.extend(v.generate_bounds()?);
let fields_idents =
variant.fields.iter().enumerate().map(|(i, f)| {
f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"))
});
let matcher = match variant.fields {
syn::Fields::Named(_) => {
quote! { Self::#ident { #( #fields_idents ),* } }
}
syn::Fields::Unnamed(_) => {
quote! { Self::#ident ( #( #fields_idents ),* ) }
}
syn::Fields::Unit => quote! { Self::#ident },
};
arms.extend([quote! { #matcher => { #arm_body }, }]);
Ok::<_, syn::Error>((bounds, arms))
},
)?;
let body = if match_arms.is_empty() {
quote! { match *self {} }
} else {
quote! { match self { #match_arms } }
};
Ok((bounds, body))
}
/// Representation of a [`fmt::Debug`] derive macro field attribute.
///
/// ```rust,ignore
/// #[debug(skip)]
/// #[debug("<fmt-literal>", <fmt-args>)]
/// ```
///
/// [`fmt::Debug`]: std::fmt::Debug
type FieldAttribute = Either<attr::Skip, FmtAttribute>;
/// Helper struct to generate [`Debug::fmt()`] implementation body and trait
/// bounds for a struct or an enum variant.
///
/// [`Debug::fmt()`]: std::fmt::Debug::fmt()
#[derive(Debug)]
struct Expansion<'a> {
attr: &'a ContainerAttributes,
/// Struct or enum [`Ident`](struct@syn::Ident).
ident: &'a syn::Ident,
/// Struct or enum [`syn::Fields`].
fields: &'a syn::Fields,
/// Type parameters in this struct or enum.
type_params: &'a [&'a syn::Ident],
/// Name of the attributes, considered by this macro.
attr_name: &'a syn::Ident,
}
impl Expansion<'_> {
/// Validates attributes of this [`Expansion`] to be consistent.
fn validate_attrs(&self) -> syn::Result<()> {
if self.attr.fmt.is_some() {
for field_attr in self
.fields
.iter()
.map(|f| FieldAttribute::parse_attrs(&f.attrs, self.attr_name))
{
if let Some(FieldAttribute::Right(fmt_attr)) =
field_attr?.map(Spanning::into_inner)
{
return Err(syn::Error::new_spanned(
fmt_attr,
"`#[debug(...)]` attributes are not allowed on fields when \
`#[debug(\"...\", ...)]` is specified on struct or variant",
));
}
}
}
Ok(())
}
/// Generates [`Debug::fmt()`] implementation for a struct or an enum variant.
///
/// [`Debug::fmt()`]: std::fmt::Debug::fmt()
fn generate_body(&self) -> syn::Result<TokenStream> {
if let Some(fmt) = &self.attr.fmt {
return Ok(
if let Some((expr, trait_ident)) =
fmt.transparent_call_on_fields(self.fields)
{
quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
} else {
let deref_args = fmt.additional_deref_args(self.fields);
quote! { derive_more::core::write!(__derive_more_f, #fmt, #(#deref_args),*) }
},
);
};
match self.fields {
syn::Fields::Unit => {
let ident = self.ident.to_string();
Ok(quote! {
derive_more::core::fmt::Formatter::write_str(
__derive_more_f,
#ident,
)
})
}
syn::Fields::Unnamed(unnamed) => {
let mut exhaustive = true;
let ident_str = self.ident.to_string();
let out = quote! {
&mut derive_more::__private::debug_tuple(
__derive_more_f,
#ident_str,
)
};
let out = unnamed.unnamed.iter().enumerate().try_fold(
out,
|out, (i, field)| match FieldAttribute::parse_attrs(
&field.attrs,
self.attr_name,
)?
.map(Spanning::into_inner)
{
Some(FieldAttribute::Left(_skip)) => {
exhaustive = false;
Ok::<_, syn::Error>(out)
}
Some(FieldAttribute::Right(fmt_attr)) => {
let deref_args = fmt_attr.additional_deref_args(self.fields);
Ok(quote! {
derive_more::__private::DebugTuple::field(
#out,
&derive_more::core::format_args!(#fmt_attr, #(#deref_args),*),
)
})
}
None => {
let ident = format_ident!("_{i}");
Ok(quote! {
derive_more::__private::DebugTuple::field(#out, &#ident)
})
}
},
)?;
Ok(if exhaustive {
quote! { derive_more::__private::DebugTuple::finish(#out) }
} else {
quote! { derive_more::__private::DebugTuple::finish_non_exhaustive(#out) }
})
}
syn::Fields::Named(named) => {
let mut exhaustive = true;
let ident = self.ident.to_string();
let out = quote! {
&mut derive_more::core::fmt::Formatter::debug_struct(
__derive_more_f,
#ident,
)
};
let out = named.named.iter().try_fold(out, |out, field| {
let field_ident = field.ident.as_ref().unwrap_or_else(|| {
unreachable!("`syn::Fields::Named`");
});
let field_str = field_ident.unraw().to_string();
match FieldAttribute::parse_attrs(&field.attrs, self.attr_name)?
.map(Spanning::into_inner)
{
Some(FieldAttribute::Left(_skip)) => {
exhaustive = false;
Ok::<_, syn::Error>(out)
}
Some(FieldAttribute::Right(fmt_attr)) => {
let deref_args =
fmt_attr.additional_deref_args(self.fields);
Ok(quote! {
derive_more::core::fmt::DebugStruct::field(
#out,
#field_str,
&derive_more::core::format_args!(
#fmt_attr, #(#deref_args),*
),
)
})
}
None => Ok(quote! {
derive_more::core::fmt::DebugStruct::field(
#out, #field_str, &#field_ident
)
}),
}
})?;
Ok(if exhaustive {
quote! { derive_more::core::fmt::DebugStruct::finish(#out) }
} else {
quote! { derive_more::core::fmt::DebugStruct::finish_non_exhaustive(#out) }
})
}
}
}
/// Generates trait bounds for a struct or an enum variant.
fn generate_bounds(&self) -> syn::Result<Vec<syn::WherePredicate>> {
let mut out = self.attr.bounds.0.clone().into_iter().collect::<Vec<_>>();
if let Some(fmt) = self.attr.fmt.as_ref() {
out.extend(fmt.bounded_types(self.fields).filter_map(
|(ty, trait_name)| {
if !ty.contains_generics(self.type_params) {
return None;
}
let trait_ident = format_ident!("{trait_name}");
Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
},
));
Ok(out)
} else {
self.fields.iter().try_fold(out, |mut out, field| {
let ty = &field.ty;
if !ty.contains_generics(self.type_params) {
return Ok(out);
}
match FieldAttribute::parse_attrs(&field.attrs, self.attr_name)?
.map(Spanning::into_inner)
{
Some(FieldAttribute::Right(fmt_attr)) => {
out.extend(fmt_attr.bounded_types(self.fields).map(
|(ty, trait_name)| {
let trait_ident = format_ident!("{trait_name}");
parse_quote! { #ty: derive_more::core::fmt::#trait_ident }
},
));
}
Some(FieldAttribute::Left(_skip)) => {}
None => out.extend([parse_quote! { #ty: derive_more::core::fmt::Debug }]),
}
Ok(out)
})
}
}
}
+585
View File
@@ -0,0 +1,585 @@
//! Implementation of [`fmt::Display`]-like derive macros.
#[cfg(doc)]
use std::fmt;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
ext::IdentExt as _,
parse::{Parse, ParseStream},
parse_quote,
spanned::Spanned as _,
token, LitStr,
};
use crate::utils::{
attr::{self, ParseMultiple as _},
Spanning,
};
use super::{trait_name_to_attribute_name, ContainsGenericsExt as _, FmtAttribute};
/// Expands a [`fmt::Display`]-like derive macro.
///
/// Available macros:
/// - [`Binary`](fmt::Binary)
/// - [`Display`](fmt::Display)
/// - [`LowerExp`](fmt::LowerExp)
/// - [`LowerHex`](fmt::LowerHex)
/// - [`Octal`](fmt::Octal)
/// - [`Pointer`](fmt::Pointer)
/// - [`UpperExp`](fmt::UpperExp)
/// - [`UpperHex`](fmt::UpperHex)
pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
let trait_name = normalize_trait_name(trait_name);
let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
let attrs = ContainerAttributes::parse_attrs(&input.attrs, &attr_name)?
.map(Spanning::into_inner)
.unwrap_or_default();
let trait_ident = format_ident!("{trait_name}");
let ident = &input.ident;
let type_params = input
.generics
.params
.iter()
.filter_map(|p| match p {
syn::GenericParam::Type(t) => Some(&t.ident),
syn::GenericParam::Const(..) | syn::GenericParam::Lifetime(..) => None,
})
.collect::<Vec<_>>();
let ctx: ExpansionCtx = (&attrs, &type_params, ident, &trait_ident, &attr_name);
let (bounds, body) = match &input.data {
syn::Data::Struct(s) => expand_struct(s, ctx),
syn::Data::Enum(e) => expand_enum(e, ctx),
syn::Data::Union(u) => expand_union(u, ctx),
}?;
let (impl_gens, ty_gens, where_clause) = {
let (impl_gens, ty_gens, where_clause) = input.generics.split_for_impl();
let mut where_clause = where_clause
.cloned()
.unwrap_or_else(|| parse_quote! { where });
where_clause.predicates.extend(bounds);
(impl_gens, ty_gens, where_clause)
};
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_gens derive_more::core::fmt::#trait_ident for #ident #ty_gens #where_clause {
fn fmt(
&self, __derive_more_f: &mut derive_more::core::fmt::Formatter<'_>
) -> derive_more::core::fmt::Result {
#body
}
}
})
}
/// Representation of possible [`fmt::Display`]-like derive macro attributes placed on a container
/// (struct or enum variant).
///
/// ```rust,ignore
/// #[<attribute>("<fmt-literal>", <fmt-args>)]
/// #[<attribute>(bound(<where-predicates>))]
/// #[<attribute>(rename_all = "<casing>")]
/// ```
///
/// `#[<attribute>("...")]` and `#[<attribute>(rename_all = "...")]` can be specified only once,
/// while multiple `#[<attribute>(bound(...))]` are allowed.
#[derive(Debug, Default)]
struct ContainerAttributes {
/// [`attr::RenameAll`] for case conversion.
rename_all: Option<attr::RenameAll>,
/// Common [`ContainerAttributes`].
///
/// [`ContainerAttributes`]: super::ContainerAttributes
common: super::ContainerAttributes,
}
impl Parse for ContainerAttributes {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
mod ident {
use syn::custom_keyword;
custom_keyword!(bounds);
custom_keyword!(bound);
custom_keyword!(rename_all);
}
// We do check `FmtAttribute::check_legacy_fmt` eagerly here, because `.lookahead1()` won't
// check for the `fmt =` ident below, to omit including it into the `ahead.error()`.
FmtAttribute::check_legacy_fmt(input)?;
// We use `.lookahead1()` with all possible idents to form a nice error message including
// all the possible variants.
let ahead = input.lookahead1();
if ahead.peek(LitStr)
|| ahead.peek(ident::bounds)
|| ahead.peek(ident::bound)
|| ahead.peek(token::Where)
{
Ok(Self {
common: input.parse()?,
..Default::default()
})
} else if ahead.peek(ident::rename_all) {
Ok(Self {
rename_all: Some(input.parse()?),
..Self::default()
})
} else {
Err(ahead.error())
}
}
}
impl attr::ParseMultiple for ContainerAttributes {
fn merge_attrs(
prev: Spanning<Self>,
new: Spanning<Self>,
name: &syn::Ident,
) -> syn::Result<Spanning<Self>> {
let Spanning {
span: prev_span,
item: mut prev,
} = prev;
let Spanning {
span: new_span,
item: new,
} = new;
if new
.rename_all
.and_then(|n| prev.rename_all.replace(n))
.is_some()
{
return Err(syn::Error::new(
new_span,
format!("multiple `#[{name}(rename_all=\"...\")]` attributes aren't allowed"),
));
}
prev.common = super::ContainerAttributes::merge_attrs(
Spanning::new(prev.common, prev_span),
Spanning::new(new.common, new_span),
name,
)?
.into_inner();
Ok(Spanning::new(
prev,
prev_span.join(new_span).unwrap_or(prev_span),
))
}
}
/// Type alias for an expansion context:
/// - [`ContainerAttributes`].
/// - Type parameters. Slice of [`syn::Ident`].
/// - Struct/enum/union [`syn::Ident`].
/// - Derived trait [`syn::Ident`].
/// - Attribute name [`syn::Ident`].
///
/// [`syn::Ident`]: struct@syn::Ident
type ExpansionCtx<'a> = (
&'a ContainerAttributes,
&'a [&'a syn::Ident],
&'a syn::Ident,
&'a syn::Ident,
&'a syn::Ident,
);
/// Expands a [`fmt::Display`]-like derive macro for the provided struct.
fn expand_struct(
s: &syn::DataStruct,
(attrs, type_params, ident, trait_ident, _): ExpansionCtx<'_>,
) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
let s = Expansion {
shared_attr: None,
attrs,
fields: &s.fields,
type_params,
trait_ident,
ident,
};
let bounds = s.generate_bounds();
let body = s.generate_body()?;
let vars = s.fields.iter().enumerate().map(|(i, f)| {
let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"));
let member = f
.ident
.clone()
.map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named);
quote! {
let #var = &self.#member;
}
});
let body = quote! {
#( #vars )*
#body
};
Ok((bounds, body))
}
/// Expands a [`fmt`]-like derive macro for the provided enum.
fn expand_enum(
e: &syn::DataEnum,
(container_attrs, type_params, _, trait_ident, attr_name): ExpansionCtx<'_>,
) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
if let Some(shared_fmt) = &container_attrs.common.fmt {
if shared_fmt
.placeholders_by_arg("_variant")
.any(|p| p.has_modifiers || p.trait_name != "Display")
{
// TODO: This limitation can be lifted, by analyzing the `shared_fmt` deeper and using
// `&dyn fmt::TraitName` for transparency instead of just `format_args!()` in the
// expansion.
return Err(syn::Error::new(
shared_fmt.span(),
"shared format `_variant` placeholder cannot contain format specifiers",
));
}
}
let (bounds, match_arms) = e.variants.iter().try_fold(
(Vec::new(), TokenStream::new()),
|(mut bounds, mut arms), variant| {
let mut attrs = ContainerAttributes::parse_attrs(&variant.attrs, attr_name)?
.map(Spanning::into_inner)
.unwrap_or_default();
let ident = &variant.ident;
if attrs.common.fmt.is_none()
&& variant.fields.is_empty()
&& attr_name != "display" {
let container_fmt = container_attrs.common.fmt.as_ref();
if container_fmt.is_none() {
return Err(syn::Error::new(
e.variants.span(),
format!(
"implicit formatting of unit enum variant is supported only for \
`Display` macro, use `#[{attr_name}(\"...\")]` to explicitly specify \
the formatting on every variant or the enum",
),
));
} else if container_fmt.is_some_and(|fmt| fmt.contains_arg("_variant")) {
return Err(syn::Error::new_spanned(
variant,
format!(
"implicit formatting of unit enum variant is supported only for \
`Display` macro, use `#[{attr_name}(\"...\")]` to explicitly specify \
the formatting on every variant when using `{{_variant}}`",
),
));
}
}
if let Some(rename_all) = container_attrs.rename_all {
attrs.rename_all.get_or_insert(rename_all);
}
let v = Expansion {
shared_attr: container_attrs.common.fmt.as_ref(),
attrs: &attrs,
fields: &variant.fields,
type_params,
trait_ident,
ident,
};
let arm_body = v.generate_body()?;
bounds.extend(v.generate_bounds());
let fields_idents =
variant.fields.iter().enumerate().map(|(i, f)| {
f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"))
});
let matcher = match variant.fields {
syn::Fields::Named(_) => {
quote! { Self::#ident { #( #fields_idents ),* } }
}
syn::Fields::Unnamed(_) => {
quote! { Self::#ident ( #( #fields_idents ),* ) }
}
syn::Fields::Unit => quote! { Self::#ident },
};
arms.extend([quote! { #matcher => { #arm_body }, }]);
Ok::<_, syn::Error>((bounds, arms))
},
)?;
let body = if match_arms.is_empty() {
quote! { match *self {} }
} else {
quote! { match self { #match_arms } }
};
Ok((bounds, body))
}
/// Expands a [`fmt::Display`]-like derive macro for the provided union.
fn expand_union(
u: &syn::DataUnion,
(attrs, _, _, _, attr_name): ExpansionCtx<'_>,
) -> syn::Result<(Vec<syn::WherePredicate>, TokenStream)> {
let fmt = &attrs.common.fmt.as_ref().ok_or_else(|| {
syn::Error::new(
u.fields.span(),
format!("unions must have `#[{attr_name}(\"...\", ...)]` attribute"),
)
})?;
Ok((
attrs.common.bounds.0.clone().into_iter().collect(),
quote! { derive_more::core::write!(__derive_more_f, #fmt) },
))
}
/// Helper struct to generate [`Display::fmt()`] implementation body and trait
/// bounds for a struct or an enum variant.
///
/// [`Display::fmt()`]: fmt::Display::fmt()
#[derive(Debug)]
struct Expansion<'a> {
/// [`FmtAttribute`] shared between all variants of an enum.
///
/// [`None`] for a struct.
shared_attr: Option<&'a FmtAttribute>,
/// Derive macro [`ContainerAttributes`].
attrs: &'a ContainerAttributes,
/// Struct or enum [`syn::Ident`].
///
/// [`syn::Ident`]: struct@syn::Ident
ident: &'a syn::Ident,
/// Struct or enum [`syn::Fields`].
fields: &'a syn::Fields,
/// Type parameters in this struct or enum.
type_params: &'a [&'a syn::Ident],
/// [`fmt`] trait [`syn::Ident`].
///
/// [`syn::Ident`]: struct@syn::Ident
trait_ident: &'a syn::Ident,
}
impl Expansion<'_> {
/// Checks and indicates whether a top-level shared [`FmtAttribute`] is present in this
/// [`Expansion`], and whether it has wrapping logic (e.g. uses `_variant` placeholder).
fn shared_attr_info(&self) -> (bool, bool) {
let shared_attr_contains_variant = self
.shared_attr
.map_or(true, |attr| attr.contains_arg("_variant"));
// If `shared_attr` is a transparent call to `_variant`, then we consider it being absent.
let has_shared_attr = self.shared_attr.is_some_and(|attr| {
attr.transparent_call().map_or(true, |(_, called_trait)| {
&called_trait != self.trait_ident || !shared_attr_contains_variant
})
});
(
has_shared_attr,
has_shared_attr && shared_attr_contains_variant,
)
}
/// Generates [`Display::fmt()`] implementation for a struct or an enum variant.
///
/// # Errors
///
/// In case [`FmtAttribute`] is [`None`] and [`syn::Fields`] length is greater than 1.
///
/// [`Display::fmt()`]: fmt::Display::fmt()
fn generate_body(&self) -> syn::Result<TokenStream> {
let mut body = TokenStream::new();
let (has_shared_attr, shared_attr_is_wrapping) = self.shared_attr_info();
let wrap_into_shared_attr = match &self.attrs.common.fmt {
Some(fmt) => {
body = if shared_attr_is_wrapping {
let deref_args = fmt.additional_deref_args(self.fields);
quote! { &derive_more::core::format_args!(#fmt, #(#deref_args),*) }
} else if let Some((expr, trait_ident)) =
fmt.transparent_call_on_fields(self.fields)
{
quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
} else {
let deref_args = fmt.additional_deref_args(self.fields);
quote! { derive_more::core::write!(__derive_more_f, #fmt, #(#deref_args),*) }
};
shared_attr_is_wrapping
}
None => {
if shared_attr_is_wrapping || !has_shared_attr {
body = if self.fields.is_empty() {
let mut ident_str = self.ident.unraw().to_string();
if let Some(rename_all) = &self.attrs.rename_all {
ident_str = rename_all.convert_case(&ident_str);
}
if shared_attr_is_wrapping {
quote! { #ident_str }
} else {
quote! { __derive_more_f.write_str(#ident_str) }
}
} else if self.fields.len() == 1 {
let field = self
.fields
.iter()
.next()
.unwrap_or_else(|| unreachable!("count() == 1"));
let ident =
field.ident.clone().unwrap_or_else(|| format_ident!("_0"));
let trait_ident = self.trait_ident;
if shared_attr_is_wrapping {
let placeholder =
trait_name_to_default_placeholder_literal(trait_ident);
quote! { &derive_more::core::format_args!(#placeholder, #ident) }
} else {
quote! {
derive_more::core::fmt::#trait_ident::fmt(#ident, __derive_more_f)
}
}
} else {
return Err(syn::Error::new(
self.fields.span(),
format!(
"struct or enum variant with more than 1 field must have \
`#[{}(\"...\", ...)]` attribute",
trait_name_to_attribute_name(self.trait_ident),
),
));
};
}
has_shared_attr
}
};
if wrap_into_shared_attr {
if let Some(shared_fmt) = &self.shared_attr {
let deref_args = shared_fmt.additional_deref_args(self.fields);
let shared_body = if let Some((expr, trait_ident)) =
shared_fmt.transparent_call_on_fields(self.fields)
{
quote! { derive_more::core::fmt::#trait_ident::fmt(#expr, __derive_more_f) }
} else {
quote! {
derive_more::core::write!(__derive_more_f, #shared_fmt, #(#deref_args),*)
}
};
body = if body.is_empty() {
shared_body
} else {
quote! { match #body { _variant => #shared_body } }
}
}
}
Ok(body)
}
/// Generates trait bounds for a struct or an enum variant.
fn generate_bounds(&self) -> Vec<syn::WherePredicate> {
let mut bounds = vec![];
let (has_shared_attr, shared_attr_is_wrapping) = self.shared_attr_info();
let mix_shared_attr_bounds = match &self.attrs.common.fmt {
Some(attr) => {
bounds.extend(
attr.bounded_types(self.fields)
.filter_map(|(ty, trait_name)| {
if !ty.contains_generics(self.type_params) {
return None;
}
let trait_ident = format_ident!("{trait_name}");
Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
})
.chain(self.attrs.common.bounds.0.clone()),
);
shared_attr_is_wrapping
}
None => {
if shared_attr_is_wrapping || !has_shared_attr {
bounds.extend(self.fields.iter().next().and_then(|f| {
let ty = &f.ty;
if !ty.contains_generics(self.type_params) {
return None;
}
let trait_ident = &self.trait_ident;
Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
}));
}
has_shared_attr
}
};
if mix_shared_attr_bounds {
bounds.extend(
self.shared_attr
.as_ref()
.unwrap()
.bounded_types(self.fields)
.filter_map(|(ty, trait_name)| {
if !ty.contains_generics(self.type_params) {
return None;
}
let trait_ident = format_ident!("{trait_name}");
Some(parse_quote! { #ty: derive_more::core::fmt::#trait_ident })
}),
);
}
bounds
}
}
/// Matches the provided derive macro `name` to appropriate actual trait name.
fn normalize_trait_name(name: &str) -> &'static str {
match name {
"Binary" => "Binary",
"Display" => "Display",
"LowerExp" => "LowerExp",
"LowerHex" => "LowerHex",
"Octal" => "Octal",
"Pointer" => "Pointer",
"UpperExp" => "UpperExp",
"UpperHex" => "UpperHex",
_ => unimplemented!(),
}
}
/// Matches the provided [`fmt`] trait `name` to its default formatting placeholder.
fn trait_name_to_default_placeholder_literal(name: &syn::Ident) -> &'static str {
match () {
_ if name == "Binary" => "{:b}",
_ if name == "Debug" => "{:?}",
_ if name == "Display" => "{}",
_ if name == "LowerExp" => "{:e}",
_ if name == "LowerHex" => "{:x}",
_ if name == "Octal" => "{:o}",
_ if name == "Pointer" => "{:p}",
_ if name == "UpperExp" => "{:E}",
_ if name == "UpperHex" => "{:X}",
_ => unimplemented!(),
}
}
+839
View File
@@ -0,0 +1,839 @@
//! Implementations of [`fmt`]-like derive macros.
//!
//! [`fmt`]: std::fmt
#[cfg(feature = "debug")]
pub(crate) mod debug;
#[cfg(feature = "display")]
pub(crate) mod display;
mod parsing;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{
ext::IdentExt as _,
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
spanned::Spanned as _,
token,
};
use crate::{
parsing::Expr,
utils::{attr, Either, Spanning},
};
/// Representation of a `bound` macro attribute, expressing additional trait bounds.
///
/// ```rust,ignore
/// #[<attribute>(bound(<where-predicates>))]
/// #[<attribute>(bounds(<where-predicates>))]
/// #[<attribute>(where(<where-predicates>))]
/// ```
#[derive(Debug, Default)]
struct BoundsAttribute(Punctuated<syn::WherePredicate, token::Comma>);
impl Parse for BoundsAttribute {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
Self::check_legacy_fmt(input)?;
let _ = input.parse::<syn::Path>().and_then(|p| {
if ["bound", "bounds", "where"]
.into_iter()
.any(|i| p.is_ident(i))
{
Ok(p)
} else {
Err(syn::Error::new(
p.span(),
"unknown attribute argument, expected `bound(...)`",
))
}
})?;
let content;
syn::parenthesized!(content in input);
content
.parse_terminated(syn::WherePredicate::parse, token::Comma)
.map(Self)
}
}
impl BoundsAttribute {
/// Errors in case legacy syntax is encountered: `bound = "..."`.
fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> {
let fork = input.fork();
let path = fork
.parse::<syn::Path>()
.and_then(|path| fork.parse::<token::Eq>().map(|_| path));
match path {
Ok(path) if path.is_ident("bound") => fork
.parse::<syn::Lit>()
.ok()
.and_then(|lit| match lit {
syn::Lit::Str(s) => Some(s.value()),
_ => None,
})
.map_or(Ok(()), |bound| {
Err(syn::Error::new(
input.span(),
format!("legacy syntax, use `bound({bound})` instead"),
))
}),
Ok(_) | Err(_) => Ok(()),
}
}
}
/// Representation of a [`fmt`]-like attribute.
///
/// ```rust,ignore
/// #[<attribute>("<fmt-literal>", <fmt-args>)]
/// ```
///
/// [`fmt`]: std::fmt
#[derive(Debug)]
struct FmtAttribute {
/// Interpolation [`syn::LitStr`].
///
/// [`syn::LitStr`]: struct@syn::LitStr
lit: syn::LitStr,
/// Optional [`token::Comma`].
///
/// [`token::Comma`]: struct@token::Comma
comma: Option<token::Comma>,
/// Interpolation arguments.
args: Punctuated<FmtArgument, token::Comma>,
}
impl Parse for FmtAttribute {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
Self::check_legacy_fmt(input)?;
let mut parsed = Self {
lit: input.parse()?,
comma: input
.peek(token::Comma)
.then(|| input.parse())
.transpose()?,
args: input.parse_terminated(FmtArgument::parse, token::Comma)?,
};
parsed.args.pop_punct();
Ok(parsed)
}
}
impl attr::ParseMultiple for FmtAttribute {}
impl ToTokens for FmtAttribute {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.lit.to_tokens(tokens);
self.comma.to_tokens(tokens);
self.args.to_tokens(tokens);
}
}
impl FmtAttribute {
/// Checks whether this [`FmtAttribute`] can be replaced with a transparent delegation (calling
/// a formatting trait directly instead of interpolation syntax).
///
/// If such transparent call is possible, then returns an [`Ident`] of the delegated trait and
/// the [`Expr`] to pass into the call, otherwise [`None`].
///
/// [`Ident`]: struct@syn::Ident
fn transparent_call(&self) -> Option<(Expr, syn::Ident)> {
// `FmtAttribute` is transparent when:
// (1) There is exactly one formatting parameter.
let lit = self.lit.value();
let param =
parsing::format(&lit).and_then(|(more, p)| more.is_empty().then_some(p))?;
// (2) And the formatting parameter doesn't contain any modifiers.
if param
.spec
.map(|s| {
s.align.is_some()
|| s.sign.is_some()
|| s.alternate.is_some()
|| s.zero_padding.is_some()
|| s.width.is_some()
|| s.precision.is_some()
|| !s.ty.is_trivial()
})
.unwrap_or_default()
{
return None;
}
let expr = match param.arg {
// (3) And either exactly one positional argument is specified.
Some(parsing::Argument::Integer(_)) | None => (self.args.len() == 1)
.then(|| self.args.first())
.flatten()
.map(|a| a.expr.clone()),
// (4) Or the formatting parameter's name refers to some outer binding.
Some(parsing::Argument::Identifier(name)) if self.args.is_empty() => {
Some(format_ident!("{name}").into())
}
// (5) Or exactly one named argument is specified for the formatting parameter's name.
Some(parsing::Argument::Identifier(name)) => (self.args.len() == 1)
.then(|| self.args.first())
.flatten()
.filter(|a| a.alias.as_ref().map(|a| a.0 == name).unwrap_or_default())
.map(|a| a.expr.clone()),
}?;
let trait_name = param
.spec
.map(|s| s.ty)
.unwrap_or(parsing::Type::Display)
.trait_name();
Some((expr, format_ident!("{trait_name}")))
}
/// Same as [`transparent_call()`], but additionally checks the returned [`Expr`] whether it's
/// one of the [`fmt_args_idents`] of the provided [`syn::Fields`], and makes it suitable for
/// passing directly into the transparent call of the delegated formatting trait.
///
/// [`fmt_args_idents`]: FieldsExt::fmt_args_idents
/// [`transparent_call()`]: FmtAttribute::transparent_call
fn transparent_call_on_fields(
&self,
fields: &syn::Fields,
) -> Option<(Expr, syn::Ident)> {
self.transparent_call().map(|(expr, trait_ident)| {
let expr = if let Some(field) = fields
.fmt_args_idents()
.find(|field| expr == *field || expr == field.unraw())
{
field.into()
} else {
parse_quote! { &(#expr) }
};
(expr, trait_ident)
})
}
/// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) by this
/// [`FmtAttribute`].
fn bounded_types<'a>(
&'a self,
fields: &'a syn::Fields,
) -> impl Iterator<Item = (&'a syn::Type, &'static str)> {
let placeholders = Placeholder::parse_fmt_string(&self.lit.value());
// We ignore unknown fields, as compiler will produce better error messages.
placeholders.into_iter().filter_map(move |placeholder| {
let name = match placeholder.arg {
Parameter::Named(name) => self
.args
.iter()
.find_map(|a| (a.alias()? == &name).then_some(&a.expr))
.map_or(Some(name), |expr| expr.ident().map(ToString::to_string))?,
Parameter::Positional(i) => self
.args
.iter()
.nth(i)
.and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))?
.to_string(),
};
let unnamed = name.strip_prefix('_').and_then(|s| s.parse().ok());
let ty = match (&fields, unnamed) {
(syn::Fields::Unnamed(f), Some(i)) => {
f.unnamed.iter().nth(i).map(|f| &f.ty)
}
(syn::Fields::Named(f), None) => f.named.iter().find_map(|f| {
f.ident
.as_ref()
.filter(|s| s.unraw() == name)
.map(|_| &f.ty)
}),
_ => None,
}?;
Some((ty, placeholder.trait_name))
})
}
#[cfg(feature = "display")]
/// Checks whether this [`FmtAttribute`] contains an argument with the provided `name` (either
/// in its direct [`FmtArgument`]s or inside [`Placeholder`]s).
fn contains_arg(&self, name: &str) -> bool {
self.placeholders_by_arg(name).next().is_some()
}
#[cfg(feature = "display")]
/// Returns an [`Iterator`] over [`Placeholder`]s using an argument with the provided `name`
/// (either in its direct [`FmtArgument`]s of this [`FmtAttribute`] or inside the
/// [`Placeholder`] itself).
fn placeholders_by_arg<'a>(
&'a self,
name: &'a str,
) -> impl Iterator<Item = Placeholder> + 'a {
let placeholders = Placeholder::parse_fmt_string(&self.lit.value());
placeholders.into_iter().filter(move |placeholder| {
match &placeholder.arg {
Parameter::Named(name) => self
.args
.iter()
.find_map(|a| (a.alias()? == name).then_some(&a.expr))
.map_or(Some(name.clone()), |expr| {
expr.ident().map(ToString::to_string)
}),
Parameter::Positional(i) => self
.args
.iter()
.nth(*i)
.and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))
.map(ToString::to_string),
}
.as_deref()
== Some(name)
})
}
/// Returns an [`Iterator`] over the additional formatting arguments doing the dereferencing
/// replacement in this [`FmtAttribute`] for those [`Placeholder`] representing the provided
/// [`syn::Fields`] and requiring it ([`fmt::Pointer`] ones).
///
/// [`fmt::Pointer`]: std::fmt::Pointer
fn additional_deref_args<'fmt: 'ret, 'fields: 'ret, 'ret>(
&'fmt self,
fields: &'fields syn::Fields,
) -> impl Iterator<Item = TokenStream> + 'ret {
let used_args = Placeholder::parse_fmt_string(&self.lit.value())
.into_iter()
.filter_map(|placeholder| match placeholder.arg {
Parameter::Named(name) if placeholder.trait_name == "Pointer" => {
Some(name)
}
_ => None,
})
.collect::<Vec<_>>();
fields.fmt_args_idents().filter_map(move |field_name| {
(used_args.iter().any(|arg| field_name.unraw() == arg)
&& !self.args.iter().any(|arg| {
arg.alias.as_ref().is_some_and(|(n, _)| n == &field_name)
}))
.then(|| quote! { #field_name = *#field_name })
})
}
/// Errors in case legacy syntax is encountered: `fmt = "...", (arg),*`.
fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> {
let fork = input.fork();
let path = fork
.parse::<syn::Path>()
.and_then(|path| fork.parse::<token::Eq>().map(|_| path));
match path {
Ok(path) if path.is_ident("fmt") => (|| {
let args = fork
.parse_terminated(
<Either<syn::Lit, syn::Ident>>::parse,
token::Comma,
)
.ok()?
.into_iter()
.enumerate()
.filter_map(|(i, arg)| match arg {
Either::Left(syn::Lit::Str(str)) => Some(if i == 0 {
format!("\"{}\"", str.value())
} else {
str.value()
}),
Either::Right(ident) => Some(ident.to_string()),
_ => None,
})
.collect::<Vec<_>>();
(!args.is_empty()).then_some(args)
})()
.map_or(Ok(()), |fmt| {
Err(syn::Error::new(
input.span(),
format!(
"legacy syntax, remove `fmt =` and use `{}` instead",
fmt.join(", "),
),
))
}),
Ok(_) | Err(_) => Ok(()),
}
}
}
/// Representation of a [named parameter][1] (`identifier '=' expression`) in a [`FmtAttribute`].
///
/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters
#[derive(Debug)]
struct FmtArgument {
/// `identifier =` [`Ident`].
///
/// [`Ident`]: struct@syn::Ident
alias: Option<(syn::Ident, token::Eq)>,
/// `expression` [`Expr`].
expr: Expr,
}
impl FmtArgument {
/// Returns an `identifier` of the [named parameter][1].
///
/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters
fn alias(&self) -> Option<&syn::Ident> {
self.alias.as_ref().map(|(ident, _)| ident)
}
}
impl Parse for FmtArgument {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
alias: (input.peek(syn::Ident) && input.peek2(token::Eq))
.then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?)))
.transpose()?,
expr: input.parse()?,
})
}
}
impl ToTokens for FmtArgument {
fn to_tokens(&self, tokens: &mut TokenStream) {
if let Some((ident, eq)) = &self.alias {
ident.to_tokens(tokens);
eq.to_tokens(tokens);
}
self.expr.to_tokens(tokens);
}
}
/// Representation of a [parameter][1] used in a [`Placeholder`].
///
/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-parameters
#[derive(Debug, Eq, PartialEq)]
enum Parameter {
/// [Positional parameter][1].
///
/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#positional-parameters
Positional(usize),
/// [Named parameter][1].
///
/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters
Named(String),
}
impl<'a> From<parsing::Argument<'a>> for Parameter {
fn from(arg: parsing::Argument<'a>) -> Self {
match arg {
parsing::Argument::Integer(i) => Self::Positional(i),
parsing::Argument::Identifier(i) => Self::Named(i.to_owned()),
}
}
}
/// Representation of a formatting placeholder.
#[derive(Debug, Eq, PartialEq)]
struct Placeholder {
/// Formatting argument (either named or positional) to be used by this [`Placeholder`].
arg: Parameter,
/// Indicator whether this [`Placeholder`] has any formatting modifiers.
has_modifiers: bool,
/// Name of [`std::fmt`] trait to be used for rendering this [`Placeholder`].
trait_name: &'static str,
}
impl Placeholder {
/// Parses [`Placeholder`]s from the provided formatting string.
fn parse_fmt_string(s: &str) -> Vec<Self> {
let mut n = 0;
parsing::format_string(s)
.into_iter()
.flat_map(|f| f.formats)
.map(|format| {
let (maybe_arg, ty) = (
format.arg,
format.spec.map(|s| s.ty).unwrap_or(parsing::Type::Display),
);
let position = maybe_arg.map(Into::into).unwrap_or_else(|| {
// Assign "the next argument".
// https://doc.rust-lang.org/stable/std/fmt/index.html#positional-parameters
n += 1;
Parameter::Positional(n - 1)
});
Self {
arg: position,
has_modifiers: format
.spec
.map(|s| {
s.align.is_some()
|| s.sign.is_some()
|| s.alternate.is_some()
|| s.zero_padding.is_some()
|| s.width.is_some()
|| s.precision.is_some()
|| !s.ty.is_trivial()
})
.unwrap_or_default(),
trait_name: ty.trait_name(),
}
})
.collect()
}
}
/// Representation of a [`fmt::Display`]-like derive macro attributes placed on a container (struct
/// or enum variant).
///
/// ```rust,ignore
/// #[<attribute>("<fmt-literal>", <fmt-args>)]
/// #[<attribute>(bound(<where-predicates>))]
/// ```
///
/// `#[<attribute>(...)]` can be specified only once, while multiple `#[<attribute>(bound(...))]`
/// are allowed.
///
/// [`fmt::Display`]: std::fmt::Display
#[derive(Debug, Default)]
struct ContainerAttributes {
/// Interpolation [`FmtAttribute`].
fmt: Option<FmtAttribute>,
/// Addition trait bounds.
bounds: BoundsAttribute,
}
impl Parse for ContainerAttributes {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
// We do check `FmtAttribute::check_legacy_fmt` eagerly here, because `Either` will swallow
// any error of the `Either::Left` if the `Either::Right` succeeds.
FmtAttribute::check_legacy_fmt(input)?;
<Either<FmtAttribute, BoundsAttribute>>::parse(input).map(|v| match v {
Either::Left(fmt) => Self {
bounds: BoundsAttribute::default(),
fmt: Some(fmt),
},
Either::Right(bounds) => Self { bounds, fmt: None },
})
}
}
impl attr::ParseMultiple for ContainerAttributes {
fn merge_attrs(
prev: Spanning<Self>,
new: Spanning<Self>,
name: &syn::Ident,
) -> syn::Result<Spanning<Self>> {
let Spanning {
span: prev_span,
item: mut prev,
} = prev;
let Spanning {
span: new_span,
item: new,
} = new;
if new.fmt.and_then(|n| prev.fmt.replace(n)).is_some() {
return Err(syn::Error::new(
new_span,
format!("multiple `#[{name}(\"...\", ...)]` attributes aren't allowed"),
));
}
prev.bounds.0.extend(new.bounds.0);
Ok(Spanning::new(
prev,
prev_span.join(new_span).unwrap_or(prev_span),
))
}
}
/// Matches the provided `trait_name` to appropriate [`FmtAttribute`]'s argument name.
fn trait_name_to_attribute_name<T>(trait_name: T) -> &'static str
where
T: for<'a> PartialEq<&'a str>,
{
match () {
_ if trait_name == "Binary" => "binary",
_ if trait_name == "Debug" => "debug",
_ if trait_name == "Display" => "display",
_ if trait_name == "LowerExp" => "lower_exp",
_ if trait_name == "LowerHex" => "lower_hex",
_ if trait_name == "Octal" => "octal",
_ if trait_name == "Pointer" => "pointer",
_ if trait_name == "UpperExp" => "upper_exp",
_ if trait_name == "UpperHex" => "upper_hex",
_ => unimplemented!(),
}
}
/// Extension of a [`syn::Type`] and a [`syn::Path`] allowing to travers its type parameters.
trait ContainsGenericsExt {
/// Checks whether this definition contains any of the provided `type_params`.
fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool;
}
impl ContainsGenericsExt for syn::Type {
fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool {
if type_params.is_empty() {
return false;
}
match self {
Self::Path(syn::TypePath { qself, path }) => {
if let Some(qself) = qself {
if qself.ty.contains_generics(type_params) {
return true;
}
}
if let Some(ident) = path.get_ident() {
type_params.contains(&ident)
} else {
path.contains_generics(type_params)
}
}
Self::Array(syn::TypeArray { elem, .. })
| Self::Group(syn::TypeGroup { elem, .. })
| Self::Paren(syn::TypeParen { elem, .. })
| Self::Ptr(syn::TypePtr { elem, .. })
| Self::Reference(syn::TypeReference { elem, .. })
| Self::Slice(syn::TypeSlice { elem, .. }) => {
elem.contains_generics(type_params)
}
Self::BareFn(syn::TypeBareFn { inputs, output, .. }) => {
inputs
.iter()
.any(|arg| arg.ty.contains_generics(type_params))
|| match output {
syn::ReturnType::Default => false,
syn::ReturnType::Type(_, ty) => {
ty.contains_generics(type_params)
}
}
}
Self::Tuple(syn::TypeTuple { elems, .. }) => {
elems.iter().any(|ty| ty.contains_generics(type_params))
}
Self::TraitObject(syn::TypeTraitObject { bounds, .. }) => {
bounds.iter().any(|bound| match bound {
syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) => {
path.contains_generics(type_params)
}
syn::TypeParamBound::Lifetime(..)
| syn::TypeParamBound::Verbatim(..) => false,
_ => unimplemented!(
"syntax is not supported by `derive_more`, please report a bug",
),
})
}
Self::ImplTrait(..)
| Self::Infer(..)
| Self::Macro(..)
| Self::Never(..)
| Self::Verbatim(..) => false,
_ => unimplemented!(
"syntax is not supported by `derive_more`, please report a bug",
),
}
}
}
impl ContainsGenericsExt for syn::Path {
fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool {
if type_params.is_empty() {
return false;
}
self.segments
.iter()
.enumerate()
.any(|(n, segment)| match &segment.arguments {
syn::PathArguments::None => {
// `TypeParam::AssocType` case.
(n == 0) && type_params.contains(&&segment.ident)
}
syn::PathArguments::AngleBracketed(
syn::AngleBracketedGenericArguments { args, .. },
) => args.iter().any(|generic| match generic {
syn::GenericArgument::Type(ty)
| syn::GenericArgument::AssocType(syn::AssocType { ty, .. }) => {
ty.contains_generics(type_params)
}
syn::GenericArgument::Lifetime(..)
| syn::GenericArgument::Const(..)
| syn::GenericArgument::AssocConst(..)
| syn::GenericArgument::Constraint(..) => false,
_ => unimplemented!(
"syntax is not supported by `derive_more`, please report a bug",
),
}),
syn::PathArguments::Parenthesized(
syn::ParenthesizedGenericArguments { inputs, output, .. },
) => {
inputs.iter().any(|ty| ty.contains_generics(type_params))
|| match output {
syn::ReturnType::Default => false,
syn::ReturnType::Type(_, ty) => {
ty.contains_generics(type_params)
}
}
}
})
}
}
/// Extension of [`syn::Fields`] providing helpers for a [`FmtAttribute`].
trait FieldsExt {
/// Returns an [`Iterator`] over [`syn::Ident`]s representing these [`syn::Fields`] in a
/// [`FmtAttribute`] as [`FmtArgument`]s or named [`Placeholder`]s.
///
/// [`syn::Ident`]: struct@syn::Ident
fn fmt_args_idents(&self) -> impl Iterator<Item = syn::Ident> + '_;
}
impl FieldsExt for syn::Fields {
fn fmt_args_idents(&self) -> impl Iterator<Item = syn::Ident> + '_ {
self.iter()
.enumerate()
.map(|(i, f)| f.ident.clone().unwrap_or_else(|| format_ident!("_{i}")))
}
}
#[cfg(test)]
mod fmt_attribute_spec {
use itertools::Itertools as _;
use quote::ToTokens;
use super::FmtAttribute;
fn assert<'a>(input: &'a str, parsed: impl AsRef<[&'a str]>) {
let parsed = parsed.as_ref();
let attr = syn::parse_str::<FmtAttribute>(&format!("\"\", {input}")).unwrap();
let fmt_args = attr
.args
.into_iter()
.map(|arg| arg.into_token_stream().to_string())
.collect::<Vec<String>>();
fmt_args.iter().zip_eq(parsed).enumerate().for_each(
|(i, (found, expected))| {
assert_eq!(
*expected, found,
"Mismatch at index {i}\n\
Expected: {parsed:?}\n\
Found: {fmt_args:?}",
);
},
);
}
#[test]
fn cases() {
let cases = [
"ident",
"alias = ident",
"[a , b , c , d]",
"counter += 1",
"async { fut . await }",
"a < b",
"a > b",
"{ let x = (a , b) ; }",
"invoke (a , b)",
"foo as f64",
"| a , b | a + b",
"obj . k",
"for pat in expr { break pat ; }",
"if expr { true } else { false }",
"vector [2]",
"1",
"\"foo\"",
"loop { break i ; }",
"format ! (\"{}\" , q)",
"match n { Some (n) => { } , None => { } }",
"x . foo ::< T > (a , b)",
"x . foo ::< T < [T < T >; if a < b { 1 } else { 2 }] >, { a < b } > (a , b)",
"(a + b)",
"i32 :: MAX",
"1 .. 2",
"& a",
"[0u8 ; N]",
"(a , b , c , d)",
"< Ty as Trait > :: T",
"< Ty < Ty < T >, { a < b } > as Trait < T > > :: T",
];
assert("", []);
for i in 1..4 {
for permutations in cases.into_iter().permutations(i) {
let mut input = permutations.clone().join(",");
assert(&input, &permutations);
input.push(',');
assert(&input, &permutations);
}
}
}
}
#[cfg(test)]
mod placeholder_parse_fmt_string_spec {
use super::{Parameter, Placeholder};
#[test]
fn indicates_position_and_trait_name_for_each_fmt_placeholder() {
let fmt_string = "{},{:?},{{}},{{{1:0$}}}-{2:.1$x}{par:#?}{:width$}";
assert_eq!(
Placeholder::parse_fmt_string(fmt_string),
vec![
Placeholder {
arg: Parameter::Positional(0),
has_modifiers: false,
trait_name: "Display",
},
Placeholder {
arg: Parameter::Positional(1),
has_modifiers: false,
trait_name: "Debug",
},
Placeholder {
arg: Parameter::Positional(1),
has_modifiers: true,
trait_name: "Display",
},
Placeholder {
arg: Parameter::Positional(2),
has_modifiers: true,
trait_name: "LowerHex",
},
Placeholder {
arg: Parameter::Named("par".to_owned()),
has_modifiers: true,
trait_name: "Debug",
},
Placeholder {
arg: Parameter::Positional(2),
has_modifiers: true,
trait_name: "Display",
},
],
);
}
}
File diff suppressed because it is too large Load Diff
+403
View File
@@ -0,0 +1,403 @@
//! Implementation of a [`From`] derive macro.
use std::{
any::{Any, TypeId},
iter,
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens as _, TokenStreamExt as _};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
spanned::Spanned as _,
token,
};
use crate::utils::{
attr::{self, ParseMultiple as _},
polyfill, Either, Spanning,
};
/// Expands a [`From`] derive macro.
pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
let attr_name = format_ident!("from");
match &input.data {
syn::Data::Struct(data) => Expansion {
attrs: StructAttribute::parse_attrs_with(
&input.attrs,
&attr_name,
&ConsiderLegacySyntax {
fields: &data.fields,
},
)?
.map(|attr| attr.into_inner().into())
.as_ref(),
ident: &input.ident,
variant: None,
fields: &data.fields,
generics: &input.generics,
has_explicit_from: false,
}
.expand(),
syn::Data::Enum(data) => {
let mut has_explicit_from = false;
let attrs = data
.variants
.iter()
.map(|variant| {
let attr = VariantAttribute::parse_attrs_with(
&variant.attrs,
&attr_name,
&ConsiderLegacySyntax {
fields: &variant.fields,
},
)?
.map(Spanning::into_inner);
if matches!(
attr,
Some(
VariantAttribute::Empty(_)
| VariantAttribute::Types(_)
| VariantAttribute::Forward(_)
),
) {
has_explicit_from = true;
}
Ok(attr)
})
.collect::<syn::Result<Vec<_>>>()?;
data.variants
.iter()
.zip(&attrs)
.map(|(variant, attrs)| {
Expansion {
attrs: attrs.as_ref(),
ident: &input.ident,
variant: Some(&variant.ident),
fields: &variant.fields,
generics: &input.generics,
has_explicit_from,
}
.expand()
})
.collect()
}
syn::Data::Union(data) => Err(syn::Error::new(
data.union_token.span(),
"`From` cannot be derived for unions",
)),
}
}
/// Representation of a [`From`] derive macro struct container attribute.
///
/// ```rust,ignore
/// #[from(forward)]
/// #[from(<types>)]
/// ```
type StructAttribute = attr::Conversion;
/// Representation of a [`From`] derive macro enum variant attribute.
///
/// ```rust,ignore
/// #[from]
/// #[from(skip)] #[from(ignore)]
/// #[from(forward)]
/// #[from(<types>)]
/// ```
type VariantAttribute = attr::FieldConversion;
/// Expansion of a macro for generating [`From`] implementation of a struct or
/// enum.
struct Expansion<'a> {
/// [`From`] attributes.
///
/// As a [`VariantAttribute`] is superset of a [`StructAttribute`], we use
/// it for both derives.
attrs: Option<&'a VariantAttribute>,
/// Struct or enum [`syn::Ident`].
///
/// [`syn::Ident`]: struct@syn::Ident
ident: &'a syn::Ident,
/// Variant [`syn::Ident`] in case of enum expansion.
///
/// [`syn::Ident`]: struct@syn::Ident
variant: Option<&'a syn::Ident>,
/// Struct or variant [`syn::Fields`].
fields: &'a syn::Fields,
/// Struct or enum [`syn::Generics`].
generics: &'a syn::Generics,
/// Indicator whether one of the enum variants has
/// [`VariantAttribute::Empty`], [`VariantAttribute::Types`] or
/// [`VariantAttribute::Forward`].
///
/// Always [`false`] for structs.
has_explicit_from: bool,
}
impl Expansion<'_> {
/// Expands [`From`] implementations for a struct or an enum variant.
fn expand(&self) -> syn::Result<TokenStream> {
use crate::utils::FieldsExt as _;
let ident = self.ident;
let field_tys = self.fields.iter().map(|f| &f.ty).collect::<Vec<_>>();
let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl();
let skip_variant = self.has_explicit_from
|| (self.variant.is_some() && self.fields.is_empty());
match (self.attrs, skip_variant) {
(Some(VariantAttribute::Types(tys)), _) => {
tys.0.iter().map(|ty| {
let variant = self.variant.iter();
let mut from_tys = self.fields.validate_type(ty)?;
let init = self.expand_fields(|ident, ty, index| {
let ident = ident.into_iter();
let index = index.into_iter();
let from_ty = from_tys.next().unwrap_or_else(|| unreachable!());
quote! {
#( #ident: )* <#ty as derive_more::core::convert::From<#from_ty>>::from(
value #( .#index )*
),
}
});
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and unreachable types
#[automatically_derived]
impl #impl_gens derive_more::core::convert::From<#ty>
for #ident #ty_gens #where_clause {
#[inline]
fn from(value: #ty) -> Self {
#ident #( :: #variant )* #init
}
}
})
})
.collect()
}
(Some(VariantAttribute::Empty(_)), _) | (None, false) => {
let variant = self.variant.iter();
let init = self.expand_fields(|ident, _, index| {
let ident = ident.into_iter();
let index = index.into_iter();
quote! { #( #ident: )* value #( . #index )*, }
});
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_gens derive_more::core::convert::From<(#( #field_tys ),*)>
for #ident #ty_gens #where_clause {
#[inline]
fn from(value: (#( #field_tys ),*)) -> Self {
#ident #( :: #variant )* #init
}
}
})
}
(Some(VariantAttribute::Forward(_)), _) => {
let mut i = 0;
let mut gen_idents = Vec::with_capacity(self.fields.len());
let init = self.expand_fields(|ident, ty, index| {
let ident = ident.into_iter();
let index = index.into_iter();
let gen_ident = format_ident!("__FromT{i}");
let out = quote! {
#( #ident: )* <#ty as derive_more::core::convert::From<#gen_ident>>::from(
value #( .#index )*
),
};
gen_idents.push(gen_ident);
i += 1;
out
});
let variant = self.variant.iter();
let generics = {
let mut generics = self.generics.clone();
for (ty, ident) in field_tys.iter().zip(&gen_idents) {
generics
.make_where_clause()
.predicates
.push(parse_quote! { #ty: derive_more::core::convert::From<#ident> });
generics
.params
.push(syn::TypeParam::from(ident.clone()).into());
}
generics
};
let (impl_gens, _, where_clause) = generics.split_for_impl();
Ok(quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_gens derive_more::core::convert::From<(#( #gen_idents ),*)>
for #ident #ty_gens #where_clause {
#[inline]
fn from(value: (#( #gen_idents ),*)) -> Self {
#ident #(:: #variant)* #init
}
}
})
}
(Some(VariantAttribute::Skip(_)), _) | (None, true) => {
Ok(TokenStream::new())
}
}
}
/// Expands fields initialization wrapped into [`token::Brace`]s in case of
/// [`syn::FieldsNamed`], or [`token::Paren`] in case of
/// [`syn::FieldsUnnamed`].
///
/// [`token::Brace`]: struct@token::Brace
/// [`token::Paren`]: struct@token::Paren
fn expand_fields(
&self,
mut wrap: impl FnMut(
Option<&syn::Ident>,
&syn::Type,
Option<syn::Index>,
) -> TokenStream,
) -> TokenStream {
let surround = match self.fields {
syn::Fields::Named(_) | syn::Fields::Unnamed(_) => {
Some(|tokens| match self.fields {
syn::Fields::Named(named) => {
let mut out = TokenStream::new();
named
.brace_token
.surround(&mut out, |out| out.append_all(tokens));
out
}
syn::Fields::Unnamed(unnamed) => {
let mut out = TokenStream::new();
unnamed
.paren_token
.surround(&mut out, |out| out.append_all(tokens));
out
}
syn::Fields::Unit => unreachable!(),
})
}
syn::Fields::Unit => None,
};
surround
.map(|surround| {
surround(if self.fields.len() == 1 {
let field = self
.fields
.iter()
.next()
.unwrap_or_else(|| unreachable!("self.fields.len() == 1"));
wrap(field.ident.as_ref(), &field.ty, None)
} else {
self.fields
.iter()
.enumerate()
.map(|(i, field)| {
wrap(field.ident.as_ref(), &field.ty, Some(i.into()))
})
.collect()
})
})
.unwrap_or_default()
}
}
/// [`attr::Parser`] considering legacy syntax for [`attr::Types`] and emitting [`legacy_error`], if
/// any occurs.
struct ConsiderLegacySyntax<'a> {
/// [`syn::Fields`] of a struct or enum variant, the attribute is parsed for.
fields: &'a syn::Fields,
}
impl attr::Parser for ConsiderLegacySyntax<'_> {
fn parse<T: Parse + Any>(&self, input: ParseStream<'_>) -> syn::Result<T> {
if TypeId::of::<T>() == TypeId::of::<attr::Types>() {
let ahead = input.fork();
if let Ok(p) = ahead.parse::<syn::Path>() {
if p.is_ident("types") {
return legacy_error(&ahead, input.span(), self.fields);
}
}
}
T::parse(input)
}
}
/// Constructs a [`syn::Error`] for legacy syntax: `#[from(types(i32, "&str"))]`.
fn legacy_error<T>(
tokens: ParseStream<'_>,
span: Span,
fields: &syn::Fields,
) -> syn::Result<T> {
let content;
syn::parenthesized!(content in tokens);
let types = content
.parse_terminated(polyfill::NestedMeta::parse, token::Comma)?
.into_iter()
.map(|meta| {
let value = match meta {
polyfill::NestedMeta::Meta(meta) => {
meta.into_token_stream().to_string()
}
polyfill::NestedMeta::Lit(syn::Lit::Str(str)) => str.value(),
polyfill::NestedMeta::Lit(_) => unreachable!(),
};
if fields.len() > 1 {
format!(
"({})",
fields
.iter()
.map(|_| value.clone())
.collect::<Vec<_>>()
.join(", "),
)
} else {
value
}
})
.chain(match fields.len() {
0 => Either::Left(iter::empty()),
1 => Either::Right(iter::once(
fields
.iter()
.next()
.unwrap_or_else(|| unreachable!("fields.len() == 1"))
.ty
.to_token_stream()
.to_string(),
)),
_ => Either::Right(iter::once(format!(
"({})",
fields
.iter()
.map(|f| f.ty.to_token_stream().to_string())
.collect::<Vec<_>>()
.join(", ")
))),
})
.collect::<Vec<_>>()
.join(", ");
Err(syn::Error::new(
span,
format!("legacy syntax, remove `types` and use `{types}` instead"),
))
}
+556
View File
@@ -0,0 +1,556 @@
//! Implementation of a [`FromStr`] derive macro.
#[cfg(doc)]
use std::str::FromStr;
use std::{collections::HashMap, iter};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{parse::Parse, parse_quote, spanned::Spanned as _};
use crate::utils::{
attr::{self, ParseMultiple as _},
Either, GenericsSearch, Spanning,
};
/// Expands a [`FromStr`] derive macro.
pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
match &input.data {
syn::Data::Struct(data) => Ok(if data.fields.is_empty() {
FlatExpansion::try_from(input)?.into_token_stream()
} else {
ForwardExpansion::try_from(input)?.into_token_stream()
}),
syn::Data::Enum(_) => Ok(FlatExpansion::try_from(input)?.into_token_stream()),
syn::Data::Union(data) => Err(syn::Error::new(
data.union_token.span(),
"`FromStr` cannot be derived for unions",
)),
}
}
/// Expansion of a macro for generating a forwarding [`FromStr`] implementation of a struct.
struct ForwardExpansion<'i> {
/// [`syn::Ident`] and [`syn::Generics`] of the struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Field`] representing the wrapped type to forward implementation on.
inner: &'i syn::Field,
/// Optional [`attr::Error`] enabling conversion into a custom error type.
custom_error: Option<attr::Error>,
}
impl<'i> TryFrom<&'i syn::DeriveInput> for ForwardExpansion<'i> {
type Error = syn::Error;
fn try_from(input: &'i syn::DeriveInput) -> syn::Result<Self> {
let syn::Data::Struct(data) = &input.data else {
return Err(syn::Error::new(
input.span(),
"expected a struct for forward `FromStr` derive",
));
};
// TODO: Unite these two conditions via `&&` once MSRV is bumped to 1.88 or above.
if data.fields.len() != 1 {
return Err(syn::Error::new(
data.fields.span(),
"only structs with single field can derive `FromStr`",
));
}
let Some(inner) = data.fields.iter().next() else {
return Err(syn::Error::new(
data.fields.span(),
"only structs with single field can derive `FromStr`",
));
};
let custom_error =
attr::Error::parse_attrs(&input.attrs, &format_ident!("from_str"))?
.map(Spanning::into_inner);
Ok(Self {
self_ty: (&input.ident, &input.generics),
inner,
custom_error,
})
}
}
impl ToTokens for ForwardExpansion<'_> {
/// Expands a forwarding [`FromStr`] implementations for a struct.
fn to_tokens(&self, tokens: &mut TokenStream) {
let inner_ty = &self.inner.ty;
let ty = self.self_ty.0;
let generics_search = GenericsSearch::from(self.self_ty.1);
let mut generics = self.self_ty.1.clone();
if generics_search.any_in(inner_ty) {
generics.make_where_clause().predicates.push(parse_quote! {
#inner_ty: derive_more::core::str::FromStr
});
}
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let constructor = self.inner.self_constructor([parse_quote! { v }]);
let mut error_ty =
quote! { <#inner_ty as derive_more::core::str::FromStr>::Err };
let mut error_conv = quote! {};
if let Some(custom_error) = &self.custom_error {
error_ty = custom_error.ty.to_token_stream();
error_conv = custom_error.conv.as_ref().map_or_else(
|| quote! { .map_err(derive_more::core::convert::Into::into) },
|conv| quote! { .map_err(#conv) },
);
}
quote! {
#[automatically_derived]
impl #impl_generics derive_more::core::str::FromStr for #ty #ty_generics #where_clause {
type Err = #error_ty;
#[inline]
fn from_str(s: &str) -> derive_more::core::result::Result<Self, Self::Err> {
derive_more::core::str::FromStr::from_str(s).map(|v| #constructor)#error_conv
}
}
}.to_tokens(tokens);
}
}
/// Expansion of a macro for generating a flat [`FromStr`] implementation of an enum or a struct.
struct FlatExpansion<'i> {
/// [`syn::Ident`] and [`syn::Generics`] of the enum/struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Ident`]s along with the matched values (enum variants or struct itself), and
/// a value-specific [`attr::RenameAll`] overriding [`FlatExpansion::rename_all`], if any.
///
/// [`syn::Ident`]: struct@syn::Ident
matches: Vec<(
&'i syn::Ident,
Either<&'i syn::DataStruct, &'i syn::Variant>,
Option<attr::RenameAll>,
)>,
/// [`FlatExpansion::matches`] grouped by its similar representation for detecting whether their
/// case-insensitivity should be disabled.
similar_matches: HashMap<String, Vec<&'i syn::Ident>>,
/// Optional [`attr::RenameAll`] indicating the case conversion to be applied to all the matched
/// values (enum variants or struct itself).
rename_all: Option<attr::RenameAll>,
/// Optional [`attr::Error`] enabling conversion into a custom error type.
custom_error: Option<attr::Error>,
}
impl<'i> TryFrom<&'i syn::DeriveInput> for FlatExpansion<'i> {
type Error = syn::Error;
fn try_from(input: &'i syn::DeriveInput) -> syn::Result<Self> {
let attr_ident = &format_ident!("from_str");
let matches = match &input.data {
syn::Data::Struct(data) => {
if !data.fields.is_empty() {
return Err(syn::Error::new(
data.fields.span(),
"only structs with no fields can derive `FromStr`",
));
}
vec![(&input.ident, Either::Left(data), None)]
}
syn::Data::Enum(data) => data
.variants
.iter()
.map(|variant| {
if !variant.fields.is_empty() {
return Err(syn::Error::new(
variant.fields.span(),
"only enums with no fields can derive `FromStr`",
));
}
let attr =
attr::RenameAll::parse_attrs(&variant.attrs, attr_ident)?
.map(Spanning::into_inner);
Ok((&variant.ident, Either::Right(variant), attr))
})
.collect::<syn::Result<_>>()?,
syn::Data::Union(_) => {
return Err(syn::Error::new(
input.span(),
"expected an enum or a struct for flat `FromStr` derive",
))
}
};
let FlatContainerAttributes {
rename_all,
error: custom_error,
} = FlatContainerAttributes::parse_attrs(&input.attrs, attr_ident)?
.map(Spanning::into_inner)
.unwrap_or_default();
let mut similar_matches = <HashMap<_, Vec<_>>>::new();
if rename_all.is_none() {
for (ident, _, renaming) in &matches {
let name = ident.to_string();
let lowercased = name.to_lowercase();
if let Some(rename) = renaming {
let renamed_lowercased = rename.convert_case(&name);
if renamed_lowercased != lowercased {
similar_matches
.entry(renamed_lowercased)
.or_default()
.push(*ident);
}
}
similar_matches.entry(lowercased).or_default().push(*ident);
}
}
let mut exact_matches = <HashMap<String, Vec<String>>>::new();
for (ident, _, renaming) in &matches {
let name = ident.to_string();
let exact = if let Some(default_renaming) = &rename_all {
renaming
.as_ref()
.unwrap_or(default_renaming)
.convert_case(&name)
} else if let Some(renaming) = renaming {
renaming.convert_case(&name)
} else {
let lowercased = name.to_lowercase();
if similar_matches[&lowercased].len() > 1 {
name.clone()
} else {
lowercased
}
};
exact_matches.entry(exact).or_default().push(name);
}
if let Some((string, variants)) =
exact_matches.into_iter().find(|(_, vs)| vs.len() > 1)
{
return Err(syn::Error::new(
input.ident.span(),
format!(
"`{}` variants cannot have the same \"{string}\" string representation",
variants.join("`, `"),
),
));
}
Ok(Self {
self_ty: (&input.ident, &input.generics),
matches,
similar_matches,
rename_all,
custom_error,
})
}
}
impl ToTokens for FlatExpansion<'_> {
/// Expands a flat [`FromStr`] implementations for an enum.
fn to_tokens(&self, tokens: &mut TokenStream) {
let ty = self.self_ty.0;
let (impl_generics, ty_generics, where_clause) =
self.self_ty.1.split_for_impl();
let ty_name = ty.to_string();
let scrutinee_lowercased = self
.rename_all
.is_none()
.then(|| quote! { .to_lowercase().as_str() });
let match_arms = if let Some(default_renaming) = self.rename_all {
self.matches
.iter()
.map(|(ident, value, renaming)| {
let converted = renaming
.unwrap_or(default_renaming)
.convert_case(&ident.to_string());
let constructor = value.self_constructor_empty();
quote! { #converted => #constructor, }
})
.collect::<Vec<_>>()
} else {
self.matches
.iter()
.map(|(ident, value, renaming)| {
let name = ident.to_string();
let constructor = value.self_constructor_empty();
if let Some(rename) = renaming {
let exact_name = rename.convert_case(&name);
quote! { _ if s == #exact_name => #constructor, }
} else {
let lowercased = name.to_lowercase();
let exact_guard = (self.similar_matches[&lowercased].len() > 1)
.then(|| quote! { if s == #name });
quote! { #lowercased #exact_guard => #constructor, }
}
})
.collect()
};
let default_error = quote! { derive_more::FromStrError::new(#ty_name) };
let mut error_ty = quote! { derive_more::FromStrError };
let mut error_val = default_error.clone();
if let Some(custom_error) = &self.custom_error {
error_ty = custom_error.ty.to_token_stream();
error_val = custom_error.conv.as_ref().map_or_else(
|| quote! { derive_more::core::convert::Into::into(#default_error) },
|conv| quote! { (#conv)(#default_error) },
);
}
quote! {
#[allow(unreachable_code)] // for empty enums
#[automatically_derived]
impl #impl_generics derive_more::core::str::FromStr for #ty #ty_generics #where_clause {
type Err = #error_ty;
fn from_str(s: &str) -> derive_more::core::result::Result<
Self, <Self as derive_more::core::str::FromStr>::Err,
> {
derive_more::core::result::Result::Ok(match s #scrutinee_lowercased {
#( #match_arms )*
_ => return derive_more::core::result::Result::Err(#error_val),
})
}
}
}.to_tokens(tokens);
}
}
/// Extension of [`syn::Fields`] used by this expansion.
trait FieldsExt {
/// Generates a `name`d constructor with the provided `values` assigned to these
/// [`syn::Fields`].
///
/// # Panics
///
/// If number of provided `values` doesn't match number of these [`syn::Fields`].
fn constructor(
&self,
name: &syn::Path,
values: impl IntoIterator<Item = syn::Ident>,
) -> TokenStream;
/// Generates a `Self` type constructor with the provided `values` assigned to these
/// [`syn::Fields`].
///
/// # Panics
///
/// If number of provided `values` doesn't match number of these [`syn::Fields`].
fn self_constructor(
&self,
values: impl IntoIterator<Item = syn::Ident>,
) -> TokenStream {
self.constructor(&self.self_ty(), values)
}
/// Generates a `Self` type constructor with no fields.
///
/// # Panics
///
/// If these [`syn::Fields`] are not [empty].
///
/// [empty]: syn::Fields::is_empty
fn self_constructor_empty(&self) -> TokenStream {
self.self_constructor(iter::empty())
}
/// Returns a [`syn::Path`] representing a `Self` type of these [`syn::Fields`].
fn self_ty(&self) -> syn::Path {
parse_quote! { Self }
}
}
impl FieldsExt for syn::Fields {
fn constructor(
&self,
name: &syn::Path,
values: impl IntoIterator<Item = syn::Ident>,
) -> TokenStream {
let values = values.into_iter();
let fields = match self {
Self::Named(fields) => {
let initializers = fields.named.iter().zip(values).map(|(f, value)| {
let ident = &f.ident;
quote! { #ident: #value }
});
Some(quote! { { #( #initializers, )*} })
}
Self::Unnamed(_) => Some(quote! { ( #( #values, )* ) }),
Self::Unit => None,
};
quote! { #name #fields }
}
}
impl FieldsExt for syn::Field {
fn constructor(
&self,
name: &syn::Path,
values: impl IntoIterator<Item = syn::Ident>,
) -> TokenStream {
let mut values = values.into_iter();
let value = values.next().expect("expected a single value");
if values.next().is_some() {
panic!("expected a single value");
}
if let Some(ident) = &self.ident {
quote! { #name { #ident: #value } }
} else {
quote! { #name(#value) }
}
}
}
impl FieldsExt for syn::Variant {
fn constructor(
&self,
name: &syn::Path,
values: impl IntoIterator<Item = syn::Ident>,
) -> TokenStream {
self.fields.constructor(name, values)
}
fn self_ty(&self) -> syn::Path {
let variant = &self.ident;
parse_quote! { Self::#variant }
}
}
impl FieldsExt for syn::DataStruct {
fn constructor(
&self,
name: &syn::Path,
values: impl IntoIterator<Item = syn::Ident>,
) -> TokenStream {
self.fields.constructor(name, values)
}
}
impl<L: FieldsExt, R: FieldsExt> FieldsExt for Either<&L, &R> {
fn constructor(
&self,
name: &syn::Path,
values: impl IntoIterator<Item = syn::Ident>,
) -> TokenStream {
match self {
Self::Left(l) => l.constructor(name, values),
Self::Right(r) => r.constructor(name, values),
}
}
fn self_ty(&self) -> syn::Path {
match self {
Self::Left(l) => l.self_ty(),
Self::Right(r) => r.self_ty(),
}
}
}
/// Representation of possible [`FromStr`] derive macro attributes placed on an enum or a struct for
/// a [`FlatExpansion`].
///
/// ```rust,ignore
/// #[<attribute>(rename_all = "<casing>")]
/// #[<attribute>(error(<ty>))]
/// #[<attribute>(error(<ty>, <conv>))]
/// ```
///
/// Both `#[<attribute>(rename_all = "<casing>")]` and `#[<attribute>(error(<ty>[, <conv>]))]` can
/// be specified only once.
#[derive(Default)]
struct FlatContainerAttributes {
/// [`attr::RenameAll`] for case conversion.
rename_all: Option<attr::RenameAll>,
/// [`attr::Error`] for conversion into a custom error type.
error: Option<attr::Error>,
}
impl Parse for FlatContainerAttributes {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
mod ident {
use syn::custom_keyword;
custom_keyword!(error);
custom_keyword!(rename_all);
}
let ahead = input.lookahead1();
if ahead.peek(ident::error) {
Ok(Self {
error: Some(input.parse()?),
..Default::default()
})
} else if ahead.peek(ident::rename_all) {
Ok(Self {
rename_all: Some(input.parse()?),
..Default::default()
})
} else {
Err(ahead.error())
}
}
}
impl attr::ParseMultiple for FlatContainerAttributes {
fn merge_attrs(
prev: Spanning<Self>,
new: Spanning<Self>,
name: &syn::Ident,
) -> syn::Result<Spanning<Self>> {
let Spanning {
span: prev_span,
item: mut prev,
} = prev;
let Spanning {
span: new_span,
item: new,
} = new;
if new
.rename_all
.and_then(|n| prev.rename_all.replace(n))
.is_some()
{
return Err(syn::Error::new(
new_span,
format!("multiple `#[{name}(rename_all=\"...\")]` attributes aren't allowed"),
));
}
if prev.error.is_some() && new.error.is_some() {
return Err(syn::Error::new(
new_span,
format!(
"multiple `#[{name}(error(\"...\")]` attributes aren't allowed",
),
));
}
prev.error = prev.error.or(new.error);
Ok(Spanning::new(
prev,
prev_span.join(new_span).unwrap_or(prev_span),
))
}
}
+47
View File
@@ -0,0 +1,47 @@
use crate::utils::{add_where_clauses_for_new_ident, SingleFieldData, State};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Result, DeriveInput};
/// Provides the hook to expand `#[derive(Index)]` into an implementation of `Index`
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let index_type = format_ident!("__IdxT");
let mut state =
State::with_field_ignore(input, trait_name, trait_name.to_lowercase())?;
state.add_trait_path_type_param(quote! { #index_type });
let SingleFieldData {
field,
field_type,
input_type,
trait_path_with_params,
casted_trait,
member,
..
} = state.assert_single_enabled_field();
let type_where_clauses = quote! {
where #field_type: #trait_path_with_params
};
let new_generics = add_where_clauses_for_new_ident(
&input.generics,
&[field],
&index_type,
type_where_clauses,
true,
);
let (impl_generics, _, where_clause) = new_generics.split_for_impl();
let (_, ty_generics, _) = input.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
impl #impl_generics #trait_path_with_params for #input_type #ty_generics #where_clause {
type Output = #casted_trait::Output;
#[inline]
fn index(&self, idx: #index_type) -> &Self::Output {
#casted_trait::index(&#member, idx)
}
}
})
}
+44
View File
@@ -0,0 +1,44 @@
use crate::utils::{add_where_clauses_for_new_ident, SingleFieldData, State};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse::Result, DeriveInput};
/// Provides the hook to expand `#[derive(IndexMut)]` into an implementation of `IndexMut`
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let index_type = format_ident!("__IdxT");
let mut state = State::with_field_ignore(input, trait_name, "index_mut".into())?;
state.add_trait_path_type_param(quote! { #index_type });
let SingleFieldData {
field,
field_type,
input_type,
trait_path_with_params,
casted_trait,
member,
..
} = state.assert_single_enabled_field();
let type_where_clauses = quote! {
where #field_type: #trait_path_with_params
};
let new_generics = add_where_clauses_for_new_ident(
&input.generics,
&[field],
&index_type,
type_where_clauses,
true,
);
let (impl_generics, _, where_clause) = new_generics.split_for_impl();
let (_, ty_generics, _) = input.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
impl #impl_generics #trait_path_with_params for #input_type #ty_generics #where_clause {
#[inline]
fn index_mut(&mut self, idx: #index_type) -> &mut Self::Output {
#casted_trait::index_mut(&mut #member, idx)
}
}
})
}
+632
View File
@@ -0,0 +1,632 @@
//! Implementation of an [`Into`] derive macro.
use std::{
any::{Any, TypeId},
borrow::Cow,
iter, slice,
};
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens as _};
use syn::{
ext::IdentExt as _,
parse::{discouraged::Speculative as _, Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned as _,
token,
};
use crate::utils::{
attr::{self, ParseMultiple as _},
polyfill,
replace_self::DeriveInputExt as _,
Either, FieldsExt, Spanning,
};
/// Expands an [`Into`] derive macro.
pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
let input = &input.replace_self_type();
let attr_name = format_ident!("into");
let data = match &input.data {
syn::Data::Struct(data) => Ok(data),
syn::Data::Enum(e) => Err(syn::Error::new(
e.enum_token.span(),
"`Into` cannot be derived for enums",
)),
syn::Data::Union(u) => Err(syn::Error::new(
u.union_token.span(),
"`Into` cannot be derived for unions",
)),
}?;
let struct_attr = StructAttribute::parse_attrs_with(
&input.attrs,
&attr_name,
&ConsiderLegacySyntax {
fields: &data.fields,
},
)?
.map(Spanning::into_inner);
let fields_data = data
.fields
.iter()
.enumerate()
.map(|(i, f)| {
let field_attr = FieldAttribute::parse_attrs_with(
&f.attrs,
&attr_name,
&ConsiderLegacySyntax {
fields: slice::from_ref(f),
},
)?
.map(Spanning::into_inner);
let skip = field_attr
.as_ref()
.map(|attr| attr.skip.is_some())
.unwrap_or(false);
let convs = field_attr.and_then(|attr| attr.convs);
Ok(((i, f, skip), convs))
})
.collect::<syn::Result<Vec<_>>>()?;
let (fields, fields_convs): (Vec<_>, Vec<_>) = fields_data.into_iter().unzip();
let struct_attr = struct_attr.or_else(|| {
fields_convs
.iter()
.all(Option::is_none)
.then(ConversionsAttribute::default)
.map(Either::Right)
});
let mut expansions: Vec<_> = fields
.iter()
.zip(fields_convs)
.filter_map(|(&(i, field, _), convs)| {
convs.map(|convs| Expansion {
input_ident: &input.ident,
input_generics: &input.generics,
fields: vec![(i, field)],
convs,
})
})
.collect();
if let Some(attr) = struct_attr {
expansions.push(Expansion {
input_ident: &input.ident,
input_generics: &input.generics,
fields: fields
.into_iter()
.filter_map(|(i, f, skip)| (!skip).then_some((i, f)))
.collect(),
convs: attr.into(),
});
}
expansions.into_iter().map(Expansion::expand).collect()
}
/// Expansion of an [`Into`] derive macro, generating [`From`] implementations for a struct.
struct Expansion<'a> {
/// [`syn::Ident`] of the struct.
///
/// [`syn::Ident`]: struct@syn::Ident
input_ident: &'a syn::Ident,
/// [`syn::Generics`] of the struct.
input_generics: &'a syn::Generics,
/// Fields to convert from, along with their indices.
fields: Vec<(usize, &'a syn::Field)>,
/// Conversions to be generated.
convs: ConversionsAttribute,
}
impl Expansion<'_> {
fn expand(self) -> syn::Result<TokenStream> {
let Self {
input_ident,
input_generics,
fields,
convs,
} = self;
let fields_idents: Vec<_> = fields
.iter()
.map(|(i, f)| {
f.ident
.as_ref()
.map_or_else(|| Either::Left(syn::Index::from(*i)), Either::Right)
})
.collect();
let fields_tys: Vec<_> = fields.iter().map(|(_, f)| &f.ty).collect();
let fields_tuple = syn::Type::Tuple(syn::TypeTuple {
paren_token: token::Paren::default(),
elems: fields_tys.iter().cloned().cloned().collect(),
});
[
(&convs.owned, false, false),
(&convs.r#ref, true, false),
(&convs.ref_mut, true, true),
]
.into_iter()
.filter(|(conv, _, _)| conv.consider_fields_ty || !conv.tys.is_empty())
.map(|(conv, ref_, mut_)| {
let lf = ref_.then(|| syn::Lifetime::new("'__derive_more_into", Span::call_site()));
let r = ref_.then(token::And::default);
let m = mut_.then(token::Mut::default);
let gens = if let Some(lf) = lf.clone() {
let mut gens = input_generics.clone();
gens.params.push(syn::LifetimeParam::new(lf).into());
Cow::Owned(gens)
} else {
Cow::Borrowed(input_generics)
};
let (impl_gens, _, where_clause) = gens.split_for_impl();
let (_, ty_gens, _) = input_generics.split_for_impl();
if conv.consider_fields_ty {
Either::Left(iter::once(&fields_tuple))
} else {
Either::Right(iter::empty())
}
.chain(&conv.tys)
.map(|out_ty| {
let tys: Vec<_> = fields_tys.validate_type(out_ty)?.collect();
Ok(quote! {
#[allow(clippy::unused_unit)]
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[automatically_derived]
impl #impl_gens derive_more::core::convert::From<#r #lf #m #input_ident #ty_gens>
for ( #( #r #lf #m #tys ),* ) #where_clause
{
#[inline]
fn from(value: #r #lf #m #input_ident #ty_gens) -> Self {
(#(
<#r #m #tys as derive_more::core::convert::From<_>>::from(
#r #m value. #fields_idents
)
),*)
}
}
})
})
.collect::<syn::Result<TokenStream>>()
})
.collect()
}
}
/// Representation of an [`Into`] derive macro struct container attribute.
///
/// ```rust,ignore
/// #[into]
/// #[into(<types>)]
/// #[into(owned(<types>), ref(<types>), ref_mut(<types>))]
/// ```
type StructAttribute = Either<attr::Empty, ConversionsAttribute>;
impl From<StructAttribute> for ConversionsAttribute {
fn from(v: StructAttribute) -> Self {
match v {
Either::Left(_) => ConversionsAttribute::default(),
Either::Right(c) => c,
}
}
}
type Untyped = Either<attr::Skip, Either<attr::Empty, ConversionsAttribute>>;
impl From<Untyped> for FieldAttribute {
fn from(v: Untyped) -> Self {
match v {
Untyped::Left(skip) => Self {
skip: Some(skip),
convs: None,
},
Untyped::Right(c) => Self {
skip: None,
convs: Some(match c {
Either::Left(_empty) => ConversionsAttribute::default(),
Either::Right(convs) => convs,
}),
},
}
}
}
/// Representation of an [`Into`] derive macro field attribute.
///
/// ```rust,ignore
/// #[into]
/// #[into(<types>)]
/// #[into(owned(<types>), ref(<types>), ref_mut(<types>))]
/// #[into(skip)] #[into(ignore)]
/// ```
#[derive(Clone, Debug)]
struct FieldAttribute {
skip: Option<attr::Skip>,
convs: Option<ConversionsAttribute>,
}
impl Parse for FieldAttribute {
fn parse(_: ParseStream<'_>) -> syn::Result<Self> {
unreachable!("call `attr::ParseMultiple::parse_attr_with()` instead")
}
}
impl attr::ParseMultiple for FieldAttribute {
fn parse_attr_with<P: attr::Parser>(
attr: &syn::Attribute,
parser: &P,
) -> syn::Result<Self> {
Untyped::parse_attr_with(attr, parser).map(Self::from)
}
fn merge_attrs(
prev: Spanning<Self>,
new: Spanning<Self>,
name: &syn::Ident,
) -> syn::Result<Spanning<Self>> {
let skip = attr::Skip::merge_opt_attrs(
prev.clone().map(|v| v.skip).transpose(),
new.clone().map(|v| v.skip).transpose(),
name,
)?
.map(Spanning::into_inner);
let convs = ConversionsAttribute::merge_opt_attrs(
prev.clone().map(|v| v.convs).transpose(),
new.clone().map(|v| v.convs).transpose(),
name,
)?
.map(Spanning::into_inner);
Ok(Spanning::new(
Self { skip, convs },
prev.span.join(new.span).unwrap_or(prev.span),
))
}
}
/// [`Into`] conversions specified by a [`ConversionsAttribute`].
#[derive(Clone, Debug, Default)]
struct Conversions {
/// Indicator whether these [`Conversions`] should contain a conversion into fields type.
consider_fields_ty: bool,
/// [`syn::Type`]s explicitly specified in a [`ConversionsAttribute`].
tys: Punctuated<syn::Type, token::Comma>,
}
/// Representation of an [`Into`] derive macro attribute describing specified [`Into`] conversions.
///
/// ```rust,ignore
/// #[into(<types>)]
/// #[into(owned(<types>), ref(<types>), ref_mut(<types>))]
/// ```
#[derive(Clone, Debug)]
struct ConversionsAttribute {
/// [`syn::Type`]s wrapped into `owned(...)` or simply `#[into(...)]`.
owned: Conversions,
/// [`syn::Type`]s wrapped into `ref(...)`.
r#ref: Conversions,
/// [`syn::Type`]s wrapped into `ref_mut(...)`.
ref_mut: Conversions,
}
impl Default for ConversionsAttribute {
fn default() -> Self {
Self {
owned: Conversions {
consider_fields_ty: true,
tys: Punctuated::new(),
},
r#ref: Conversions::default(),
ref_mut: Conversions::default(),
}
}
}
impl Parse for ConversionsAttribute {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut out = Self {
owned: Conversions::default(),
r#ref: Conversions::default(),
ref_mut: Conversions::default(),
};
let parse_inner = |ahead, convs: &mut Conversions| {
input.advance_to(&ahead);
if input.peek(token::Paren) {
let inner;
syn::parenthesized!(inner in input);
convs.tys.extend(
inner
.parse_terminated(syn::Type::parse, token::Comma)?
.into_pairs(),
);
} else {
convs.consider_fields_ty = true;
}
if input.peek(token::Comma) {
let comma = input.parse::<token::Comma>()?;
if !convs.tys.empty_or_trailing() {
convs.tys.push_punct(comma);
}
}
Ok(())
};
let mut has_wrapped_type = false;
let mut top_level_type = None;
while !input.is_empty() {
let ahead = input.fork();
let res = if ahead.peek(syn::Ident::peek_any) {
ahead.call(syn::Ident::parse_any).map(Into::into)
} else {
ahead.parse::<syn::Path>()
};
match res {
Ok(p) if p.is_ident("owned") => {
has_wrapped_type = true;
parse_inner(ahead, &mut out.owned)?;
}
Ok(p) if p.is_ident("ref") => {
has_wrapped_type = true;
parse_inner(ahead, &mut out.r#ref)?;
}
Ok(p) if p.is_ident("ref_mut") => {
has_wrapped_type = true;
parse_inner(ahead, &mut out.ref_mut)?;
}
_ => {
let ty = input.parse::<syn::Type>()?;
let _ = top_level_type.get_or_insert_with(|| ty.clone());
out.owned.tys.push_value(ty);
if input.peek(token::Comma) {
out.owned.tys.push_punct(input.parse::<token::Comma>()?)
}
}
}
}
if let Some(ty) = top_level_type.filter(|_| has_wrapped_type) {
Err(syn::Error::new(
ty.span(),
format!(
"mixing regular types with wrapped into `owned`/`ref`/`ref_mut` is not \
allowed, try wrapping this type into `owned({ty}), ref({ty}), ref_mut({ty})`",
ty = ty.into_token_stream(),
),
))
} else {
Ok(out)
}
}
}
impl attr::ParseMultiple for ConversionsAttribute {
fn merge_attrs(
prev: Spanning<Self>,
new: Spanning<Self>,
_: &syn::Ident,
) -> syn::Result<Spanning<Self>> {
let Spanning {
span: prev_span,
item: mut prev,
} = prev;
let Spanning {
span: new_span,
item: new,
} = new;
prev.owned.tys.extend(new.owned.tys);
prev.owned.consider_fields_ty |= new.owned.consider_fields_ty;
prev.r#ref.tys.extend(new.r#ref.tys);
prev.r#ref.consider_fields_ty |= new.r#ref.consider_fields_ty;
prev.ref_mut.tys.extend(new.ref_mut.tys);
prev.ref_mut.consider_fields_ty |= new.ref_mut.consider_fields_ty;
Ok(Spanning::new(
prev,
prev_span.join(new_span).unwrap_or(prev_span),
))
}
}
/// [`attr::Parser`] considering legacy syntax and performing [`check_legacy_syntax()`] for a
/// [`StructAttribute`] or a [`FieldAttribute`].
struct ConsiderLegacySyntax<F> {
/// [`syn::Field`]s the [`StructAttribute`] or [`FieldAttribute`] is parsed for.
fields: F,
}
impl<'a, F> attr::Parser for ConsiderLegacySyntax<&'a F>
where
F: FieldsExt + ?Sized,
&'a F: IntoIterator<Item = &'a syn::Field>,
{
fn parse<T: Parse + Any>(&self, input: ParseStream<'_>) -> syn::Result<T> {
if TypeId::of::<T>() == TypeId::of::<ConversionsAttribute>() {
check_legacy_syntax(input, self.fields)?;
}
T::parse(input)
}
}
/// [`Error`]ors for legacy syntax: `#[into(types(i32, "&str"))]`.
///
/// [`Error`]: syn::Error
fn check_legacy_syntax<'a, F>(tokens: ParseStream<'_>, fields: &'a F) -> syn::Result<()>
where
F: FieldsExt + ?Sized,
&'a F: IntoIterator<Item = &'a syn::Field>,
{
let span = tokens.span();
let tokens = tokens.fork();
let map_ty = |s: String| {
if fields.len() > 1 {
format!(
"({})",
(0..fields.len())
.map(|_| s.as_str())
.collect::<Vec<_>>()
.join(", ")
)
} else {
s
}
};
let field = match fields.len() {
0 => None,
1 => Some(
fields
.into_iter()
.next()
.unwrap_or_else(|| unreachable!("fields.len() == 1"))
.ty
.to_token_stream()
.to_string(),
),
_ => Some(format!(
"({})",
fields
.into_iter()
.map(|f| f.ty.to_token_stream().to_string())
.collect::<Vec<_>>()
.join(", ")
)),
};
let Ok(metas) = tokens.parse_terminated(polyfill::Meta::parse, token::Comma) else {
return Ok(());
};
let parse_list = |list: polyfill::MetaList, attrs: &mut Option<Vec<_>>| {
if !list.path.is_ident("types") {
return None;
}
for meta in list
.parse_args_with(Punctuated::<_, token::Comma>::parse_terminated)
.ok()?
{
attrs.get_or_insert_with(Vec::new).push(match meta {
polyfill::NestedMeta::Lit(syn::Lit::Str(str)) => str.value(),
polyfill::NestedMeta::Meta(polyfill::Meta::Path(path)) => {
path.into_token_stream().to_string()
}
_ => return None,
})
}
Some(())
};
let Some((top_level, owned, ref_, ref_mut)) = metas
.into_iter()
.try_fold(
(None, None, None, None),
|(mut top_level, mut owned, mut ref_, mut ref_mut), meta| {
let is = |name| {
matches!(&meta, polyfill::Meta::Path(p) if p.is_ident(name))
|| matches!(&meta, polyfill::Meta::List(list) if list.path.is_ident(name))
};
let parse_inner = |meta, attrs: &mut Option<_>| {
match meta {
polyfill::Meta::Path(_) => {
let _ = attrs.get_or_insert_with(Vec::new);
Some(())
}
polyfill::Meta::List(list) => {
if let polyfill::NestedMeta::Meta(polyfill::Meta::List(list)) = list
.parse_args_with(Punctuated::<_, token::Comma>::parse_terminated)
.ok()?
.pop()?
.into_value()
{
parse_list(list, attrs)
} else {
None
}
}
}
};
match meta {
meta if is("owned") => parse_inner(meta, &mut owned),
meta if is("ref") => parse_inner(meta, &mut ref_),
meta if is("ref_mut") => parse_inner(meta, &mut ref_mut),
polyfill::Meta::List(list) => parse_list(list, &mut top_level),
_ => None,
}
.map(|_| (top_level, owned, ref_, ref_mut))
},
)
.filter(|(top_level, owned, ref_, ref_mut)| {
[top_level, owned, ref_, ref_mut]
.into_iter()
.any(|l| l.as_ref().is_some_and(|l| !l.is_empty()))
})
else {
return Ok(());
};
if [&owned, &ref_, &ref_mut].into_iter().any(Option::is_some) {
let format = |list: Option<Vec<_>>, name: &str| match list {
Some(l)
if top_level.as_ref().map_or(true, Vec::is_empty) && l.is_empty() =>
{
Some(name.to_owned())
}
Some(l) => Some(format!(
"{}({})",
name,
l.into_iter()
.chain(top_level.clone().into_iter().flatten())
.map(map_ty)
.chain(field.clone())
.collect::<Vec<_>>()
.join(", "),
)),
None => None,
};
let format = [
format(owned, "owned"),
format(ref_, "ref"),
format(ref_mut, "ref_mut"),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
.join(", ");
Err(syn::Error::new(
span,
format!("legacy syntax, use `{format}` instead"),
))
} else {
Err(syn::Error::new(
span,
format!(
"legacy syntax, remove `types` and use `{}` instead",
top_level.unwrap_or_else(|| unreachable!()).join(", "),
),
))
}
}
@@ -0,0 +1,62 @@
use crate::utils::{
add_extra_generic_param, add_extra_where_clauses, SingleFieldData, State,
};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse::Result, DeriveInput};
/// Provides the hook to expand `#[derive(IntoIterator)]` into an implementation of `IntoIterator`
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state =
State::with_field_ignore_and_refs(input, trait_name, "into_iterator".into())?;
let SingleFieldData {
input_type,
info,
field_type,
member,
trait_path,
..
} = state.assert_single_enabled_field();
let mut tokens = TokenStream::new();
for ref_type in info.ref_types() {
let reference = ref_type.reference();
let lifetime = ref_type.lifetime();
let reference_with_lifetime = ref_type.reference_with_lifetime();
let generics_impl;
let (impl_generics, _, _) = if ref_type.is_ref() {
generics_impl = add_extra_generic_param(&input.generics, lifetime.clone());
generics_impl.split_for_impl()
} else {
input.generics.split_for_impl()
};
let generics = add_extra_where_clauses(
&input.generics,
quote! { where #reference_with_lifetime #field_type: #trait_path },
);
let (_, ty_generics, where_clause) = generics.split_for_impl();
let casted_trait = &quote! {
<#reference_with_lifetime #field_type as #trait_path>
};
let into_iterator = quote! {
#[automatically_derived]
impl #impl_generics #trait_path for #reference_with_lifetime #input_type #ty_generics
#where_clause
{
type Item = #casted_trait::Item;
type IntoIter = #casted_trait::IntoIter;
#[inline]
fn into_iter(self) -> Self::IntoIter {
#casted_trait::into_iter(#reference #member)
}
}
};
into_iterator.to_tokens(&mut tokens);
}
Ok(tokens)
}
+65
View File
@@ -0,0 +1,65 @@
use crate::utils::{AttrParams, DeriveType, State};
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{DeriveInput, Fields, Result};
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state = State::with_attr_params(
input,
trait_name,
"is_variant".into(),
AttrParams {
enum_: vec!["ignore"],
variant: vec!["ignore"],
struct_: vec!["ignore"],
field: vec!["ignore"],
},
)?;
assert!(
state.derive_type == DeriveType::Enum,
"IsVariant can only be derived for enums",
);
let enum_name = &input.ident;
let (imp_generics, type_generics, where_clause) = input.generics.split_for_impl();
let mut funcs = vec![];
for variant_state in state.enabled_variant_data().variant_states {
let variant = variant_state.variant.unwrap();
let fn_name = format_ident!(
"is_{}",
variant.ident.to_string().to_case(Case::Snake),
span = variant.ident.span(),
);
let variant_ident = &variant.ident;
let data_pattern = match variant.fields {
Fields::Named(_) => quote! { {..} },
Fields::Unnamed(_) => quote! { (..) },
Fields::Unit => quote! {},
};
let func = quote! {
#[doc = "Returns `true` if this value is of type `"]
#[doc = stringify!(#variant_ident)]
#[doc = "`. Returns `false` otherwise"]
#[inline]
#[must_use]
pub const fn #fn_name(&self) -> bool {
derive_more::core::matches!(self, #enum_name ::#variant_ident #data_pattern)
}
};
funcs.push(func);
}
let imp = quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #imp_generics #enum_name #type_generics #where_clause {
#(#funcs)*
}
};
Ok(imp)
}
+297
View File
@@ -0,0 +1,297 @@
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![recursion_limit = "128"]
#![cfg_attr(any(not(docsrs), ci), deny(rustdoc::all))]
#![forbid(non_ascii_idents, unsafe_code)]
#![warn(clippy::nonstandard_macro_braces)]
use proc_macro::TokenStream;
use syn::parse::Error as ParseError;
mod utils;
#[cfg(feature = "as_ref")]
mod r#as;
#[cfg(feature = "eq")]
mod cmp;
#[cfg(feature = "constructor")]
mod constructor;
#[cfg(feature = "deref")]
mod deref;
#[cfg(feature = "deref_mut")]
mod deref_mut;
#[cfg(feature = "error")]
mod error;
#[cfg(any(feature = "debug", feature = "display"))]
mod fmt;
#[cfg(feature = "from")]
mod from;
#[cfg(feature = "from_str")]
mod from_str;
#[cfg(feature = "index")]
mod index;
#[cfg(feature = "index_mut")]
mod index_mut;
#[cfg(feature = "into")]
mod into;
#[cfg(feature = "into_iterator")]
mod into_iterator;
#[cfg(feature = "is_variant")]
mod is_variant;
#[cfg(feature = "not")]
mod not_like;
#[cfg(any(
feature = "add",
feature = "add_assign",
feature = "mul",
feature = "mul_assign"
))]
mod ops;
#[cfg(any(feature = "debug", feature = "display"))]
pub(crate) mod parsing;
#[cfg(feature = "sum")]
mod sum_like;
#[cfg(feature = "try_from")]
mod try_from;
#[cfg(feature = "try_into")]
mod try_into;
#[cfg(feature = "try_unwrap")]
mod try_unwrap;
#[cfg(feature = "unwrap")]
mod unwrap;
// This trait describes the possible return types of
// the derives. A derive can generally be infallible and
// return a TokenStream, or it can be fallible and return
// a Result<TokenStream, syn::parse::Error>.
//
// This trait can be unused if no feature is enabled. We already error in that case but this
// warning distracts from the actual error.
#[allow(dead_code)]
trait Output {
fn process(self) -> TokenStream;
}
impl Output for proc_macro2::TokenStream {
fn process(self) -> TokenStream {
self.into()
}
}
impl Output for Result<proc_macro2::TokenStream, ParseError> {
fn process(self) -> TokenStream {
match self {
Ok(ts) => ts.into(),
Err(e) => e.to_compile_error().into(),
}
}
}
macro_rules! create_derive(
($feature:literal, $mod_:ident $(:: $mod_rest:ident)*, $trait_:ident, $fn_name: ident $(,$attribute:ident)* $(,)?) => {
#[cfg(feature = $feature)]
#[proc_macro_derive($trait_, attributes($($attribute),*))]
#[doc = include_str!(concat!("../doc/", $feature, ".md"))]
pub fn $fn_name(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
Output::process($mod_$(:: $mod_rest)*::expand(&ast, stringify!($trait_)))
}
}
);
create_derive!("add", ops::add, Add, add_derive, add);
create_derive!("add", ops::add, Sub, sub_derive, sub);
create_derive!("add", ops::add, BitAnd, bit_and_derive, bitand);
create_derive!("add", ops::add, BitOr, bit_or_derive, bitor);
create_derive!("add", ops::add, BitXor, bit_xor_derive, bitxor);
create_derive!(
"add_assign",
ops::add_assign,
AddAssign,
add_assign_derive,
add_assign,
);
create_derive!(
"add_assign",
ops::add_assign,
SubAssign,
sub_assign_derive,
sub_assign,
);
create_derive!(
"add_assign",
ops::add_assign,
BitAndAssign,
bit_and_assign_derive,
bitand_assign,
);
create_derive!(
"add_assign",
ops::add_assign,
BitOrAssign,
bit_or_assign_derive,
bitor_assign,
);
create_derive!(
"add_assign",
ops::add_assign,
BitXorAssign,
bit_xor_assign_derive,
bitxor_assign,
);
create_derive!("as_ref", r#as::r#mut, AsMut, as_mut_derive, as_mut);
create_derive!("as_ref", r#as::r#ref, AsRef, as_ref_derive, as_ref);
create_derive!("constructor", constructor, Constructor, constructor_derive);
create_derive!("debug", fmt::debug, Debug, debug_derive, debug);
create_derive!("deref", deref, Deref, deref_derive, deref);
create_derive!(
"deref_mut",
deref_mut,
DerefMut,
deref_mut_derive,
deref_mut,
);
create_derive!("display", fmt::display, Display, display_derive, display);
create_derive!("display", fmt::display, Binary, binary_derive, binary);
create_derive!("display", fmt::display, Octal, octal_derive, octal);
create_derive!(
"display",
fmt::display,
LowerHex,
lower_hex_derive,
lower_hex,
);
create_derive!(
"display",
fmt::display,
UpperHex,
upper_hex_derive,
upper_hex,
);
create_derive!(
"display",
fmt::display,
LowerExp,
lower_exp_derive,
lower_exp,
);
create_derive!(
"display",
fmt::display,
UpperExp,
upper_exp_derive,
upper_exp,
);
create_derive!("display", fmt::display, Pointer, pointer_derive, pointer);
create_derive!("eq", cmp::eq, Eq, eq_derive, eq);
create_derive!(
"eq",
cmp::partial_eq,
PartialEq,
partial_eq_derive,
partial_eq,
);
create_derive!("error", error, Error, error_derive, error);
create_derive!("from", from, From, from_derive, from);
create_derive!("from_str", from_str, FromStr, from_str_derive, from_str);
create_derive!("index", index, Index, index_derive, index);
create_derive!(
"index_mut",
index_mut,
IndexMut,
index_mut_derive,
index_mut,
);
create_derive!("into", into, Into, into_derive, into);
create_derive!(
"into_iterator",
into_iterator,
IntoIterator,
into_iterator_derive,
into_iterator,
);
create_derive!(
"is_variant",
is_variant,
IsVariant,
is_variant_derive,
is_variant,
);
create_derive!("mul", ops::mul, Mul, mul_derive, mul);
create_derive!("mul", ops::mul, Div, div_derive, div);
create_derive!("mul", ops::mul, Rem, rem_derive, rem);
create_derive!("mul", ops::mul, Shr, shr_derive, shr);
create_derive!("mul", ops::mul, Shl, shl_derive, shl);
create_derive!(
"mul_assign",
ops::mul_assign,
MulAssign,
mul_assign_derive,
mul_assign,
);
create_derive!(
"mul_assign",
ops::mul_assign,
DivAssign,
div_assign_derive,
div_assign,
);
create_derive!(
"mul_assign",
ops::mul_assign,
RemAssign,
rem_assign_derive,
rem_assign,
);
create_derive!(
"mul_assign",
ops::mul_assign,
ShrAssign,
shr_assign_derive,
shr_assign,
);
create_derive!(
"mul_assign",
ops::mul_assign,
ShlAssign,
shl_assign_derive,
shl_assign,
);
create_derive!("not", not_like, Not, not_derive);
create_derive!("not", not_like, Neg, neg_derive);
create_derive!("sum", sum_like, Sum, sum_derive);
create_derive!("sum", sum_like, Product, product_derive);
create_derive!("try_from", try_from, TryFrom, try_from_derive, try_from);
create_derive!("try_into", try_into, TryInto, try_into_derive, try_into);
create_derive!(
"try_unwrap",
try_unwrap,
TryUnwrap,
try_unwrap_derive,
try_unwrap,
);
create_derive!("unwrap", unwrap, Unwrap, unwrap_derive, unwrap);
+176
View File
@@ -0,0 +1,176 @@
use crate::utils::{
add_extra_type_param_bound_op_output, named_to_vec, unnamed_to_vec,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use std::iter;
use syn::{Data, DataEnum, DeriveInput, Field, Fields, Ident, Index};
pub fn expand(input: &DeriveInput, trait_name: &str) -> TokenStream {
let trait_ident = format_ident!("{trait_name}");
let method_name = trait_name.to_lowercase();
let method_ident = format_ident!("{method_name}");
let input_type = &input.ident;
let generics = add_extra_type_param_bound_op_output(&input.generics, &trait_ident);
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let (output_type, block) = match input.data {
Data::Struct(ref data_struct) => match data_struct.fields {
Fields::Unnamed(ref fields) => (
quote! { #input_type #ty_generics },
tuple_content(input_type, &unnamed_to_vec(fields), &method_ident),
),
Fields::Named(ref fields) => (
quote! { #input_type #ty_generics },
struct_content(input_type, &named_to_vec(fields), &method_ident),
),
_ => panic!("Unit structs cannot use derive({trait_name})"),
},
Data::Enum(ref data_enum) => {
enum_output_type_and_content(input, data_enum, &method_ident)
}
_ => panic!("Only structs and enums can use derive({trait_name})"),
};
quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #impl_generics derive_more::core::ops::#trait_ident
for #input_type #ty_generics #where_clause {
type Output = #output_type;
#[inline]
fn #method_ident(self) -> #output_type {
#block
}
}
}
}
fn tuple_content<T: ToTokens>(
input_type: &T,
fields: &[&Field],
method_ident: &Ident,
) -> TokenStream {
let mut exprs = vec![];
for i in 0..fields.len() {
let i = Index::from(i);
// generates `self.0.add()`
let expr = quote! { self.#i.#method_ident() };
exprs.push(expr);
}
quote! { #input_type(#(#exprs),*) }
}
fn struct_content(
input_type: &Ident,
fields: &[&Field],
method_ident: &Ident,
) -> TokenStream {
let mut exprs = vec![];
for field in fields {
// It's safe to unwrap because struct fields always have an identifier
let field_id = field.ident.as_ref();
// generates `x: self.x.not()`
let expr = quote! { #field_id: self.#field_id.#method_ident() };
exprs.push(expr)
}
quote! { #input_type{#(#exprs),*} }
}
fn enum_output_type_and_content(
input: &DeriveInput,
data_enum: &DataEnum,
method_ident: &Ident,
) -> (TokenStream, TokenStream) {
let input_type = &input.ident;
let (_, ty_generics, _) = input.generics.split_for_impl();
let mut matches = vec![];
let mut method_iter = iter::repeat(method_ident);
// If the enum contains unit types that means it can error.
let has_unit_type = data_enum.variants.iter().any(|v| v.fields == Fields::Unit);
for variant in &data_enum.variants {
let subtype = &variant.ident;
let subtype = quote! { #input_type::#subtype };
match variant.fields {
Fields::Unnamed(ref fields) => {
// The pattern that is outputted should look like this:
// (Subtype(vars)) => Ok(TypePath(exprs))
let size = unnamed_to_vec(fields).len();
let vars: &Vec<_> =
&(0..size).map(|i| format_ident!("__{i}")).collect();
let method_iter = method_iter.by_ref();
let mut body = quote! { #subtype(#(#vars.#method_iter()),*) };
if has_unit_type {
body = quote! { derive_more::core::result::Result::Ok(#body) }
}
let matcher = quote! {
#subtype(#(#vars),*) => {
#body
}
};
matches.push(matcher);
}
Fields::Named(ref fields) => {
// The pattern that is outputted should look like this:
// (Subtype{a: __l_a, ...} => {
// Ok(Subtype{a: __l_a.neg(__r_a), ...})
// }
let field_vec = named_to_vec(fields);
let size = field_vec.len();
let field_names: &Vec<_> = &field_vec
.iter()
.map(|f| f.ident.as_ref().unwrap())
.collect();
let vars: &Vec<_> =
&(0..size).map(|i| format_ident!("__{i}")).collect();
let method_iter = method_iter.by_ref();
let mut body = quote! {
#subtype{#(#field_names: #vars.#method_iter()),*}
};
if has_unit_type {
body = quote! { derive_more::core::result::Result::Ok(#body) }
}
let matcher = quote! {
#subtype{#(#field_names: #vars),*} => {
#body
}
};
matches.push(matcher);
}
Fields::Unit => {
let operation_name = method_ident.to_string();
matches.push(quote! {
#subtype => derive_more::core::result::Result::Err(
derive_more::UnitError::new(#operation_name)
)
});
}
}
}
let body = quote! {
match self {
#(#matches),*
}
};
let output_type = if has_unit_type {
quote! {
derive_more::core::result::Result<#input_type #ty_generics, derive_more::UnitError>
}
} else {
quote! { #input_type #ty_generics }
};
(output_type, body)
}
+145
View File
@@ -0,0 +1,145 @@
//! Implementation of [`ops::Add`]-like derive macros.
#[cfg(doc)]
use std::ops;
use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens as _};
use syn::spanned::Spanned as _;
use super::{SkippedFields, StructuralExpansion};
use crate::utils::attr::{self, ParseMultiple as _};
/// Expands an [`ops::Add`]-like derive macro.
///
/// Available macros:
/// - [`Add`](ops::Add)
/// - [`BitAnd`](ops::BitAnd)
/// - [`BitOr`](ops::BitOr)
/// - [`BitXor`](ops::BitXor)
/// - [`Sub`](ops::Sub)
pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
let trait_name = normalize_trait_name(trait_name);
let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
let mut variants = vec![];
match &input.data {
syn::Data::Struct(data) => {
if let Some(skip) = attr::Skip::parse_attrs(&input.attrs, &attr_name)? {
return Err(syn::Error::new(
skip.span,
format!(
"`#[{attr_name}({})]` attribute can be placed only on struct fields",
skip.item.name(),
),
));
} else if matches!(data.fields, syn::Fields::Unit) {
return Err(syn::Error::new(
data.struct_token.span(),
format!("`{trait_name}` cannot be derived for unit structs"),
));
}
let mut skipped_fields = SkippedFields::default();
for (n, field) in data.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if data.fields.len() == skipped_fields.len() {
return Err(syn::Error::new(
data.struct_token.span(),
format!(
"`{trait_name}` cannot be derived for structs with all the fields being \
skipped",
),
));
}
variants.push((None, &data.fields, skipped_fields));
}
syn::Data::Enum(data) => {
if let Some(skip) = attr::Skip::parse_attrs(&input.attrs, &attr_name)? {
return Err(syn::Error::new(
skip.span,
format!(
"`#[{attr_name}({})]` attribute can be placed only on enum fields",
skip.item.name(),
),
));
}
for variant in &data.variants {
if let Some(skip) = attr::Skip::parse_attrs(&variant.attrs, &attr_name)?
{
return Err(syn::Error::new(
skip.span,
format!(
"`#[{attr_name}({})]` attribute can be placed only on variant fields",
skip.item.name(),
),
));
}
let mut skipped_fields = SkippedFields::default();
for (n, field) in variant.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if !matches!(variant.fields, syn::Fields::Unit)
&& variant.fields.len() == skipped_fields.len()
{
return Err(syn::Error::new(
variant.span(),
format!(
"`{trait_name}` cannot be derived for enum with all the fields being \
skipped in its variants",
),
));
}
variants.push((Some(&variant.ident), &variant.fields, skipped_fields));
}
}
syn::Data::Union(data) => {
return Err(syn::Error::new(
data.union_token.span(),
format!("`{trait_name}` cannot be derived for unions"),
));
}
}
Ok(StructuralExpansion {
trait_ty: format_ident!("{trait_name}"),
method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
self_ty: (&input.ident, &input.generics),
variants,
is_enum: matches!(input.data, syn::Data::Enum(_)),
}
.into_token_stream())
}
/// Matches the provided derive macro `name` to appropriate actual trait name.
fn normalize_trait_name(name: &str) -> &'static str {
match name {
"Add" => "Add",
"BitAnd" => "BitAnd",
"BitOr" => "BitOr",
"BitXor" => "BitXor",
"Sub" => "Sub",
_ => unimplemented!(),
}
}
/// Matches the provided [`ops::Add`]-like trait `name` to its attribute's name.
fn trait_name_to_attribute_name(name: &str) -> &'static str {
trait_name_to_method_name(name)
}
/// Matches the provided [`ops::Add`]-like trait `name` to its method name.
fn trait_name_to_method_name(name: &str) -> &'static str {
match name {
"Add" => "add",
"BitAnd" => "bitand",
"BitOr" => "bitor",
"BitXor" => "bitxor",
"Sub" => "sub",
_ => unimplemented!(),
}
}
@@ -0,0 +1,110 @@
//! Implementation of [`ops::AddAssign`]-like derive macros.
#[cfg(doc)]
use std::ops;
use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens as _};
use syn::spanned::Spanned as _;
use super::{AssignStructuralExpansion, SkippedFields};
use crate::utils::attr::{self, ParseMultiple as _};
/// Expands an [`ops::AddAssign`]-like derive macro.
///
/// Available macros:
/// - [`AddAssign`](ops::AddAssign)
/// - [`BitAndAssign`](ops::BitAndAssign)
/// - [`BitOrAssign`](ops::BitOrAssign)
/// - [`BitXorAssign`](ops::BitXorAssign)
/// - [`SubAssign`](ops::SubAssign)
pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
let trait_name = normalize_trait_name(trait_name);
let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
let mut variants = vec![];
match &input.data {
syn::Data::Struct(data) => {
if let Some(skip) = attr::Skip::parse_attrs(&input.attrs, &attr_name)? {
return Err(syn::Error::new(
skip.span,
format!(
"`#[{attr_name}({})]` attribute can be placed only on struct fields",
skip.item.name(),
),
));
} else if matches!(data.fields, syn::Fields::Unit) {
return Err(syn::Error::new(
data.struct_token.span(),
format!("`{trait_name}` cannot be derived for unit structs"),
));
}
let mut skipped_fields = SkippedFields::default();
for (n, field) in data.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if data.fields.len() == skipped_fields.len() {
return Err(syn::Error::new(
data.struct_token.span(),
format!(
"`{trait_name}` cannot be derived for structs with all the fields being \
skipped",
),
));
}
variants.push((None, &data.fields, skipped_fields));
}
syn::Data::Enum(data) => {
return Err(syn::Error::new(
data.enum_token.span(),
format!("`{trait_name}` cannot be derived for enums"),
));
}
syn::Data::Union(data) => {
return Err(syn::Error::new(
data.union_token.span(),
format!("`{trait_name}` cannot be derived for unions"),
));
}
}
Ok(AssignStructuralExpansion {
trait_ty: format_ident!("{trait_name}"),
method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
self_ty: (&input.ident, &input.generics),
variants,
is_enum: false,
}
.into_token_stream())
}
/// Matches the provided derive macro `name` to appropriate actual trait name.
fn normalize_trait_name(name: &str) -> &'static str {
match name {
"AddAssign" => "AddAssign",
"BitAndAssign" => "BitAndAssign",
"BitOrAssign" => "BitOrAssign",
"BitXorAssign" => "BitXorAssign",
"SubAssign" => "SubAssign",
_ => unimplemented!(),
}
}
/// Matches the provided [`ops::AddAssign`]-like trait `name` to its attribute's name.
fn trait_name_to_attribute_name(name: &str) -> &'static str {
trait_name_to_method_name(name)
}
/// Matches the provided [`ops::AddAssign`]-like trait `name` to its method name.
fn trait_name_to_method_name(name: &str) -> &'static str {
match name {
"AddAssign" => "add_assign",
"BitAndAssign" => "bitand_assign",
"BitOrAssign" => "bitor_assign",
"BitXorAssign" => "bitxor_assign",
"SubAssign" => "sub_assign",
_ => unimplemented!(),
}
}
+343
View File
@@ -0,0 +1,343 @@
//! Implementations of [`ops`]-related derive macros.
//!
//! [`ops`]: std::ops
#[cfg(feature = "add")]
pub(crate) mod add;
#[cfg(feature = "add_assign")]
pub(crate) mod add_assign;
#[cfg(feature = "mul")]
pub(crate) mod mul;
#[cfg(feature = "mul_assign")]
pub(crate) mod mul_assign;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::parse_quote;
#[cfg(doc)]
use crate::utils::attr;
use crate::utils::{
pattern_matching::FieldsExt as _, structural_inclusion::TypeExt as _,
GenericsSearch, HashSet,
};
/// Indices of [`syn::Field`]s marked with an [`attr::Skip`].
type SkippedFields = HashSet<usize>;
#[cfg(any(feature = "add_assign", feature = "mul_assign"))]
/// Expansion of a macro for generating a structural trait implementation with a `&mut self` method
/// receiver for an enum or a struct.
struct AssignStructuralExpansion<'i> {
/// [`syn::Ident`] of the implemented trait.
///
/// [`syn::Ident`]: struct@syn::Ident
trait_ty: syn::Ident,
/// [`syn::Ident`] and [`syn::Receiver`] of the implemented method in trait.
///
/// [`syn::Ident`]: struct@syn::Ident
method_ident: syn::Ident,
/// [`syn::Ident`] and [`syn::Generics`] of the implementor enum/struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Fields`] of the enum/struct to be used in this [`AssignStructuralExpansion`].
variants: Vec<(Option<&'i syn::Ident>, &'i syn::Fields, SkippedFields)>,
/// Indicator whether this expansion is for an enum.
is_enum: bool,
}
#[cfg(any(feature = "add_assign", feature = "mul_assign"))]
impl AssignStructuralExpansion<'_> {
/// Generates body of the method implementation for this [`StructuralExpansion`].
fn body(&self) -> TokenStream {
let method_path = {
let trait_ty = &self.trait_ty;
let method_ident = &self.method_ident;
quote! { derive_more::core::ops::#trait_ty::#method_ident }
};
let match_arms = self
.variants
.iter()
.map(|(variant, all_fields, skipped_fields)| {
let variant = variant.map(|variant| quote! { :: #variant });
let self_pat = all_fields.non_exhaustive_arm_pattern("__self_", skipped_fields);
let rhs_pat = all_fields.non_exhaustive_arm_pattern("__rhs_", skipped_fields);
let fields_exprs = (0..all_fields.len())
.filter(|num| !skipped_fields.contains(num))
.map(|num| {
let self_val = format_ident!("__self_{num}");
let rhs_val = format_ident!("__rhs_{num}");
quote! { #method_path(#self_val, #rhs_val); }
});
quote! {
(Self #variant #self_pat, Self #variant #rhs_pat) => { #( #fields_exprs )* }
}
})
.collect::<Vec<_>>();
let wrong_variant_arm = (self.is_enum && match_arms.len() > 1).then(|| {
quote! { _ => {} }
});
quote! {
match (self, __rhs) {
#( #match_arms )*
#wrong_variant_arm
}
}
}
}
#[cfg(any(feature = "add_assign", feature = "mul_assign"))]
impl ToTokens for AssignStructuralExpansion<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let trait_ty = &self.trait_ty;
let method_ident = &self.method_ident;
let ty = self.self_ty.0;
let (_, ty_generics, _) = self.self_ty.1.split_for_impl();
let implementor_ty: syn::Type = parse_quote! { #ty #ty_generics };
let self_ty: syn::Type = parse_quote! { Self };
let generics_search = GenericsSearch::from(self.self_ty.1);
let mut generics = self.self_ty.1.clone();
for (_, all_fields, skipped_fields) in &self.variants {
for field_ty in all_fields.iter().enumerate().filter_map(|(n, field)| {
(!skipped_fields.contains(&n)).then_some(&field.ty)
}) {
if generics_search.any_in(field_ty)
&& !field_ty.contains_type_structurally(&self_ty)
&& !field_ty.contains_type_structurally(&implementor_ty)
{
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty: derive_more::core::ops:: #trait_ty
});
}
}
}
let (impl_generics, _, where_clause) = generics.split_for_impl();
let body = self.body();
quote! {
#[allow(private_bounds)]
#[automatically_derived]
impl #impl_generics derive_more::core::ops:: #trait_ty for #implementor_ty
#where_clause
{
#[inline]
#[track_caller]
fn #method_ident(&mut self, __rhs: Self) {
#body
}
}
}
.to_tokens(tokens);
}
}
#[cfg(any(feature = "add", feature = "mul"))]
/// Expansion of a macro for generating a structural trait implementation with a `self` method
/// receiver for an enum or a struct.
struct StructuralExpansion<'i> {
/// [`syn::Ident`] of the implemented trait.
///
/// [`syn::Ident`]: struct@syn::Ident
trait_ty: syn::Ident,
/// [`syn::Ident`] and [`syn::Receiver`] of the implemented method in trait.
///
/// [`syn::Ident`]: struct@syn::Ident
method_ident: syn::Ident,
/// [`syn::Ident`] and [`syn::Generics`] of the implementor enum/struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Fields`] of the enum/struct to be used in this [`StructuralExpansion`].
variants: Vec<(Option<&'i syn::Ident>, &'i syn::Fields, SkippedFields)>,
/// Indicator whether this expansion is for an enum.
is_enum: bool,
}
#[cfg(any(feature = "add", feature = "mul"))]
impl StructuralExpansion<'_> {
/// Generates body of the method implementation for this [`StructuralExpansion`].
fn body(&self) -> TokenStream {
// TODO: Try remove once MSRV is bumped up.
// Special case: empty enum.
if self.is_enum && self.variants.is_empty() {
return quote! { match self {} };
}
let method_name = self.method_ident.to_string();
let method_path = {
let trait_ty = &self.trait_ty;
let method_ident = &self.method_ident;
parse_quote! { derive_more::core::ops::#trait_ty::#method_ident }
};
let match_arms = self
.variants
.iter()
.map(|(variant, all_fields, skipped_fields)| {
let variant = variant.map(|variant| quote! { :: #variant });
let self_pat = all_fields.exhaustive_arm_pattern("__self_");
let rhs_pat = all_fields.exhaustive_arm_pattern("__rhs_");
let expr = if matches!(all_fields, syn::Fields::Unit) {
quote! {
derive_more::core::result::Result::Err(derive_more::BinaryError::Unit(
derive_more::UnitError::new(#method_name)
))
}
} else {
let fields_expr = all_fields.arm_expr(&method_path, skipped_fields);
if self.is_enum {
quote! { derive_more::core::result::Result::Ok(Self #variant #fields_expr) }
} else {
quote! { Self #variant #fields_expr }
}
};
quote! {
(Self #variant #self_pat, Self #variant #rhs_pat) => #expr,
}
})
.collect::<Vec<_>>();
let wrong_variant_arm = (self.is_enum && match_arms.len() > 1).then(|| {
quote! {
_ => derive_more::core::result::Result::Err(derive_more::BinaryError::Mismatch(
derive_more::WrongVariantError::new(#method_name)
)),
}
});
quote! {
match (self, __rhs) {
#( #match_arms )*
#wrong_variant_arm
}
}
}
}
#[cfg(any(feature = "add", feature = "mul"))]
impl ToTokens for StructuralExpansion<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let trait_ty = &self.trait_ty;
let method_ident = &self.method_ident;
let ty = self.self_ty.0;
let (_, ty_generics, _) = self.self_ty.1.split_for_impl();
let implementor_ty: syn::Type = parse_quote! { #ty #ty_generics };
let self_ty: syn::Type = parse_quote! { Self };
let output_ty = if self.is_enum {
parse_quote! { derive_more::core::result::Result<#self_ty, derive_more::BinaryError> }
} else {
self_ty.clone()
};
let generics_search = GenericsSearch::from(self.self_ty.1);
let mut generics = self.self_ty.1.clone();
for (_, all_fields, skipped_fields) in &self.variants {
for field_ty in all_fields.iter().enumerate().filter_map(|(n, field)| {
(!skipped_fields.contains(&n)).then_some(&field.ty)
}) {
if generics_search.any_in(field_ty)
&& !field_ty.contains_type_structurally(&self_ty)
&& !field_ty.contains_type_structurally(&implementor_ty)
{
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty: derive_more::core::ops:: #trait_ty <Output = #field_ty>
});
}
}
}
let (impl_generics, _, where_clause) = generics.split_for_impl();
let body = self.body();
quote! {
#[allow(private_bounds)]
#[automatically_derived]
impl #impl_generics derive_more::core::ops:: #trait_ty for #implementor_ty
#where_clause
{
type Output = #output_ty;
#[inline]
#[track_caller]
fn #method_ident(self, __rhs: Self) -> Self::Output {
#body
}
}
}
.to_tokens(tokens);
}
}
#[cfg(any(feature = "add", feature = "mul"))]
/// Extension of [`syn::Fields`] used by a [`StructuralExpansion`].
trait StructuralExpansionFieldsExt {
/// Generates a resulting expression with these [`syn::Fields`] in a matched arm of a `match`
/// expression, by applying the specified method.
fn arm_expr(
&self,
method: &syn::Path,
skipped_indices: &SkippedFields,
) -> TokenStream;
}
#[cfg(any(feature = "add", feature = "mul"))]
impl StructuralExpansionFieldsExt for syn::Fields {
fn arm_expr(
&self,
method_path: &syn::Path,
skipped_indices: &SkippedFields,
) -> TokenStream {
match self {
Self::Named(fields) => {
let fields = fields.named.iter().enumerate().map(|(num, field)| {
let name = &field.ident;
let self_val = format_ident!("__self_{num}");
if skipped_indices.contains(&num) {
quote! { #name: #self_val }
} else {
let rhs_val = format_ident!("__rhs_{num}");
quote! { #name: #method_path(#self_val, #rhs_val) }
}
});
quote! {{ #( #fields , )* }}
}
Self::Unnamed(fields) => {
let fields = (0..fields.unnamed.len()).map(|num| {
let self_val = format_ident!("__self_{num}");
if skipped_indices.contains(&num) {
quote! { #self_val }
} else {
let rhs_val = format_ident!("__rhs_{num}");
quote! { #method_path(#self_val, #rhs_val) }
}
});
quote! {( #( #fields , )* )}
}
Self::Unit => quote! {},
}
}
}
+331
View File
@@ -0,0 +1,331 @@
//! Implementation of [`ops::Mul`]-like derive macros.
#[cfg(doc)]
use std::ops;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{parse_quote, spanned::Spanned as _};
use super::{SkippedFields, StructuralExpansion};
use crate::utils::{
attr::{self, ParseMultiple as _},
pattern_matching::FieldsExt as _,
structural_inclusion::TypeExt as _,
};
/// Expands an [`ops::Mul`]-like derive macro.
///
/// Available macros:
/// - [`Div`](ops::Div)
/// - [`Mul`](ops::Mul)
/// - [`Rem`](ops::Rem)
/// - [`Shl`](ops::Shl)
/// - [`Shr`](ops::Shr)
pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
let trait_name = normalize_trait_name(trait_name);
let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
match &input.data {
syn::Data::Struct(data) if matches!(data.fields, syn::Fields::Unit) => {
Err(syn::Error::new(
data.struct_token.span(),
format!("`{trait_name}` cannot be derived for unit structs"),
))
}
syn::Data::Union(data) => Err(syn::Error::new(
data.union_token.span(),
format!("`{trait_name}` cannot be derived for unions"),
)),
_ => {
if attr::Forward::parse_attrs(&input.attrs, &attr_name)?.is_some() {
expand_structural(input, trait_name, attr_name)
.map(ToTokens::into_token_stream)
} else {
expand_scalar(input, trait_name, attr_name)
.map(ToTokens::into_token_stream)
}
}
}
}
/// Expands an [`ops::Mul`]-like derive macro in a structural manner.
fn expand_structural<'i>(
input: &'i syn::DeriveInput,
trait_name: &str,
attr_name: syn::Ident,
) -> syn::Result<StructuralExpansion<'i>> {
let mut variants = vec![];
match &input.data {
syn::Data::Struct(data) => {
let mut skipped_fields = SkippedFields::default();
for (n, field) in data.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if data.fields.len() == skipped_fields.len() {
return Err(syn::Error::new(
data.struct_token.span(),
format!(
"`{trait_name}` cannot be derived for structs with all the fields being \
skipped",
),
));
}
variants.push((None, &data.fields, skipped_fields));
}
syn::Data::Enum(data) => {
for variant in &data.variants {
if let Some(skip) = attr::Skip::parse_attrs(&variant.attrs, &attr_name)?
{
return Err(syn::Error::new(
skip.span,
format!(
"`#[{attr_name}({})]` attribute can be placed only on variant fields",
skip.item.name(),
),
));
}
let mut skipped_fields = SkippedFields::default();
for (n, field) in variant.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if !matches!(variant.fields, syn::Fields::Unit)
&& variant.fields.len() == skipped_fields.len()
{
return Err(syn::Error::new(
variant.span(),
format!(
"`{trait_name}` cannot be derived for enum with all the fields being \
skipped in its variants",
),
));
}
variants.push((Some(&variant.ident), &variant.fields, skipped_fields));
}
}
syn::Data::Union(_) => unreachable!(),
}
Ok(StructuralExpansion {
trait_ty: format_ident!("{trait_name}"),
method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
self_ty: (&input.ident, &input.generics),
variants,
is_enum: matches!(input.data, syn::Data::Enum(_)),
})
}
/// Expands an [`ops::Mul`]-like derive macro in a scalar manner.
fn expand_scalar<'i>(
input: &'i syn::DeriveInput,
trait_name: &str,
attr_name: syn::Ident,
) -> syn::Result<ScalarExpansion<'i>> {
let mut skipped_fields = SkippedFields::default();
let fields = match &input.data {
syn::Data::Struct(data) => {
for (n, field) in data.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if data.fields.len() == skipped_fields.len() {
return Err(syn::Error::new(
data.struct_token.span(),
format!(
"`{trait_name}` cannot be derived for structs with all the fields being \
skipped",
),
));
}
&data.fields
}
syn::Data::Enum(data) => {
return Err(syn::Error::new(
data.enum_token.span(),
format!(
"`{trait_name}` can be derived for enums only when using \
`#[{attr_name}(forward)]` attribute",
),
));
}
syn::Data::Union(_) => unreachable!(),
};
Ok(ScalarExpansion {
trait_ty: format_ident!("{trait_name}"),
method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
self_ty: (&input.ident, &input.generics),
fields,
skipped_fields,
})
}
/// Expansion of a macro for generating a scalar [`ops::Mul`]-like trait implementation for a
/// struct.
struct ScalarExpansion<'i> {
/// [`syn::Ident`] of the implemented trait.
///
/// [`syn::Ident`]: struct@syn::Ident
trait_ty: syn::Ident,
/// [`syn::Ident`] and [`syn::Receiver`] of the implemented method in trait.
///
/// [`syn::Ident`]: struct@syn::Ident
method_ident: syn::Ident,
/// [`syn::Ident`] and [`syn::Generics`] of the implementor struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Fields`] of the struct to be used in this [`ScalarExpansion`].
fields: &'i syn::Fields,
/// Indices of the struct [`syn::Fields`] marked with an [`attr::Skip`].
skipped_fields: SkippedFields,
}
impl ToTokens for ScalarExpansion<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let trait_ty = &self.trait_ty;
let method_ident = &self.method_ident;
let ty = self.self_ty.0;
let (_, ty_generics, _) = self.self_ty.1.split_for_impl();
let implementor_ty: syn::Type = parse_quote! { #ty #ty_generics };
let self_ty: syn::Type = parse_quote! { Self };
let rhs_ty: syn::TypeParam = parse_quote! { __derive_more_Rhs };
let mut generics = self.self_ty.1.clone();
generics.params.push(rhs_ty.clone().into());
let mut used_fields_count = 0;
for field_ty in self.fields.iter().enumerate().filter_map(|(n, field)| {
(!self.skipped_fields.contains(&n)).then_some(&field.ty)
}) {
if !field_ty.contains_type_structurally(&self_ty)
&& !field_ty.contains_type_structurally(&implementor_ty)
{
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty: derive_more::core::ops:: #trait_ty <#rhs_ty, Output = #field_ty>
});
}
used_fields_count += 1;
}
if used_fields_count > 1 {
generics.make_where_clause().predicates.push(parse_quote! {
#rhs_ty: derive_more::core::marker::Copy
});
}
let (impl_generics, _, where_clause) = generics.split_for_impl();
let body = {
let method_path =
parse_quote! { derive_more::core::ops::#trait_ty::#method_ident };
let self_pat = self.fields.exhaustive_arm_pattern("__self_");
let fields_expr = self.fields.arm_expr(&method_path, &self.skipped_fields);
quote! {
match self {
Self #self_pat => Self #fields_expr,
}
}
};
quote! {
#[allow(private_bounds)]
#[automatically_derived]
impl #impl_generics derive_more::core::ops:: #trait_ty<#rhs_ty> for #implementor_ty
#where_clause
{
type Output = #self_ty;
#[inline]
#[track_caller]
fn #method_ident(self, __rhs: #rhs_ty) -> Self::Output {
#body
}
}
}
.to_tokens(tokens);
}
}
/// Extension of [`syn::Fields`] used by a [`ScalarExpansion`].
trait ScalarExpansionFieldsExt {
/// Generates a resulting expression with these [`syn::Fields`] in a matched arm of a `match`
/// expression, by applying the specified method.
fn arm_expr(
&self,
method: &syn::Path,
skipped_indices: &SkippedFields,
) -> TokenStream;
}
impl ScalarExpansionFieldsExt for syn::Fields {
fn arm_expr(
&self,
method_path: &syn::Path,
skipped_indices: &SkippedFields,
) -> TokenStream {
match self {
Self::Named(fields) => {
let fields = fields.named.iter().enumerate().map(|(num, field)| {
let name = &field.ident;
let self_val = format_ident!("__self_{num}");
if skipped_indices.contains(&num) {
quote! { #name: #self_val }
} else {
quote! { #name: #method_path(#self_val, __rhs) }
}
});
quote! {{ #( #fields , )* }}
}
Self::Unnamed(fields) => {
let fields = (0..fields.unnamed.len()).map(|num| {
let self_val = format_ident!("__self_{num}");
if skipped_indices.contains(&num) {
quote! { #self_val }
} else {
quote! { #method_path(#self_val, __rhs) }
}
});
quote! {( #( #fields , )* )}
}
Self::Unit => quote! {},
}
}
}
/// Matches the provided derive macro `name` to appropriate actual trait name.
fn normalize_trait_name(name: &str) -> &'static str {
match name {
"Div" => "Div",
"Mul" => "Mul",
"Rem" => "Rem",
"Shl" => "Shl",
"Shr" => "Shr",
_ => unimplemented!(),
}
}
/// Matches the provided [`ops::Mul`]-like trait `name` to its attribute's name.
fn trait_name_to_attribute_name(name: &str) -> &'static str {
trait_name_to_method_name(name)
}
/// Matches the provided [`ops::Mul`]-like trait `name` to its method name.
fn trait_name_to_method_name(name: &str) -> &'static str {
match name {
"Div" => "div",
"Mul" => "mul",
"Rem" => "rem",
"Shl" => "shl",
"Shr" => "shr",
_ => unimplemented!(),
}
}
@@ -0,0 +1,249 @@
//! Implementation of [`ops::MulAssign`]-like derive macros.
#[cfg(doc)]
use std::ops;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{parse_quote, spanned::Spanned as _};
use super::{AssignStructuralExpansion, SkippedFields};
use crate::utils::{
attr::{self, ParseMultiple as _},
pattern_matching::FieldsExt as _,
structural_inclusion::TypeExt as _,
};
/// Expands an [`ops::MulAssign`]-like derive macro.
///
/// Available macros:
/// - [`DivAssign`](ops::DivAssign)
/// - [`MulAssign`](ops::MulAssign)
/// - [`RemAssign`](ops::RemAssign)
/// - [`ShlAssign`](ops::ShlAssign)
/// - [`ShrAssign`](ops::ShrAssign)
pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
let trait_name = normalize_trait_name(trait_name);
let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
match &input.data {
syn::Data::Struct(data) => {
if matches!(data.fields, syn::Fields::Unit) {
return Err(syn::Error::new(
data.struct_token.span(),
format!("`{trait_name}` cannot be derived for unit structs"),
));
}
if attr::Forward::parse_attrs(&input.attrs, &attr_name)?.is_some() {
expand_structural(input, trait_name, attr_name)
.map(ToTokens::into_token_stream)
} else {
expand_scalar(input, trait_name, attr_name)
.map(ToTokens::into_token_stream)
}
}
syn::Data::Enum(data) => Err(syn::Error::new(
data.enum_token.span(),
format!("`{trait_name}` cannot be derived for enums"),
)),
syn::Data::Union(data) => Err(syn::Error::new(
data.union_token.span(),
format!("`{trait_name}` cannot be derived for unions"),
)),
}
}
/// Expands an [`ops::MulAssign`]-like derive macro in a structural manner.
fn expand_structural<'i>(
input: &'i syn::DeriveInput,
trait_name: &str,
attr_name: syn::Ident,
) -> syn::Result<AssignStructuralExpansion<'i>> {
let syn::Data::Struct(data) = &input.data else {
unreachable!()
};
let mut variants = vec![];
let mut skipped_fields = SkippedFields::default();
for (n, field) in data.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if data.fields.len() == skipped_fields.len() {
return Err(syn::Error::new(
data.struct_token.span(),
format!(
"`{trait_name}` cannot be derived for structs with all the fields being skipped",
),
));
}
variants.push((None, &data.fields, skipped_fields));
Ok(AssignStructuralExpansion {
trait_ty: format_ident!("{trait_name}"),
method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
self_ty: (&input.ident, &input.generics),
variants,
is_enum: false,
})
}
/// Expands an [`ops::MulAssign`]-like derive macro in a scalar manner.
fn expand_scalar<'i>(
input: &'i syn::DeriveInput,
trait_name: &str,
attr_name: syn::Ident,
) -> syn::Result<ScalarExpansion<'i>> {
let syn::Data::Struct(data) = &input.data else {
unreachable!()
};
let mut skipped_fields = SkippedFields::default();
for (n, field) in data.fields.iter().enumerate() {
if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
_ = skipped_fields.insert(n);
}
}
if data.fields.len() == skipped_fields.len() {
return Err(syn::Error::new(
data.struct_token.span(),
format!(
"`{trait_name}` cannot be derived for structs with all the fields being skipped",
),
));
}
Ok(ScalarExpansion {
trait_ty: format_ident!("{trait_name}"),
method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
self_ty: (&input.ident, &input.generics),
fields: &data.fields,
skipped_fields,
})
}
/// Expansion of a macro for generating a scalar [`ops::MulAssign`]-like trait implementation for a
/// struct.
struct ScalarExpansion<'i> {
/// [`syn::Ident`] of the implemented trait.
///
/// [`syn::Ident`]: struct@syn::Ident
trait_ty: syn::Ident,
/// [`syn::Ident`] and [`syn::Receiver`] of the implemented method in trait.
///
/// [`syn::Ident`]: struct@syn::Ident
method_ident: syn::Ident,
/// [`syn::Ident`] and [`syn::Generics`] of the implementor struct.
///
/// [`syn::Ident`]: struct@syn::Ident
self_ty: (&'i syn::Ident, &'i syn::Generics),
/// [`syn::Fields`] of the struct to be used in this [`ScalarExpansion`].
fields: &'i syn::Fields,
/// Indices of the struct [`syn::Fields`] marked with an [`attr::Skip`].
skipped_fields: SkippedFields,
}
impl ToTokens for ScalarExpansion<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let trait_ty = &self.trait_ty;
let method_ident = &self.method_ident;
let ty = self.self_ty.0;
let (_, ty_generics, _) = self.self_ty.1.split_for_impl();
let implementor_ty: syn::Type = parse_quote! { #ty #ty_generics };
let self_ty: syn::Type = parse_quote! { Self };
let rhs_ty: syn::TypeParam = parse_quote! { __derive_more_Rhs };
let mut generics = self.self_ty.1.clone();
generics.params.push(rhs_ty.clone().into());
let mut used_fields_count = 0;
for field_ty in self.fields.iter().enumerate().filter_map(|(n, field)| {
(!self.skipped_fields.contains(&n)).then_some(&field.ty)
}) {
if !field_ty.contains_type_structurally(&self_ty)
&& !field_ty.contains_type_structurally(&implementor_ty)
{
generics.make_where_clause().predicates.push(parse_quote! {
#field_ty: derive_more::core::ops:: #trait_ty <#rhs_ty>
});
}
used_fields_count += 1;
}
if used_fields_count > 1 {
generics.make_where_clause().predicates.push(parse_quote! {
#rhs_ty: derive_more::core::marker::Copy
});
}
let (impl_generics, _, where_clause) = generics.split_for_impl();
let body = {
let method_path =
quote! { derive_more::core::ops::#trait_ty::#method_ident };
let self_pat = self
.fields
.non_exhaustive_arm_pattern("__self_", &self.skipped_fields);
let fields_exprs = (0..self.fields.len())
.filter(|num| !self.skipped_fields.contains(num))
.map(|num| {
let self_val = format_ident!("__self_{num}");
quote! { #method_path(#self_val, __rhs); }
});
quote! {
match self {
Self #self_pat => { #( #fields_exprs )* },
}
}
};
quote! {
#[allow(private_bounds)]
#[automatically_derived]
impl #impl_generics derive_more::core::ops:: #trait_ty<#rhs_ty> for #implementor_ty
#where_clause
{
#[inline]
#[track_caller]
fn #method_ident(&mut self, __rhs: #rhs_ty) {
#body
}
}
}
.to_tokens(tokens);
}
}
/// Matches the provided derive macro `name` to appropriate actual trait name.
fn normalize_trait_name(name: &str) -> &'static str {
match name {
"DivAssign" => "DivAssign",
"MulAssign" => "MulAssign",
"RemAssign" => "RemAssign",
"ShlAssign" => "ShlAssign",
"ShrAssign" => "ShrAssign",
_ => unimplemented!(),
}
}
/// Matches the provided [`ops::MulAssign`]-like trait `name` to its attribute's name.
fn trait_name_to_attribute_name(name: &str) -> &'static str {
trait_name_to_method_name(name)
}
/// Matches the provided [`ops::MulAssign`]-like trait `name` to its method name.
fn trait_name_to_method_name(name: &str) -> &'static str {
match name {
"DivAssign" => "div_assign",
"MulAssign" => "mul_assign",
"RemAssign" => "rem_assign",
"ShlAssign" => "shl_assign",
"ShrAssign" => "shr_assign",
_ => unimplemented!(),
}
}
+310
View File
@@ -0,0 +1,310 @@
//! Common parsing utilities for derive macros.
//!
//! Fair parsing of [`syn::Expr`] requires [`syn`]'s `full` feature to be enabled, which unnecessary
//! increases compile times. As we don't have complex AST manipulation, usually requiring only
//! understanding where syntax item begins and ends, simpler manual parsing is implemented.
use proc_macro2::{Spacing, TokenStream};
use quote::ToTokens;
use syn::{
buffer::Cursor,
parse::{Parse, ParseStream},
};
/// [`syn::Expr`] [`Parse`]ing polyfill.
#[derive(Clone, Debug)]
pub(crate) enum Expr {
/// [`syn::Expr::Path`] of length 1 [`Parse`]ing polyfill.
Ident(syn::Ident),
/// Every other [`syn::Expr`] variant.
Other(TokenStream),
}
impl Expr {
/// Returns a [`syn::Ident`] in case this [`Expr`] is represented only by it.
///
/// [`syn::Ident`]: struct@syn::Ident
pub(crate) fn ident(&self) -> Option<&syn::Ident> {
match self {
Self::Ident(ident) => Some(ident),
Self::Other(_) => None,
}
}
}
impl From<syn::Ident> for Expr {
fn from(ident: syn::Ident) -> Self {
Self::Ident(ident)
}
}
impl Parse for Expr {
fn parse(input: ParseStream) -> syn::Result<Self> {
if let Ok(ident) = input.step(|c| {
c.ident()
.filter(|(_, c)| c.eof() || punct(',')(*c).is_some())
.ok_or_else(|| syn::Error::new(c.span(), "expected `ident(,|eof)`"))
}) {
Ok(Self::Ident(ident))
} else {
input.step(|c| {
take_until1(
alt([
&mut seq([
&mut path_sep,
&mut balanced_pair(punct('<'), punct('>')),
]),
&mut seq([
&mut balanced_pair(punct('<'), punct('>')),
&mut path_sep,
]),
&mut balanced_pair(punct('|'), punct('|')),
&mut token_tree,
]),
punct(','),
)(*c)
.map(|(stream, cursor)| (Self::Other(stream), cursor))
.ok_or_else(|| syn::Error::new(c.span(), "failed to parse expression"))
})
}
}
}
impl PartialEq<syn::Ident> for Expr {
fn eq(&self, other: &syn::Ident) -> bool {
self.ident().is_some_and(|i| i == other)
}
}
impl ToTokens for Expr {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Ident(ident) => ident.to_tokens(tokens),
Self::Other(other) => other.to_tokens(tokens),
}
}
}
/// Result of parsing.
type ParsingResult<'a> = Option<(TokenStream, Cursor<'a>)>;
/// Tries to parse a [`token::PathSep`].
///
/// [`token::PathSep`]: struct@syn::token::PathSep
pub fn path_sep(c: Cursor<'_>) -> ParsingResult<'_> {
seq([
&mut punct_with_spacing(':', Spacing::Joint),
&mut punct(':'),
])(c)
}
/// Tries to parse a [`punct`] with [`Spacing`].
pub fn punct_with_spacing(
p: char,
spacing: Spacing,
) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> {
move |c| {
c.punct().and_then(|(punct, c)| {
(punct.as_char() == p && punct.spacing() == spacing)
.then(|| (punct.into_token_stream(), c))
})
}
}
/// Tries to parse a [`Punct`].
///
/// [`Punct`]: proc_macro2::Punct
pub fn punct(p: char) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> {
move |c| {
c.punct().and_then(|(punct, c)| {
(punct.as_char() == p).then(|| (punct.into_token_stream(), c))
})
}
}
/// Tries to parse any [`TokenTree`].
///
/// [`TokenTree`]: proc_macro2::TokenTree
pub fn token_tree(c: Cursor<'_>) -> ParsingResult<'_> {
c.token_tree().map(|(tt, c)| (tt.into_token_stream(), c))
}
/// Parses until balanced amount of `open` and `close` or eof.
///
/// [`Cursor`] should be pointing **right after** the first `open`ing.
pub fn balanced_pair(
mut open: impl FnMut(Cursor<'_>) -> ParsingResult<'_>,
mut close: impl FnMut(Cursor<'_>) -> ParsingResult<'_>,
) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> {
move |c| {
let (mut out, mut c) = open(c)?;
let mut count = 1;
while count != 0 {
let (stream, cursor) = if let Some(closing) = close(c) {
count -= 1;
closing
} else if let Some(opening) = open(c) {
count += 1;
opening
} else {
let (tt, c) = c.token_tree()?;
(tt.into_token_stream(), c)
};
out.extend(stream);
c = cursor;
}
Some((out, c))
}
}
/// Tries to execute the provided sequence of `parsers`.
pub fn seq<const N: usize>(
mut parsers: [&mut dyn FnMut(Cursor<'_>) -> ParsingResult<'_>; N],
) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> + '_ {
move |c| {
parsers.iter_mut().try_fold(
(TokenStream::new(), c),
|(mut out, mut c), parser| {
let (stream, cursor) = parser(c)?;
out.extend(stream);
c = cursor;
Some((out, c))
},
)
}
}
/// Tries to execute the first successful parser.
pub fn alt<const N: usize>(
mut parsers: [&mut dyn FnMut(Cursor<'_>) -> ParsingResult<'_>; N],
) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> + '_ {
move |c| parsers.iter_mut().find_map(|parser| parser(c))
}
/// Parses with `basic` while `until` fails. Returns [`None`] in case
/// `until` succeeded initially or `basic` never succeeded. Doesn't consume
/// tokens parsed by `until`.
pub fn take_until1<P, U>(
mut parser: P,
mut until: U,
) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_>
where
P: FnMut(Cursor<'_>) -> ParsingResult<'_>,
U: FnMut(Cursor<'_>) -> ParsingResult<'_>,
{
move |mut cursor| {
let mut out = TokenStream::new();
let mut parsed = false;
loop {
if cursor.eof() || until(cursor).is_some() {
return parsed.then_some((out, cursor));
}
let (stream, c) = parser(cursor)?;
out.extend(stream);
cursor = c;
parsed = true;
}
}
}
#[cfg(test)]
mod spec {
use std::{fmt::Debug, str::FromStr};
use itertools::Itertools as _;
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{
parse::{Parse, Parser as _},
punctuated::Punctuated,
token::Comma,
};
use super::Expr;
fn assert<'a, T: Debug + Parse + ToTokens>(
input: &'a str,
parsed: impl AsRef<[&'a str]>,
) {
let parsed = parsed.as_ref();
let punctuated = Punctuated::<T, Comma>::parse_terminated
.parse2(TokenStream::from_str(input).unwrap())
.unwrap();
assert_eq!(
parsed.len(),
punctuated.len(),
"Wrong length\n\
Expected: {parsed:?}\n\
Found: {punctuated:?}",
);
punctuated
.iter()
.map(|ty| ty.to_token_stream().to_string())
.zip(parsed)
.enumerate()
.for_each(|(i, (found, expected))| {
assert_eq!(
*expected, &found,
"Mismatch at index {i}\n\
Expected: {parsed:?}\n\
Found: {punctuated:?}",
);
});
}
mod expr {
use super::*;
#[test]
fn cases() {
let cases = [
"ident",
"[a , b , c , d]",
"counter += 1",
"async { fut . await }",
"a < b",
"a > b",
"{ let x = (a , b) ; }",
"invoke (a , b)",
"foo as f64",
"| a , b | a + b",
"obj . k",
"for pat in expr { break pat ; }",
"if expr { true } else { false }",
"vector [2]",
"1",
"\"foo\"",
"loop { break i ; }",
"format ! (\"{}\" , q)",
"match n { Some (n) => { } , None => { } }",
"x . foo ::< T > (a , b)",
"x . foo ::< T < [T < T >; if a < b { 1 } else { 2 }] >, { a < b } > (a , b)",
"(a + b)",
"i32 :: MAX",
"1 .. 2",
"& a",
"[0u8 ; N]",
"(a , b , c , d)",
"< Ty as Trait > :: T",
"< Ty < Ty < T >, { a < b } > as Trait < T > > :: T",
];
assert::<Expr>("", []);
for i in 1..4 {
for permutations in cases.into_iter().permutations(i) {
let mut input = permutations.clone().join(",");
assert::<Expr>(&input, &permutations);
input.push(',');
assert::<Expr>(&input, &permutations);
}
}
}
}
}
+53
View File
@@ -0,0 +1,53 @@
use crate::utils::{
add_extra_ty_param_bound, add_extra_where_clauses, MultiFieldData, State,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{DeriveInput, Result};
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state = State::new(input, trait_name, trait_name.to_lowercase())?;
let multi_field_data = state.enabled_fields_data();
let MultiFieldData {
input_type,
field_types,
trait_path,
method_ident,
..
} = multi_field_data.clone();
let op_trait_name = if trait_name == "Sum" { "Add" } else { "Mul" };
let op_trait_ident = format_ident!("{op_trait_name}");
let op_path = quote! { derive_more::core::ops::#op_trait_ident };
let op_method_ident = format_ident!("{}", op_trait_name.to_lowercase());
let has_type_params = input.generics.type_params().next().is_none();
let generics = if has_type_params {
input.generics.clone()
} else {
let (_, ty_generics, _) = input.generics.split_for_impl();
let generics = add_extra_ty_param_bound(&input.generics, trait_path);
let operator_where_clause = quote! {
where #input_type #ty_generics: #op_path<Output=#input_type #ty_generics>
};
add_extra_where_clauses(&generics, operator_where_clause)
};
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let initializers: Vec<_> = field_types
.iter()
.map(|field_type| {
quote! { #trait_path::#method_ident(derive_more::core::iter::empty::<#field_type>()) }
})
.collect();
let identity = multi_field_data.initializer(&initializers);
Ok(quote! {
#[automatically_derived]
impl #impl_generics #trait_path for #input_type #ty_generics #where_clause {
#[inline]
fn #method_ident<I: derive_more::core::iter::Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(#identity, #op_path::#op_method_ident)
}
}
})
}
+153
View File
@@ -0,0 +1,153 @@
//! Implementation of a [`TryFrom`] derive macro.
use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::spanned::Spanned as _;
use crate::utils::{
attr::{self, ParseMultiple as _},
Spanning,
};
/// Expands a [`TryFrom`] derive macro.
pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
match &input.data {
syn::Data::Struct(data) => Err(syn::Error::new(
data.struct_token.span(),
"`TryFrom` cannot be derived for structs",
)),
syn::Data::Enum(data) => Ok(Expansion {
repr: attr::ReprInt::parse_attrs(&input.attrs, &format_ident!("repr"))?
.map(Spanning::into_inner)
.unwrap_or_default(),
attr: Some(
ItemAttribute::parse_attrs(&input.attrs, &format_ident!("try_from"))?
.map(|attr| {
if matches!(attr.item, ItemAttribute::Types(_)) {
Err(syn::Error::new(
attr.span,
"`#[try_from(repr(...))]` attribute is not supported yet",
))
} else {
Ok(attr.item)
}
})
.transpose()?
.ok_or_else(|| {
syn::Error::new(
input.ident.span(),
"`#[try_from(repr)]` attribute is expected here",
)
})?,
),
ident: input.ident.clone(),
generics: input.generics.clone(),
variants: data.variants.clone().into_iter().collect(),
}
.into_token_stream()),
syn::Data::Union(data) => Err(syn::Error::new(
data.union_token.span(),
"`TryFrom` cannot be derived for unions",
)),
}
}
/// Representation of a [`TryFrom`] derive macro struct item attribute.
///
/// ```rust,ignore
/// #[try_from(repr)]
/// #[try_from(repr(<types>))]
/// ```
type ItemAttribute = attr::ReprConversion;
/// Expansion of a macro for generating [`TryFrom`] implementation of an enum.
struct Expansion {
/// `#[repr(u/i*)]` of the enum.
repr: attr::ReprInt,
/// [`ItemAttribute`] of the enum.
attr: Option<ItemAttribute>,
/// [`syn::Ident`] of the enum.
///
/// [`syn::Ident`]: struct@syn::Ident
ident: syn::Ident,
/// [`syn::Generics`] of the enum.
generics: syn::Generics,
/// [`syn::Variant`]s of the enum.
variants: Vec<syn::Variant>,
}
impl ToTokens for Expansion {
/// Expands [`TryFrom`] implementations for a struct.
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.attr.is_none() {
return;
}
let ident = &self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
let repr_ty = &self.repr.ty();
let mut last_discriminant = quote! { 0 };
let mut inc = 0usize;
let (consts, (discriminants, variants)): (
Vec<syn::Ident>,
(Vec<TokenStream>, Vec<TokenStream>),
) = self
.variants
.iter()
.filter_map(
|syn::Variant {
ident,
fields,
discriminant,
..
}| {
if let Some(d) = discriminant {
last_discriminant = d.1.to_token_stream();
inc = 0;
}
let ret = {
let inc = Literal::usize_unsuffixed(inc);
fields.is_empty().then_some((
format_ident!("__DISCRIMINANT_{ident}"),
(
quote! { #last_discriminant + #inc },
quote! { #ident #fields },
),
))
};
inc += 1;
ret
},
)
.unzip();
let error = quote! { derive_more::TryFromReprError<#repr_ty> };
quote! {
#[automatically_derived]
impl #impl_generics derive_more::core::convert::TryFrom<#repr_ty #ty_generics>
for #ident #where_clause {
type Error = #error;
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(non_upper_case_globals)]
#[inline]
fn try_from(val: #repr_ty) -> derive_more::core::result::Result<Self, #error> {
#( const #consts: #repr_ty = #discriminants; )*
match val {
#(#consts => derive_more::core::result::Result::Ok(#ident::#variants),)*
_ => derive_more::core::result::Result::Err(
derive_more::TryFromReprError::new(val)
),
}
}
}
}.to_tokens(tokens);
}
}
+196
View File
@@ -0,0 +1,196 @@
use crate::utils::{
add_extra_generic_param, numbered_vars, replace_self::DeriveInputExt as _,
AttrParams, DeriveType, MultiFieldData, State,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use crate::utils::{
attr::{self, ParseMultiple as _},
HashMap, Spanning,
};
/// Provides the hook to expand `#[derive(TryInto)]` into an implementation of `TryInto`.
#[allow(clippy::cognitive_complexity)]
pub fn expand(
input: &syn::DeriveInput,
trait_name: &'static str,
) -> syn::Result<TokenStream> {
let input = &mut input.replace_self_type();
let trait_attr = "try_into";
// TODO: Use `Vec::extract_if` once MSRV is bumped to 1.87 or above.
let (error_indices, error_attrs) = input
.attrs
.iter()
.enumerate()
.filter(|(_, attr)| {
attr.path().is_ident(trait_attr)
&& attr.parse_args_with(detect_error_attr).is_ok()
})
.map(|(i, attr)| (i, attr.clone()))
.unzip::<_, _, Vec<_>, Vec<_>>();
for i in error_indices {
_ = &mut input.attrs.remove(i);
}
let custom_error =
attr::Error::parse_attrs(error_attrs, &format_ident!("{trait_attr}"))?
.map(Spanning::into_inner);
let state = State::with_attr_params(
input,
trait_name,
trait_attr.into(),
AttrParams {
enum_: vec!["ignore", "owned", "ref", "ref_mut"],
variant: vec!["ignore", "owned", "ref", "ref_mut"],
struct_: vec!["ignore", "owned", "ref", "ref_mut"],
field: vec!["ignore"],
},
).map_err(|e| {
// Temporary adjustment of attribute parsing errors until these attributes are reimplemented
// via `utils::attr` machinery.
let msg = &e.to_string();
if msg == "Only a single attribute is allowed" {
syn::Error::new(
e.span(),
"Only a single attribute is allowed.\n\
For the top-level attribute, an additional `#[try_into(error(...))]` may be \
provided.",
)
} else if msg.starts_with(
"Attribute parameter not supported. Supported attribute parameters are:",
) {
syn::Error::new(e.span(), format!("{msg}, error"))
} else {
e
}
})?;
assert!(
state.derive_type == DeriveType::Enum,
"only enums can derive `TryInto`",
);
let mut variants_per_types = HashMap::default();
for variant_state in state.enabled_variant_data().variant_states {
let multi_field_data = variant_state.enabled_fields_data();
let MultiFieldData {
variant_info,
field_types,
..
} = multi_field_data.clone();
for ref_type in variant_info.ref_types() {
variants_per_types
.entry((ref_type, field_types.clone()))
.or_insert_with(Vec::new)
.push(multi_field_data.clone());
}
}
let mut tokens = TokenStream::new();
for ((ref_type, ref original_types), ref multi_field_data) in variants_per_types {
let input_type = &input.ident;
let pattern_ref = ref_type.pattern_ref();
let lifetime = ref_type.lifetime();
let reference_with_lifetime = ref_type.reference_with_lifetime();
let mut matchers = vec![];
let vars = &numbered_vars(original_types.len(), "");
for multi_field_data in multi_field_data {
let patterns: Vec<_> = vars
.iter()
.map(|var| quote! { #pattern_ref #var })
.collect();
matchers.push(
multi_field_data.matcher(&multi_field_data.field_indexes, &patterns),
);
}
let vars = if vars.len() == 1 {
quote! { #(#vars)* }
} else {
quote! { (#(#vars),*) }
};
let output_type = if original_types.len() == 1 {
quote! { #(#original_types)* }.to_string()
} else {
let types = original_types
.iter()
.map(|t| quote! { #t }.to_string())
.collect::<Vec<_>>();
format!("({})", types.join(", "))
};
let variant_names = multi_field_data
.iter()
.map(|d| {
d.variant_name
.expect("somehow there was no variant name")
.to_string()
})
.collect::<Vec<_>>()
.join(", ");
let generics_impl;
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
let (impl_generics, _, _) = if ref_type.is_ref() {
generics_impl = add_extra_generic_param(&input.generics, lifetime.clone());
generics_impl.split_for_impl()
} else {
input.generics.split_for_impl()
};
let mut error_ty = quote! {
derive_more::TryIntoError<#reference_with_lifetime #input_type #ty_generics>
};
let mut error_conv = quote! {};
if let Some(custom_error) = custom_error.as_ref() {
error_ty = custom_error.ty.to_token_stream();
error_conv = custom_error.conv.as_ref().map_or_else(
|| quote! { .map_err(derive_more::core::convert::Into::into) },
|conv| quote! { .map_err(#conv) },
);
}
let try_from = quote! {
#[automatically_derived]
impl #impl_generics derive_more::core::convert::TryFrom<
#reference_with_lifetime #input_type #ty_generics
> for (#(#reference_with_lifetime #original_types),*) #where_clause {
type Error = #error_ty;
#[inline]
fn try_from(
value: #reference_with_lifetime #input_type #ty_generics,
) -> derive_more::core::result::Result<Self, #error_ty> {
match value {
#(#matchers)|* => derive_more::core::result::Result::Ok(#vars),
_ => derive_more::core::result::Result::Err(
derive_more::TryIntoError::new(value, #variant_names, #output_type),
)#error_conv,
}
}
}
};
try_from.to_tokens(&mut tokens)
}
Ok(tokens)
}
/// Checks whether the inner of a [`syn::Attribute`] represents an [`attr::Error`].
fn detect_error_attr(input: syn::parse::ParseStream) -> syn::Result<()> {
mod ident {
syn::custom_keyword!(error);
}
let ahead = input.lookahead1();
if ahead.peek(ident::error) {
let _ = input.parse::<TokenStream>();
Ok(())
} else {
Err(ahead.error())
}
}
+184
View File
@@ -0,0 +1,184 @@
use crate::utils::{AttrParams, DeriveType, State};
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{DeriveInput, Fields, Ident, Result, Type};
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state = State::with_attr_params(
input,
trait_name,
"try_unwrap".into(),
AttrParams {
enum_: vec!["ignore", "owned", "ref", "ref_mut"],
variant: vec!["ignore", "owned", "ref", "ref_mut"],
struct_: vec!["ignore"],
field: vec!["ignore"],
},
)?;
assert!(
state.derive_type == DeriveType::Enum,
"TryUnwrap can only be derived for enums",
);
let enum_name = &input.ident;
let (imp_generics, type_generics, where_clause) = input.generics.split_for_impl();
let variant_data = state.enabled_variant_data();
let mut funcs = vec![];
for (variant_state, info) in
Iterator::zip(variant_data.variant_states.iter(), variant_data.infos)
{
let variant = variant_state.variant.unwrap();
let fn_name = format_ident!(
"try_unwrap_{ident}",
ident = variant.ident.to_string().to_case(Case::Snake),
span = variant.ident.span(),
);
let ref_fn_name = format_ident!(
"try_unwrap_{ident}_ref",
ident = variant.ident.to_string().to_case(Case::Snake),
span = variant.ident.span(),
);
let mut_fn_name = format_ident!(
"try_unwrap_{ident}_mut",
ident = variant.ident.to_string().to_case(Case::Snake),
span = variant.ident.span(),
);
let variant_ident = &variant.ident;
let (data_pattern, ret_value, data_types) = get_field_info(&variant.fields);
let pattern = quote! { #enum_name :: #variant_ident #data_pattern };
let (failed_block, failed_block_ref, failed_block_mut) = (
failed_block(&state, enum_name, &fn_name),
failed_block(&state, enum_name, &ref_fn_name),
failed_block(&state, enum_name, &mut_fn_name),
);
let doc_owned = format!(
"Attempts to unwrap this value to the `{enum_name}::{variant_ident}` variant.\n",
);
let doc_ref = format!(
"Attempts to unwrap this reference to the `{enum_name}::{variant_ident}` variant.\n",
);
let doc_mut = format!(
"Attempts to unwrap this mutable reference to the `{enum_name}::{variant_ident}` variant.\n",
);
let doc_else = "Returns a [TryUnwrapError] with the original value if this value is of any other type.";
let func = quote! {
#[inline]
#[track_caller]
#[doc = #doc_owned]
#[doc = #doc_else]
pub fn #fn_name(self) -> derive_more::core::result::Result<
(#(#data_types),*), derive_more::TryUnwrapError<Self>
> {
match self {
#pattern => derive_more::core::result::Result::Ok(#ret_value),
val @ _ => #failed_block,
}
}
};
let ref_func = quote! {
#[inline]
#[track_caller]
#[doc = #doc_ref]
#[doc = #doc_else]
pub fn #ref_fn_name(&self) -> derive_more::core::result::Result<
(#(&#data_types),*), derive_more::TryUnwrapError<&Self>
> {
match self {
#pattern => derive_more::core::result::Result::Ok(#ret_value),
val @ _ => #failed_block_ref,
}
}
};
let mut_func = quote! {
#[inline]
#[track_caller]
#[doc = #doc_mut]
#[doc = #doc_else]
pub fn #mut_fn_name(&mut self) -> derive_more::core::result::Result<
(#(&mut #data_types),*), derive_more::TryUnwrapError<&mut Self>
> {
match self {
#pattern => derive_more::core::result::Result::Ok(#ret_value),
val @ _ => #failed_block_mut,
}
}
};
if info.owned && state.default_info.owned {
funcs.push(func);
}
if info.ref_ && state.default_info.ref_ {
funcs.push(ref_func);
}
if info.ref_mut && state.default_info.ref_mut {
funcs.push(mut_func);
}
}
let imp = quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #imp_generics #enum_name #type_generics #where_clause {
#(#funcs)*
}
};
Ok(imp)
}
fn get_field_info(fields: &Fields) -> (TokenStream, TokenStream, Vec<&Type>) {
match fields {
Fields::Named(_) => panic!("cannot unwrap anonymous records"),
Fields::Unnamed(ref fields) => {
let (idents, types) = fields
.unnamed
.iter()
.enumerate()
.map(|(n, it)| (format_ident!("field_{n}"), &it.ty))
.unzip::<_, _, Vec<_>, Vec<_>>();
(quote! { (#(#idents),*) }, quote! { (#(#idents),*) }, types)
}
Fields::Unit => (quote! {}, quote! { () }, vec![]),
}
}
fn failed_block(state: &State, enum_name: &Ident, func_name: &Ident) -> TokenStream {
let arms = state
.variant_states
.iter()
.map(|it| it.variant.unwrap())
.map(|variant| {
let data_pattern = match variant.fields {
Fields::Named(_) => quote! { {..} },
Fields::Unnamed(_) => quote! { (..) },
Fields::Unit => quote! {},
};
let variant_ident = &variant.ident;
let error = quote! {
derive_more::TryUnwrapError::<_>::new(
val,
stringify!(#enum_name),
stringify!(#variant_ident),
stringify!(#func_name),
)
};
quote! {
val @ #enum_name :: #variant_ident #data_pattern
=> derive_more::core::result::Result::Err(#error)
}
});
quote! {
match val {
#(#arms),*
}
}
}
+171
View File
@@ -0,0 +1,171 @@
use crate::utils::{AttrParams, DeriveType, State};
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{DeriveInput, Fields, Ident, Result, Type};
pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
let state = State::with_attr_params(
input,
trait_name,
"unwrap".into(),
AttrParams {
enum_: vec!["ignore", "owned", "ref", "ref_mut"],
variant: vec!["ignore", "owned", "ref", "ref_mut"],
struct_: vec!["ignore"],
field: vec!["ignore"],
},
)?;
assert!(
state.derive_type == DeriveType::Enum,
"Unwrap can only be derived for enums",
);
let enum_name = &input.ident;
let (imp_generics, type_generics, where_clause) = input.generics.split_for_impl();
let variant_data = state.enabled_variant_data();
let mut funcs = vec![];
for (variant_state, info) in
Iterator::zip(variant_data.variant_states.iter(), variant_data.infos)
{
let variant = variant_state.variant.unwrap();
let fn_name = format_ident!(
"unwrap_{ident}",
ident = variant.ident.to_string().to_case(Case::Snake),
span = variant.ident.span(),
);
let ref_fn_name = format_ident!(
"unwrap_{ident}_ref",
ident = variant.ident.to_string().to_case(Case::Snake),
span = variant.ident.span(),
);
let mut_fn_name = format_ident!(
"unwrap_{ident}_mut",
ident = variant.ident.to_string().to_case(Case::Snake),
span = variant.ident.span(),
);
let variant_ident = &variant.ident;
let (data_pattern, ret_value, data_types) = get_field_info(&variant.fields);
let pattern = quote! { #enum_name :: #variant_ident #data_pattern };
let (failed_block, failed_block_ref, failed_block_mut) = (
failed_block(&state, enum_name, &fn_name),
failed_block(&state, enum_name, &ref_fn_name),
failed_block(&state, enum_name, &mut_fn_name),
);
let doc_owned = format!(
"Unwraps this value to the `{enum_name}::{variant_ident}` variant.\n",
);
let doc_ref = format!(
"Unwraps this reference to the `{enum_name}::{variant_ident}` variant.\n",
);
let doc_mut = format!(
"Unwraps this mutable reference to the `{enum_name}::{variant_ident}` variant.\n",
);
let doc_else = "Panics if this value is of any other type.";
let func = quote! {
#[inline]
#[track_caller]
#[doc = #doc_owned]
#[doc = #doc_else]
pub fn #fn_name(self) -> (#(#data_types),*) {
match self {
#pattern => #ret_value,
val @ _ => #failed_block,
}
}
};
let ref_func = quote! {
#[inline]
#[track_caller]
#[doc = #doc_ref]
#[doc = #doc_else]
pub fn #ref_fn_name(&self) -> (#(&#data_types),*) {
match self {
#pattern => #ret_value,
val @ _ => #failed_block_ref,
}
}
};
let mut_func = quote! {
#[inline]
#[track_caller]
#[doc = #doc_mut]
#[doc = #doc_else]
pub fn #mut_fn_name(&mut self) -> (#(&mut #data_types),*) {
match self {
#pattern => #ret_value,
val @ _ => #failed_block_mut,
}
}
};
if info.owned && state.default_info.owned {
funcs.push(func);
}
if info.ref_ && state.default_info.ref_ {
funcs.push(ref_func);
}
if info.ref_mut && state.default_info.ref_mut {
funcs.push(mut_func);
}
}
let imp = quote! {
#[allow(deprecated)] // omit warnings on deprecated fields/variants
#[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
#[automatically_derived]
impl #imp_generics #enum_name #type_generics #where_clause {
#(#funcs)*
}
};
Ok(imp)
}
fn get_field_info(fields: &Fields) -> (TokenStream, TokenStream, Vec<&Type>) {
match fields {
Fields::Named(_) => panic!("cannot unwrap anonymous records"),
Fields::Unnamed(ref fields) => {
let (idents, types) = fields
.unnamed
.iter()
.enumerate()
.map(|(n, it)| (format_ident!("field_{n}"), &it.ty))
.unzip::<_, _, Vec<_>, Vec<_>>();
(quote! { (#(#idents),*) }, quote! { (#(#idents),*) }, types)
}
Fields::Unit => (quote! {}, quote! { () }, vec![]),
}
}
fn failed_block(state: &State, enum_name: &Ident, fn_name: &Ident) -> TokenStream {
let arms = state
.variant_states
.iter()
.map(|it| it.variant.unwrap())
.map(|variant| {
let data_pattern = match variant.fields {
Fields::Named(_) => quote! { {..} },
Fields::Unnamed(_) => quote! { (..) },
Fields::Unit => quote! {},
};
let variant_ident = &variant.ident;
let panic_msg = format!(
"called `{enum_name}::{fn_name}()` on a `{enum_name}::{variant_ident}` value"
);
quote! { #enum_name :: #variant_ident #data_pattern => panic!(#panic_msg) }
});
quote! {
match val {
#(#arms),*
}
}
}
File diff suppressed because it is too large Load Diff