derive_more_impl/ops/
mul.rs

1//! Implementation of [`ops::Mul`]-like derive macros.
2
3#[cfg(doc)]
4use std::ops;
5
6use proc_macro2::TokenStream;
7use quote::{format_ident, quote, ToTokens};
8use syn::{parse_quote, spanned::Spanned as _};
9
10use super::{SkippedFields, StructuralExpansion};
11use crate::utils::{
12    attr::{self, ParseMultiple as _},
13    pattern_matching::FieldsExt as _,
14    structural_inclusion::TypeExt as _,
15};
16
17/// Expands an [`ops::Mul`]-like derive macro.
18///
19/// Available macros:
20/// - [`Div`](ops::Div)
21/// - [`Mul`](ops::Mul)
22/// - [`Rem`](ops::Rem)
23/// - [`Shl`](ops::Shl)
24/// - [`Shr`](ops::Shr)
25pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result<TokenStream> {
26    let trait_name = normalize_trait_name(trait_name);
27    let attr_name = format_ident!("{}", trait_name_to_attribute_name(trait_name));
28
29    match &input.data {
30        syn::Data::Struct(data) if matches!(data.fields, syn::Fields::Unit) => {
31            Err(syn::Error::new(
32                data.struct_token.span(),
33                format!("`{trait_name}` cannot be derived for unit structs"),
34            ))
35        }
36        syn::Data::Union(data) => Err(syn::Error::new(
37            data.union_token.span(),
38            format!("`{trait_name}` cannot be derived for unions"),
39        )),
40        _ => {
41            if attr::Forward::parse_attrs(&input.attrs, &attr_name)?.is_some() {
42                expand_structural(input, trait_name, attr_name)
43                    .map(ToTokens::into_token_stream)
44            } else {
45                expand_scalar(input, trait_name, attr_name)
46                    .map(ToTokens::into_token_stream)
47            }
48        }
49    }
50}
51
52/// Expands an [`ops::Mul`]-like derive macro in a structural manner.
53fn expand_structural<'i>(
54    input: &'i syn::DeriveInput,
55    trait_name: &str,
56    attr_name: syn::Ident,
57) -> syn::Result<StructuralExpansion<'i>> {
58    let mut variants = vec![];
59    match &input.data {
60        syn::Data::Struct(data) => {
61            let mut skipped_fields = SkippedFields::default();
62            for (n, field) in data.fields.iter().enumerate() {
63                if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
64                    _ = skipped_fields.insert(n);
65                }
66            }
67            if data.fields.len() == skipped_fields.len() {
68                return Err(syn::Error::new(
69                    data.struct_token.span(),
70                    format!(
71                        "`{trait_name}` cannot be derived for structs with all the fields being \
72                         skipped",
73                    ),
74                ));
75            }
76            variants.push((None, &data.fields, skipped_fields));
77        }
78        syn::Data::Enum(data) => {
79            for variant in &data.variants {
80                if let Some(skip) = attr::Skip::parse_attrs(&variant.attrs, &attr_name)?
81                {
82                    return Err(syn::Error::new(
83                        skip.span,
84                        format!(
85                            "`#[{attr_name}({})]` attribute can be placed only on variant fields",
86                            skip.item.name(),
87                        ),
88                    ));
89                }
90                let mut skipped_fields = SkippedFields::default();
91                for (n, field) in variant.fields.iter().enumerate() {
92                    if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
93                        _ = skipped_fields.insert(n);
94                    }
95                }
96                if !matches!(variant.fields, syn::Fields::Unit)
97                    && variant.fields.len() == skipped_fields.len()
98                {
99                    return Err(syn::Error::new(
100                        variant.span(),
101                        format!(
102                            "`{trait_name}` cannot be derived for enum with all the fields being \
103                             skipped in its variants",
104                        ),
105                    ));
106                }
107                variants.push((Some(&variant.ident), &variant.fields, skipped_fields));
108            }
109        }
110        syn::Data::Union(_) => unreachable!(),
111    }
112
113    Ok(StructuralExpansion {
114        trait_ty: format_ident!("{trait_name}"),
115        method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
116        self_ty: (&input.ident, &input.generics),
117        variants,
118        is_enum: matches!(input.data, syn::Data::Enum(_)),
119    })
120}
121
122/// Expands an [`ops::Mul`]-like derive macro in a scalar manner.
123fn expand_scalar<'i>(
124    input: &'i syn::DeriveInput,
125    trait_name: &str,
126    attr_name: syn::Ident,
127) -> syn::Result<ScalarExpansion<'i>> {
128    let mut skipped_fields = SkippedFields::default();
129    let fields = match &input.data {
130        syn::Data::Struct(data) => {
131            for (n, field) in data.fields.iter().enumerate() {
132                if attr::Skip::parse_attrs(&field.attrs, &attr_name)?.is_some() {
133                    _ = skipped_fields.insert(n);
134                }
135            }
136            if data.fields.len() == skipped_fields.len() {
137                return Err(syn::Error::new(
138                    data.struct_token.span(),
139                    format!(
140                        "`{trait_name}` cannot be derived for structs with all the fields being \
141                         skipped",
142                    ),
143                ));
144            }
145            &data.fields
146        }
147        syn::Data::Enum(data) => {
148            return Err(syn::Error::new(
149                data.enum_token.span(),
150                format!(
151                    "`{trait_name}` can be derived for enums only when using \
152                     `#[{attr_name}(forward)]` attribute",
153                ),
154            ));
155        }
156        syn::Data::Union(_) => unreachable!(),
157    };
158
159    Ok(ScalarExpansion {
160        trait_ty: format_ident!("{trait_name}"),
161        method_ident: format_ident!("{}", trait_name_to_method_name(trait_name)),
162        self_ty: (&input.ident, &input.generics),
163        fields,
164        skipped_fields,
165    })
166}
167
168/// Expansion of a macro for generating a scalar [`ops::Mul`]-like trait implementation for a
169/// struct.
170struct ScalarExpansion<'i> {
171    /// [`syn::Ident`] of the implemented trait.
172    ///
173    /// [`syn::Ident`]: struct@syn::Ident
174    trait_ty: syn::Ident,
175
176    /// [`syn::Ident`] and [`syn::Receiver`] of the implemented method in trait.
177    ///
178    /// [`syn::Ident`]: struct@syn::Ident
179    method_ident: syn::Ident,
180
181    /// [`syn::Ident`] and [`syn::Generics`] of the implementor struct.
182    ///
183    /// [`syn::Ident`]: struct@syn::Ident
184    self_ty: (&'i syn::Ident, &'i syn::Generics),
185
186    /// [`syn::Fields`] of the struct to be used in this [`ScalarExpansion`].
187    fields: &'i syn::Fields,
188
189    /// Indices of the struct [`syn::Fields`] marked with an [`attr::Skip`].
190    skipped_fields: SkippedFields,
191}
192
193impl ToTokens for ScalarExpansion<'_> {
194    fn to_tokens(&self, tokens: &mut TokenStream) {
195        let trait_ty = &self.trait_ty;
196        let method_ident = &self.method_ident;
197
198        let ty = self.self_ty.0;
199        let (_, ty_generics, _) = self.self_ty.1.split_for_impl();
200        let implementor_ty: syn::Type = parse_quote! { #ty #ty_generics };
201        let self_ty: syn::Type = parse_quote! { Self };
202        let rhs_ty: syn::TypeParam = parse_quote! { __derive_more_Rhs };
203
204        let mut generics = self.self_ty.1.clone();
205        generics.params.push(rhs_ty.clone().into());
206        let mut used_fields_count = 0;
207        for field_ty in self.fields.iter().enumerate().filter_map(|(n, field)| {
208            (!self.skipped_fields.contains(&n)).then_some(&field.ty)
209        }) {
210            if !field_ty.contains_type_structurally(&self_ty)
211                && !field_ty.contains_type_structurally(&implementor_ty)
212            {
213                generics.make_where_clause().predicates.push(parse_quote! {
214                    #field_ty: derive_more::core::ops:: #trait_ty <#rhs_ty, Output = #field_ty>
215                });
216            }
217            used_fields_count += 1;
218        }
219        if used_fields_count > 1 {
220            generics.make_where_clause().predicates.push(parse_quote! {
221                #rhs_ty: derive_more::core::marker::Copy
222            });
223        }
224        let (impl_generics, _, where_clause) = generics.split_for_impl();
225
226        let body = {
227            let method_path =
228                parse_quote! { derive_more::core::ops::#trait_ty::#method_ident };
229            let self_pat = self.fields.exhaustive_arm_pattern("__self_");
230            let fields_expr = self.fields.arm_expr(&method_path, &self.skipped_fields);
231
232            quote! {
233                match self {
234                    Self #self_pat => Self #fields_expr,
235                }
236            }
237        };
238
239        quote! {
240            #[allow(private_bounds)]
241            #[automatically_derived]
242            impl #impl_generics derive_more::core::ops:: #trait_ty<#rhs_ty> for #implementor_ty
243                 #where_clause
244            {
245                type Output = #self_ty;
246
247                #[inline]
248                #[track_caller]
249                fn #method_ident(self, __rhs: #rhs_ty) -> Self::Output {
250                    #body
251                }
252            }
253        }
254        .to_tokens(tokens);
255    }
256}
257
258/// Extension of [`syn::Fields`] used by a [`ScalarExpansion`].
259trait ScalarExpansionFieldsExt {
260    /// Generates a resulting expression with these [`syn::Fields`] in a matched arm of a `match`
261    /// expression, by applying the specified method.
262    fn arm_expr(
263        &self,
264        method: &syn::Path,
265        skipped_indices: &SkippedFields,
266    ) -> TokenStream;
267}
268
269impl ScalarExpansionFieldsExt for syn::Fields {
270    fn arm_expr(
271        &self,
272        method_path: &syn::Path,
273        skipped_indices: &SkippedFields,
274    ) -> TokenStream {
275        match self {
276            Self::Named(fields) => {
277                let fields = fields.named.iter().enumerate().map(|(num, field)| {
278                    let name = &field.ident;
279                    let self_val = format_ident!("__self_{num}");
280                    if skipped_indices.contains(&num) {
281                        quote! { #name: #self_val }
282                    } else {
283                        quote! { #name: #method_path(#self_val, __rhs) }
284                    }
285                });
286                quote! {{ #( #fields , )* }}
287            }
288            Self::Unnamed(fields) => {
289                let fields = (0..fields.unnamed.len()).map(|num| {
290                    let self_val = format_ident!("__self_{num}");
291                    if skipped_indices.contains(&num) {
292                        quote! { #self_val }
293                    } else {
294                        quote! { #method_path(#self_val, __rhs) }
295                    }
296                });
297                quote! {( #( #fields , )* )}
298            }
299            Self::Unit => quote! {},
300        }
301    }
302}
303
304/// Matches the provided derive macro `name` to appropriate actual trait name.
305fn normalize_trait_name(name: &str) -> &'static str {
306    match name {
307        "Div" => "Div",
308        "Mul" => "Mul",
309        "Rem" => "Rem",
310        "Shl" => "Shl",
311        "Shr" => "Shr",
312        _ => unimplemented!(),
313    }
314}
315
316/// Matches the provided [`ops::Mul`]-like trait `name` to its attribute's name.
317fn trait_name_to_attribute_name(name: &str) -> &'static str {
318    trait_name_to_method_name(name)
319}
320
321/// Matches the provided [`ops::Mul`]-like trait `name` to its method name.
322fn trait_name_to_method_name(name: &str) -> &'static str {
323    match name {
324        "Div" => "div",
325        "Mul" => "mul",
326        "Rem" => "rem",
327        "Shl" => "shl",
328        "Shr" => "shr",
329        _ => unimplemented!(),
330    }
331}