derive_more_impl/as/
mod.rs

1//! Implementations of [`AsRef`]/[`AsMut`] derive macros.
2
3pub(crate) mod r#mut;
4pub(crate) mod r#ref;
5
6use std::{borrow::Cow, iter};
7
8use proc_macro2::TokenStream;
9use quote::{quote, ToTokens};
10use syn::{parse_quote, spanned::Spanned, Token};
11
12use crate::utils::{
13    attr::{self, ParseMultiple as _},
14    Either, GenericsSearch, Spanning,
15};
16
17/// Expands an [`AsRef`]/[`AsMut`] derive macro.
18pub fn expand(
19    input: &syn::DeriveInput,
20    trait_info: ExpansionCtx<'_>,
21) -> syn::Result<TokenStream> {
22    let (trait_ident, attr_name, _) = trait_info;
23
24    let data = match &input.data {
25        syn::Data::Struct(data) => Ok(data),
26        syn::Data::Enum(e) => Err(syn::Error::new(
27            e.enum_token.span(),
28            format!("`{trait_ident}` cannot be derived for enums"),
29        )),
30        syn::Data::Union(u) => Err(syn::Error::new(
31            u.union_token.span(),
32            format!("`{trait_ident}` cannot be derived for unions"),
33        )),
34    }?;
35
36    let expansions = if let Some(attr) =
37        StructAttribute::parse_attrs(&input.attrs, attr_name)?
38    {
39        if data.fields.len() != 1 {
40            return Err(syn::Error::new(
41                if data.fields.is_empty() {
42                    data.struct_token.span
43                } else {
44                    data.fields.span()
45                },
46                format!(
47                    "`#[{attr_name}(...)]` attribute can only be placed on structs with exactly \
48                     one field",
49                ),
50            ));
51        }
52
53        let field = data.fields.iter().next().unwrap();
54        if FieldAttribute::parse_attrs(&field.attrs, attr_name)?.is_some() {
55            return Err(syn::Error::new(
56                field.span(),
57                format!("`#[{attr_name}(...)]` cannot be placed on both struct and its field"),
58            ));
59        }
60
61        vec![Expansion {
62            trait_info,
63            ident: &input.ident,
64            generics: &input.generics,
65            field,
66            field_index: 0,
67            conversions: Some(attr.into_inner()),
68        }]
69    } else {
70        let attrs = data
71            .fields
72            .iter()
73            .map(|field| FieldAttribute::parse_attrs(&field.attrs, attr_name))
74            .collect::<syn::Result<Vec<_>>>()?;
75
76        let present_attrs = attrs.iter().filter_map(Option::as_ref).collect::<Vec<_>>();
77
78        let all = present_attrs
79            .iter()
80            .all(|attr| matches!(attr.item, FieldAttribute::Skip(_)));
81
82        if !all {
83            if let Some(skip_attr) = present_attrs.iter().find_map(|attr| {
84                if let FieldAttribute::Skip(skip) = &attr.item {
85                    Some(attr.as_ref().map(|_| skip))
86                } else {
87                    None
88                }
89            }) {
90                return Err(syn::Error::new(
91                    skip_attr.span(),
92                    format!(
93                        "`#[{attr_name}({})]` cannot be used in the same struct with other \
94                         `#[{attr_name}(...)]` attributes",
95                        skip_attr.name(),
96                    ),
97                ));
98            }
99        }
100
101        if all {
102            data.fields
103                .iter()
104                .enumerate()
105                .zip(attrs)
106                .filter_map(|((i, field), attr)| {
107                    attr.is_none().then_some(Expansion {
108                        trait_info,
109                        ident: &input.ident,
110                        generics: &input.generics,
111                        field,
112                        field_index: i,
113                        conversions: None,
114                    })
115                })
116                .collect()
117        } else {
118            data.fields
119                .iter()
120                .enumerate()
121                .zip(attrs)
122                .filter_map(|((i, field), attr)| match attr.map(Spanning::into_inner) {
123                    Some(
124                        attr @ (FieldAttribute::Empty(_)
125                        | FieldAttribute::Forward(_)
126                        | FieldAttribute::Types(_)),
127                    ) => Some(Expansion {
128                        trait_info,
129                        ident: &input.ident,
130                        generics: &input.generics,
131                        field,
132                        field_index: i,
133                        conversions: attr.into(),
134                    }),
135                    Some(FieldAttribute::Skip(_)) => unreachable!(),
136                    None => None,
137                })
138                .collect()
139        }
140    };
141    Ok(expansions
142        .into_iter()
143        .map(ToTokens::into_token_stream)
144        .collect())
145}
146
147/// Type alias for an expansion context:
148/// - [`syn::Ident`] of the derived trait.
149/// - [`syn::Ident`] of the derived trait method.
150/// - Optional `mut` token indicating [`AsMut`] expansion.
151///
152/// [`syn::Ident`]: struct@syn::Ident
153type ExpansionCtx<'a> = (&'a syn::Ident, &'a syn::Ident, Option<&'a Token![mut]>);
154
155/// Expansion of a macro for generating [`AsRef`]/[`AsMut`] implementations for a single field of a
156/// struct.
157struct Expansion<'a> {
158    /// [`ExpansionCtx`] of the derived trait.
159    trait_info: ExpansionCtx<'a>,
160
161    /// [`syn::Ident`] of the struct.
162    ///
163    /// [`syn::Ident`]: struct@syn::Ident
164    ident: &'a syn::Ident,
165
166    /// [`syn::Generics`] of the struct.
167    generics: &'a syn::Generics,
168
169    /// [`syn::Field`] of the struct.
170    field: &'a syn::Field,
171
172    /// Index of the [`syn::Field`].
173    field_index: usize,
174
175    /// Attribute specifying which conversions should be generated.
176    conversions: Option<attr::Conversion>,
177}
178
179impl ToTokens for Expansion<'_> {
180    fn to_tokens(&self, tokens: &mut TokenStream) {
181        let field_ty = &self.field.ty;
182        let field_ident = self.field.ident.as_ref().map_or_else(
183            || Either::Right(syn::Index::from(self.field_index)),
184            Either::Left,
185        );
186
187        let (trait_ident, method_ident, mut_) = &self.trait_info;
188        let ty_ident = &self.ident;
189
190        let field_ref = quote! { & #mut_ self.#field_ident };
191
192        let generics_search = GenericsSearch::from(self.generics);
193        let field_contains_generics = generics_search.any_in(field_ty);
194
195        let is_blanket =
196            matches!(&self.conversions, Some(attr::Conversion::Forward(_)));
197
198        let return_tys = match &self.conversions {
199            Some(attr::Conversion::Forward(_)) => {
200                Either::Left(iter::once(Cow::Owned(parse_quote! { __AsT })))
201            }
202            Some(attr::Conversion::Types(tys)) => {
203                Either::Right(tys.0.iter().map(Cow::Borrowed))
204            }
205            None => Either::Left(iter::once(Cow::Borrowed(field_ty))),
206        };
207
208        for return_ty in return_tys {
209            /// Kind of a generated implementation, chosen based on attribute arguments.
210            enum ImplKind {
211                /// Returns a reference to a field.
212                Direct,
213
214                /// Forwards `as_ref`/`as_mut` call on a field.
215                Forwarded,
216
217                /// Uses autoref-based specialization to determine whether to use direct or
218                /// forwarded implementation, based on whether the field and the return type match.
219                ///
220                /// Doesn't work when generics are involved.
221                Specialized,
222            }
223
224            let impl_kind = if is_blanket {
225                ImplKind::Forwarded
226            } else if field_ty == return_ty.as_ref() {
227                ImplKind::Direct
228            } else if field_contains_generics || generics_search.any_in(&return_ty) {
229                ImplKind::Forwarded
230            } else {
231                ImplKind::Specialized
232            };
233
234            let trait_ty = quote! {
235                derive_more::core::convert::#trait_ident <#return_ty>
236            };
237
238            let generics = match &impl_kind {
239                ImplKind::Forwarded => {
240                    let mut generics = self.generics.clone();
241                    generics
242                        .make_where_clause()
243                        .predicates
244                        .push(parse_quote! { #field_ty: #trait_ty });
245                    if is_blanket {
246                        generics
247                            .params
248                            .push(parse_quote! { #return_ty: ?derive_more::core::marker::Sized });
249                    }
250                    Cow::Owned(generics)
251                }
252                ImplKind::Direct | ImplKind::Specialized => {
253                    Cow::Borrowed(self.generics)
254                }
255            };
256            let (impl_gens, _, where_clause) = generics.split_for_impl();
257            let (_, ty_gens, _) = self.generics.split_for_impl();
258
259            let body = match &impl_kind {
260                ImplKind::Direct => Cow::Borrowed(&field_ref),
261                ImplKind::Forwarded => Cow::Owned(quote! {
262                    <#field_ty as #trait_ty>::#method_ident(#field_ref)
263                }),
264                ImplKind::Specialized => Cow::Owned(quote! {
265                    use derive_more::__private::ExtractRef as _;
266
267                    let conv =
268                        <derive_more::__private::Conv<& #mut_ #field_ty, #return_ty>
269                         as derive_more::core::default::Default>::default();
270                    (&&conv).__extract_ref(#field_ref)
271                }),
272            };
273
274            quote! {
275                #[allow(deprecated)] // omit warnings on deprecated fields/variants
276                #[allow(unreachable_code)] // omit warnings for `!` and other unreachable types
277                #[automatically_derived]
278                impl #impl_gens #trait_ty for #ty_ident #ty_gens #where_clause {
279                    #[inline]
280                    fn #method_ident(& #mut_ self) -> & #mut_ #return_ty {
281                        #body
282                    }
283                }
284            }
285            .to_tokens(tokens);
286        }
287    }
288}
289
290/// Representation of an [`AsRef`]/[`AsMut`] derive macro struct container attribute.
291///
292/// ```rust,ignore
293/// #[as_ref(forward)]
294/// #[as_ref(<types>)]
295/// ```
296type StructAttribute = attr::Conversion;
297
298/// Representation of an [`AsRef`]/[`AsMut`] derive macro field attribute.
299///
300/// ```rust,ignore
301/// #[as_ref]
302/// #[as_ref(skip)] #[as_ref(ignore)]
303/// #[as_ref(forward)]
304/// #[as_ref(<types>)]
305/// ```
306type FieldAttribute = attr::FieldConversion;