derive_more_impl/
into.rs

1//! Implementation of an [`Into`] derive macro.
2
3use std::{
4    any::{Any, TypeId},
5    borrow::Cow,
6    iter, slice,
7};
8
9use proc_macro2::{Span, TokenStream};
10use quote::{format_ident, quote, ToTokens as _};
11use syn::{
12    ext::IdentExt as _,
13    parse::{discouraged::Speculative as _, Parse, ParseStream},
14    punctuated::Punctuated,
15    spanned::Spanned as _,
16    token,
17};
18
19use crate::utils::{
20    attr::{self, ParseMultiple as _},
21    polyfill, Either, FieldsExt, Spanning,
22};
23
24/// Expands an [`Into`] derive macro.
25pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> {
26    let attr_name = format_ident!("into");
27
28    let data = match &input.data {
29        syn::Data::Struct(data) => Ok(data),
30        syn::Data::Enum(e) => Err(syn::Error::new(
31            e.enum_token.span(),
32            "`Into` cannot be derived for enums",
33        )),
34        syn::Data::Union(u) => Err(syn::Error::new(
35            u.union_token.span(),
36            "`Into` cannot be derived for unions",
37        )),
38    }?;
39
40    let struct_attr = StructAttribute::parse_attrs_with(
41        &input.attrs,
42        &attr_name,
43        &ConsiderLegacySyntax {
44            fields: &data.fields,
45        },
46    )?
47    .map(Spanning::into_inner);
48
49    let fields_data = data
50        .fields
51        .iter()
52        .enumerate()
53        .map(|(i, f)| {
54            let field_attr = FieldAttribute::parse_attrs_with(
55                &f.attrs,
56                &attr_name,
57                &ConsiderLegacySyntax {
58                    fields: slice::from_ref(f),
59                },
60            )?
61            .map(Spanning::into_inner);
62
63            let skip = field_attr
64                .as_ref()
65                .map(|attr| attr.skip.is_some())
66                .unwrap_or(false);
67
68            let convs = field_attr.and_then(|attr| attr.convs);
69
70            Ok(((i, f, skip), convs))
71        })
72        .collect::<syn::Result<Vec<_>>>()?;
73    let (fields, fields_convs): (Vec<_>, Vec<_>) = fields_data.into_iter().unzip();
74
75    let struct_attr = struct_attr.or_else(|| {
76        fields_convs
77            .iter()
78            .all(Option::is_none)
79            .then(ConversionsAttribute::default)
80            .map(Either::Right)
81    });
82
83    let mut expansions: Vec<_> = fields
84        .iter()
85        .zip(fields_convs)
86        .filter_map(|(&(i, field, _), convs)| {
87            convs.map(|convs| Expansion {
88                input_ident: &input.ident,
89                input_generics: &input.generics,
90                fields: vec![(i, field)],
91                convs,
92            })
93        })
94        .collect();
95    if let Some(attr) = struct_attr {
96        expansions.push(Expansion {
97            input_ident: &input.ident,
98            input_generics: &input.generics,
99            fields: fields
100                .into_iter()
101                .filter_map(|(i, f, skip)| (!skip).then_some((i, f)))
102                .collect(),
103            convs: attr.into(),
104        });
105    }
106    expansions.into_iter().map(Expansion::expand).collect()
107}
108
109/// Expansion of an [`Into`] derive macro, generating [`From`] implementations for a struct.
110struct Expansion<'a> {
111    /// [`syn::Ident`] of the struct.
112    ///
113    /// [`syn::Ident`]: struct@syn::Ident
114    input_ident: &'a syn::Ident,
115
116    /// [`syn::Generics`] of the struct.
117    input_generics: &'a syn::Generics,
118
119    /// Fields to convert from, along with their indices.
120    fields: Vec<(usize, &'a syn::Field)>,
121
122    /// Conversions to be generated.
123    convs: ConversionsAttribute,
124}
125
126impl Expansion<'_> {
127    fn expand(self) -> syn::Result<TokenStream> {
128        let Self {
129            input_ident,
130            input_generics,
131            fields,
132            convs,
133        } = self;
134
135        let fields_idents: Vec<_> = fields
136            .iter()
137            .map(|(i, f)| {
138                f.ident
139                    .as_ref()
140                    .map_or_else(|| Either::Left(syn::Index::from(*i)), Either::Right)
141            })
142            .collect();
143        let fields_tys: Vec<_> = fields.iter().map(|(_, f)| &f.ty).collect();
144        let fields_tuple = syn::Type::Tuple(syn::TypeTuple {
145            paren_token: token::Paren::default(),
146            elems: fields_tys.iter().cloned().cloned().collect(),
147        });
148
149        [
150            (&convs.owned, false, false),
151            (&convs.r#ref, true, false),
152            (&convs.ref_mut, true, true),
153        ]
154        .into_iter()
155        .filter(|(conv, _, _)| conv.consider_fields_ty || !conv.tys.is_empty())
156        .map(|(conv, ref_, mut_)| {
157            let lf = ref_.then(|| syn::Lifetime::new("'__derive_more_into", Span::call_site()));
158            let r = ref_.then(token::And::default);
159            let m = mut_.then(token::Mut::default);
160
161            let gens = if let Some(lf) = lf.clone() {
162                let mut gens = input_generics.clone();
163                gens.params.push(syn::LifetimeParam::new(lf).into());
164                Cow::Owned(gens)
165            } else {
166                Cow::Borrowed(input_generics)
167            };
168            let (impl_gens, _, where_clause) = gens.split_for_impl();
169            let (_, ty_gens, _) = input_generics.split_for_impl();
170
171            if conv.consider_fields_ty {
172                Either::Left(iter::once(&fields_tuple))
173            } else {
174                Either::Right(iter::empty())
175            }
176            .chain(&conv.tys)
177            .map(|out_ty| {
178                let tys: Vec<_> = fields_tys.validate_type(out_ty)?.collect();
179
180                Ok(quote! {
181                    #[allow(clippy::unused_unit)]
182                    #[automatically_derived]
183                    impl #impl_gens derive_more::core::convert::From<#r #lf #m #input_ident #ty_gens>
184                     for ( #( #r #lf #m #tys ),* ) #where_clause
185                    {
186                        #[inline]
187                        fn from(value: #r #lf #m #input_ident #ty_gens) -> Self {
188                            (#(
189                                <#r #m #tys as derive_more::core::convert::From<_>>::from(
190                                    #r #m value. #fields_idents
191                                )
192                            ),*)
193                        }
194                    }
195                })
196            })
197            .collect::<syn::Result<TokenStream>>()
198        })
199        .collect()
200    }
201}
202
203/// Representation of an [`Into`] derive macro struct container attribute.
204///
205/// ```rust,ignore
206/// #[into]
207/// #[into(<types>)]
208/// #[into(owned(<types>), ref(<types>), ref_mut(<types>))]
209/// ```
210type StructAttribute = Either<attr::Empty, ConversionsAttribute>;
211
212impl From<StructAttribute> for ConversionsAttribute {
213    fn from(v: StructAttribute) -> Self {
214        match v {
215            Either::Left(_) => ConversionsAttribute::default(),
216            Either::Right(c) => c,
217        }
218    }
219}
220
221type Untyped = Either<attr::Skip, Either<attr::Empty, ConversionsAttribute>>;
222impl From<Untyped> for FieldAttribute {
223    fn from(v: Untyped) -> Self {
224        match v {
225            Untyped::Left(skip) => Self {
226                skip: Some(skip),
227                convs: None,
228            },
229            Untyped::Right(c) => Self {
230                skip: None,
231                convs: Some(match c {
232                    Either::Left(_empty) => ConversionsAttribute::default(),
233                    Either::Right(convs) => convs,
234                }),
235            },
236        }
237    }
238}
239
240/// Representation of an [`Into`] derive macro field attribute.
241///
242/// ```rust,ignore
243/// #[into]
244/// #[into(<types>)]
245/// #[into(owned(<types>), ref(<types>), ref_mut(<types>))]
246/// #[into(skip)] #[into(ignore)]
247/// ```
248#[derive(Clone, Debug)]
249struct FieldAttribute {
250    skip: Option<attr::Skip>,
251    convs: Option<ConversionsAttribute>,
252}
253
254impl Parse for FieldAttribute {
255    fn parse(_: ParseStream<'_>) -> syn::Result<Self> {
256        unreachable!("call `attr::ParseMultiple::parse_attr_with()` instead")
257    }
258}
259
260impl attr::ParseMultiple for FieldAttribute {
261    fn parse_attr_with<P: attr::Parser>(
262        attr: &syn::Attribute,
263        parser: &P,
264    ) -> syn::Result<Self> {
265        Untyped::parse_attr_with(attr, parser).map(Self::from)
266    }
267
268    fn merge_attrs(
269        prev: Spanning<Self>,
270        new: Spanning<Self>,
271        name: &syn::Ident,
272    ) -> syn::Result<Spanning<Self>> {
273        let skip = attr::Skip::merge_opt_attrs(
274            prev.clone().map(|v| v.skip).transpose(),
275            new.clone().map(|v| v.skip).transpose(),
276            name,
277        )?
278        .map(Spanning::into_inner);
279
280        let convs = ConversionsAttribute::merge_opt_attrs(
281            prev.clone().map(|v| v.convs).transpose(),
282            new.clone().map(|v| v.convs).transpose(),
283            name,
284        )?
285        .map(Spanning::into_inner);
286
287        Ok(Spanning::new(
288            Self { skip, convs },
289            prev.span.join(new.span).unwrap_or(prev.span),
290        ))
291    }
292}
293
294/// [`Into`] conversions specified by a [`ConversionsAttribute`].
295#[derive(Clone, Debug, Default)]
296struct Conversions {
297    /// Indicator whether these [`Conversions`] should contain a conversion into fields type.
298    consider_fields_ty: bool,
299
300    /// [`syn::Type`]s explicitly specified in a [`ConversionsAttribute`].
301    tys: Punctuated<syn::Type, token::Comma>,
302}
303
304/// Representation of an [`Into`] derive macro attribute describing specified [`Into`] conversions.
305///
306/// ```rust,ignore
307/// #[into(<types>)]
308/// #[into(owned(<types>), ref(<types>), ref_mut(<types>))]
309/// ```
310#[derive(Clone, Debug)]
311struct ConversionsAttribute {
312    /// [`syn::Type`]s wrapped into `owned(...)` or simply `#[into(...)]`.
313    owned: Conversions,
314
315    /// [`syn::Type`]s wrapped into `ref(...)`.
316    r#ref: Conversions,
317
318    /// [`syn::Type`]s wrapped into `ref_mut(...)`.
319    ref_mut: Conversions,
320}
321
322impl Default for ConversionsAttribute {
323    fn default() -> Self {
324        Self {
325            owned: Conversions {
326                consider_fields_ty: true,
327                tys: Punctuated::new(),
328            },
329            r#ref: Conversions::default(),
330            ref_mut: Conversions::default(),
331        }
332    }
333}
334
335impl Parse for ConversionsAttribute {
336    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
337        let mut out = Self {
338            owned: Conversions::default(),
339            r#ref: Conversions::default(),
340            ref_mut: Conversions::default(),
341        };
342
343        let parse_inner = |ahead, convs: &mut Conversions| {
344            input.advance_to(&ahead);
345
346            if input.peek(token::Paren) {
347                let inner;
348                syn::parenthesized!(inner in input);
349
350                convs.tys.extend(
351                    inner
352                        .parse_terminated(syn::Type::parse, token::Comma)?
353                        .into_pairs(),
354                );
355            } else {
356                convs.consider_fields_ty = true;
357            }
358
359            if input.peek(token::Comma) {
360                let comma = input.parse::<token::Comma>()?;
361                if !convs.tys.empty_or_trailing() {
362                    convs.tys.push_punct(comma);
363                }
364            }
365
366            Ok(())
367        };
368
369        let mut has_wrapped_type = false;
370        let mut top_level_type = None;
371
372        while !input.is_empty() {
373            let ahead = input.fork();
374            let res = if ahead.peek(syn::Ident::peek_any) {
375                ahead.call(syn::Ident::parse_any).map(Into::into)
376            } else {
377                ahead.parse::<syn::Path>()
378            };
379            match res {
380                Ok(p) if p.is_ident("owned") => {
381                    has_wrapped_type = true;
382                    parse_inner(ahead, &mut out.owned)?;
383                }
384                Ok(p) if p.is_ident("ref") => {
385                    has_wrapped_type = true;
386                    parse_inner(ahead, &mut out.r#ref)?;
387                }
388                Ok(p) if p.is_ident("ref_mut") => {
389                    has_wrapped_type = true;
390                    parse_inner(ahead, &mut out.ref_mut)?;
391                }
392                _ => {
393                    let ty = input.parse::<syn::Type>()?;
394                    let _ = top_level_type.get_or_insert_with(|| ty.clone());
395                    out.owned.tys.push_value(ty);
396
397                    if input.peek(token::Comma) {
398                        out.owned.tys.push_punct(input.parse::<token::Comma>()?)
399                    }
400                }
401            }
402        }
403
404        if let Some(ty) = top_level_type.filter(|_| has_wrapped_type) {
405            Err(syn::Error::new(
406                ty.span(),
407                format!(
408                    "mixing regular types with wrapped into `owned`/`ref`/`ref_mut` is not \
409                     allowed, try wrapping this type into `owned({ty}), ref({ty}), ref_mut({ty})`",
410                    ty = ty.into_token_stream(),
411                ),
412            ))
413        } else {
414            Ok(out)
415        }
416    }
417}
418
419impl attr::ParseMultiple for ConversionsAttribute {
420    fn merge_attrs(
421        prev: Spanning<Self>,
422        new: Spanning<Self>,
423        _: &syn::Ident,
424    ) -> syn::Result<Spanning<Self>> {
425        let Spanning {
426            span: prev_span,
427            item: mut prev,
428        } = prev;
429        let Spanning {
430            span: new_span,
431            item: new,
432        } = new;
433
434        prev.owned.tys.extend(new.owned.tys);
435        prev.owned.consider_fields_ty |= new.owned.consider_fields_ty;
436        prev.r#ref.tys.extend(new.r#ref.tys);
437        prev.r#ref.consider_fields_ty |= new.r#ref.consider_fields_ty;
438        prev.ref_mut.tys.extend(new.ref_mut.tys);
439        prev.ref_mut.consider_fields_ty |= new.ref_mut.consider_fields_ty;
440
441        Ok(Spanning::new(
442            prev,
443            prev_span.join(new_span).unwrap_or(prev_span),
444        ))
445    }
446}
447
448/// [`attr::Parser`] considering legacy syntax and performing [`check_legacy_syntax()`] for a
449/// [`StructAttribute`] or a [`FieldAttribute`].
450struct ConsiderLegacySyntax<F> {
451    /// [`syn::Field`]s the [`StructAttribute`] or [`FieldAttribute`] is parsed for.
452    fields: F,
453}
454
455impl<'a, F> attr::Parser for ConsiderLegacySyntax<&'a F>
456where
457    F: FieldsExt + ?Sized,
458    &'a F: IntoIterator<Item = &'a syn::Field>,
459{
460    fn parse<T: Parse + Any>(&self, input: ParseStream<'_>) -> syn::Result<T> {
461        if TypeId::of::<T>() == TypeId::of::<ConversionsAttribute>() {
462            check_legacy_syntax(input, self.fields)?;
463        }
464        T::parse(input)
465    }
466}
467
468/// [`Error`]ors for legacy syntax: `#[into(types(i32, "&str"))]`.
469///
470/// [`Error`]: syn::Error
471fn check_legacy_syntax<'a, F>(tokens: ParseStream<'_>, fields: &'a F) -> syn::Result<()>
472where
473    F: FieldsExt + ?Sized,
474    &'a F: IntoIterator<Item = &'a syn::Field>,
475{
476    let span = tokens.span();
477    let tokens = tokens.fork();
478
479    let map_ty = |s: String| {
480        if fields.len() > 1 {
481            format!(
482                "({})",
483                (0..fields.len())
484                    .map(|_| s.as_str())
485                    .collect::<Vec<_>>()
486                    .join(", ")
487            )
488        } else {
489            s
490        }
491    };
492    let field = match fields.len() {
493        0 => None,
494        1 => Some(
495            fields
496                .into_iter()
497                .next()
498                .unwrap_or_else(|| unreachable!("fields.len() == 1"))
499                .ty
500                .to_token_stream()
501                .to_string(),
502        ),
503        _ => Some(format!(
504            "({})",
505            fields
506                .into_iter()
507                .map(|f| f.ty.to_token_stream().to_string())
508                .collect::<Vec<_>>()
509                .join(", ")
510        )),
511    };
512
513    let Ok(metas) = tokens.parse_terminated(polyfill::Meta::parse, token::Comma) else {
514        return Ok(());
515    };
516
517    let parse_list = |list: polyfill::MetaList, attrs: &mut Option<Vec<_>>| {
518        if !list.path.is_ident("types") {
519            return None;
520        }
521        for meta in list
522            .parse_args_with(Punctuated::<_, token::Comma>::parse_terminated)
523            .ok()?
524        {
525            attrs.get_or_insert_with(Vec::new).push(match meta {
526                polyfill::NestedMeta::Lit(syn::Lit::Str(str)) => str.value(),
527                polyfill::NestedMeta::Meta(polyfill::Meta::Path(path)) => {
528                    path.into_token_stream().to_string()
529                }
530                _ => return None,
531            })
532        }
533        Some(())
534    };
535
536    let Some((top_level, owned, ref_, ref_mut)) = metas
537            .into_iter()
538            .try_fold(
539                (None, None, None, None),
540                |(mut top_level, mut owned, mut ref_, mut ref_mut), meta| {
541                    let is = |name| {
542                        matches!(&meta, polyfill::Meta::Path(p) if p.is_ident(name))
543                            || matches!(&meta, polyfill::Meta::List(list) if list.path.is_ident(name))
544                    };
545                    let parse_inner = |meta, attrs: &mut Option<_>| {
546                        match meta {
547                            polyfill::Meta::Path(_) => {
548                                let _ = attrs.get_or_insert_with(Vec::new);
549                                Some(())
550                            }
551                            polyfill::Meta::List(list) => {
552                                if let polyfill::NestedMeta::Meta(polyfill::Meta::List(list)) = list
553                                    .parse_args_with(Punctuated::<_, token::Comma>::parse_terminated)
554                                    .ok()?
555                                    .pop()?
556                                    .into_value()
557                                {
558                                    parse_list(list, attrs)
559                                } else {
560                                    None
561                                }
562                            }
563                        }
564                    };
565
566                    match meta {
567                        meta if is("owned") => parse_inner(meta, &mut owned),
568                        meta if is("ref") => parse_inner(meta, &mut ref_),
569                        meta if is("ref_mut") => parse_inner(meta, &mut ref_mut),
570                        polyfill::Meta::List(list) => parse_list(list, &mut top_level),
571                        _ => None,
572                    }
573                    .map(|_| (top_level, owned, ref_, ref_mut))
574                },
575            )
576            .filter(|(top_level, owned, ref_, ref_mut)| {
577                [top_level, owned, ref_, ref_mut]
578                    .into_iter()
579                    .any(|l| l.as_ref().is_some_and(|l| !l.is_empty()))
580            })
581        else {
582            return Ok(());
583        };
584
585    if [&owned, &ref_, &ref_mut].into_iter().any(Option::is_some) {
586        let format = |list: Option<Vec<_>>, name: &str| match list {
587            Some(l)
588                if top_level.as_ref().map_or(true, Vec::is_empty) && l.is_empty() =>
589            {
590                Some(name.to_owned())
591            }
592            Some(l) => Some(format!(
593                "{}({})",
594                name,
595                l.into_iter()
596                    .chain(top_level.clone().into_iter().flatten())
597                    .map(map_ty)
598                    .chain(field.clone())
599                    .collect::<Vec<_>>()
600                    .join(", "),
601            )),
602            None => None,
603        };
604        let format = [
605            format(owned, "owned"),
606            format(ref_, "ref"),
607            format(ref_mut, "ref_mut"),
608        ]
609        .into_iter()
610        .flatten()
611        .collect::<Vec<_>>()
612        .join(", ");
613
614        Err(syn::Error::new(
615            span,
616            format!("legacy syntax, use `{format}` instead"),
617        ))
618    } else {
619        Err(syn::Error::new(
620            span,
621            format!(
622                "legacy syntax, remove `types` and use `{}` instead",
623                top_level.unwrap_or_else(|| unreachable!()).join(", "),
624            ),
625        ))
626    }
627}