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: ConversionsAttribute) => 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: Skip) => Self {
226 skip: Some(skip),
227 convs: None,
228 },
229 Untyped::Right(c: Either) => Self {
230 skip: None,
231 convs: Some(match c {
232 Either::Left(_empty: Empty) => ConversionsAttribute::default(),
233 Either::Right(convs: ConversionsAttribute) => 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(tokens: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}
628

Provided by KDAB

Privacy Policy