derive_more_impl/ops/
add_assign.rs

1//! Implementation of [`ops::AddAssign`]-like derive macros.
2
3#[cfg(doc)]
4use std::ops;
5
6use proc_macro2::TokenStream;
7use quote::{format_ident, ToTokens as _};
8use syn::spanned::Spanned as _;
9
10use super::{AssignStructuralExpansion, SkippedFields};
11use crate::utils::attr::{self, ParseMultiple as _};
12
13/// Expands an [`ops::AddAssign`]-like derive macro.
14///
15/// Available macros:
16/// - [`AddAssign`](ops::AddAssign)
17/// - [`BitAndAssign`](ops::BitAndAssign)
18/// - [`BitOrAssign`](ops::BitOrAssign)
19/// - [`BitXorAssign`](ops::BitXorAssign)
20/// - [`SubAssign`](ops::SubAssign)
21pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
22    let trait_name = normalize_trait_name(trait_name);
23    let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
24
25    let mut variants = vec![];
26    match &input.data {
27        syn::Data::Struct(data) => {
28            if let Some(skip) = attr::Skip::parse_attrs(&input.attrs, &attr_name)? {
29                return Err(syn::Error::new(
30                    skip.span,
31                    format!(
32                        "`#[{attr_name}({})]` attribute can be placed only on struct fields",
33                        skip.item.name(),
34                    ),
35                ));
36            } else if matches!(data.fields, syn::Fields::Unit) {
37                return Err(syn::Error::new(
38                    data.struct_token.span(),
39                    format!("`{trait_name}` cannot be derived for unit structs"),
40                ));
41            }
42            let mut skipped_fields = SkippedFields::default();
43            for (n, field) in data.fields.iter().enumerate() {
44                if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
45                    _ = skipped_fields.insert(n);
46                }
47            }
48            if data.fields.len() == skipped_fields.len() {
49                return Err(syn::Error::new(
50                    data.struct_token.span(),
51                    format!(
52                        "`{trait_name}` cannot be derived for structs with all the fields being \
53                         skipped",
54                    ),
55                ));
56            }
57            variants.push((None, &data.fields, skipped_fields));
58        }
59        syn::Data::Enum(data) => {
60            return Err(syn::Error::new(
61                data.enum_token.span(),
62                format!("`{trait_name}` cannot be derived for enums"),
63            ));
64        }
65        syn::Data::Union(data) => {
66            return Err(syn::Error::new(
67                data.union_token.span(),
68                format!("`{trait_name}` cannot be derived for unions"),
69            ));
70        }
71    }
72
73    Ok(AssignStructuralExpansion {
74        trait_ty: format_ident!("{trait_name}"),
75        method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
76        self_ty: (&input.ident, &input.generics),
77        variants,
78        is_enum: false,
79    }
80    .into_token_stream())
81}
82
83/// Matches the provided derive macro `name` to appropriate actual trait name.
84fn normalize_trait_name(name: &str) -> &'static str {
85    match name {
86        "AddAssign" => "AddAssign",
87        "BitAndAssign" => "BitAndAssign",
88        "BitOrAssign" => "BitOrAssign",
89        "BitXorAssign" => "BitXorAssign",
90        "SubAssign" => "SubAssign",
91        _ => unimplemented!(),
92    }
93}
94
95/// Matches the provided [`ops::AddAssign`]-like trait `name` to its attribute's name.
96fn trait_name_to_attribute_name(name: &str) -> &'static str {
97    trait_name_to_method_name(name)
98}
99
100/// Matches the provided [`ops::AddAssign`]-like trait `name` to its method name.
101fn trait_name_to_method_name(name: &str) -> &'static str {
102    match name {
103        "AddAssign" => "add_assign",
104        "BitAndAssign" => "bitand_assign",
105        "BitOrAssign" => "bitor_assign",
106        "BitXorAssign" => "bitxor_assign",
107        "SubAssign" => "sub_assign",
108        _ => unimplemented!(),
109    }
110}