1#[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
17pub 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
52fn 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
122fn 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
168struct ScalarExpansion<'i> {
171 trait_ty: syn::Ident,
175
176 method_ident: syn::Ident,
180
181 self_ty: (&'i syn::Ident, &'i syn::Generics),
185
186 fields: &'i syn::Fields,
188
189 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
258trait ScalarExpansionFieldsExt {
260 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
304fn 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
316fn trait_name_to_attribute_name(name: &str) -> &'static str {
318 trait_name_to_method_name(name)
319}
320
321fn 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}