From 2df8ad0916e185557d41bb7492f5b01d362c9850 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Thu, 10 Aug 2023 12:25:22 +0300 Subject: [PATCH 01/44] Allow empty `#[into]` attribute --- impl/src/into.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 91e23bf4..e0ca0425 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -117,6 +117,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result)] /// #[into(owned(), ref(), ref_mut())] /// ``` @@ -138,13 +139,6 @@ impl StructAttribute { attrs: impl AsRef<[syn::Attribute]>, fields: &syn::Fields, ) -> syn::Result> { - fn infer(v: T) -> T - where - T: for<'a> FnOnce(ParseStream<'a>) -> syn::Result, - { - v - } - attrs .as_ref() .iter() @@ -158,8 +152,7 @@ impl StructAttribute { (Some(_), None) | (None, None) => {} }; - let field_attr = - attr.parse_args_with(infer(|stream| Self::parse(stream, fields)))?; + let field_attr = Self::parse_attr(attr, fields)?; let out = attrs.get_or_insert_with(Self::default); merge(&mut out.owned, field_attr.owned); merge(&mut out.r#ref, field_attr.r#ref); @@ -169,7 +162,22 @@ impl StructAttribute { }) } - /// Parses a single [`StructAttribute`]. + /// Parses a single [`StructAttribute`] + fn parse_attr(attr: &syn::Attribute, fields: &syn::Fields) -> syn::Result { + if matches!(attr.meta, syn::Meta::Path(_)) { + Ok(Self { + owned: Some(Punctuated::new()), + r#ref: None, + ref_mut: None, + }) + } else { + attr.parse_args_with(|content: ParseStream<'_>| { + Self::parse(content, fields) + }) + } + } + + /// Parses a single [`StructAttribute`]'s arguments fn parse(content: ParseStream<'_>, fields: &syn::Fields) -> syn::Result { check_legacy_syntax(content, fields)?; From c8688d99ffbe0829410ed6aa7a12221d1fb1d27e Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Thu, 10 Aug 2023 13:33:28 +0300 Subject: [PATCH 02/44] Abstract over fields type in check_legacy_syntax --- impl/src/into.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index e0ca0425..d15d986c 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -9,12 +9,12 @@ use syn::{ parse::{discouraged::Speculative as _, Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned as _, - token, Ident, + token, Field, Ident, }; use crate::{ parsing::Type, - utils::{polyfill, Either, FieldsExt as _}, + utils::{polyfill, Either, FieldsExt}, }; /// Expands an [`Into`] derive macro. @@ -298,10 +298,11 @@ impl Parse for SkipFieldAttribute { } /// [`Error`]ors for legacy syntax: `#[into(types(i32, "&str"))]`. -fn check_legacy_syntax( - tokens: ParseStream<'_>, - fields: &syn::Fields, -) -> syn::Result<()> { +fn check_legacy_syntax<'a, F>(tokens: ParseStream<'_>, fields: &'a F) -> syn::Result<()> +where + F: FieldsExt, + &'a F: IntoIterator, +{ let span = tokens.span(); let tokens = tokens.fork(); @@ -322,7 +323,7 @@ fn check_legacy_syntax( 0 => None, 1 => Some( fields - .iter() + .into_iter() .next() .unwrap_or_else(|| unreachable!("fields.len() == 1")) .ty @@ -332,7 +333,7 @@ fn check_legacy_syntax( _ => Some(format!( "({})", fields - .iter() + .into_iter() .map(|f| f.ty.to_token_stream().to_string()) .collect::>() .join(", ") From 7f3953691b385f2227384549ffebadaa36ce886f Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Thu, 10 Aug 2023 13:51:59 +0300 Subject: [PATCH 03/44] Fix fields type abstraction to work with slices --- impl/src/into.rs | 14 +++++++++++++- impl/src/utils.rs | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index d15d986c..b379d9ab 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -260,6 +260,18 @@ impl StructAttribute { } } +enum FieldAttribute { + Skip, + Args(StructAttribute), +} + +impl FieldAttribute { + fn parse(content: ParseStream, field: &Field) -> syn::Result { + check_legacy_syntax(content, std::slice::from_ref(field))?; + todo!() + } +} + /// `#[into(skip)]` field attribute. struct SkipFieldAttribute; @@ -300,7 +312,7 @@ impl Parse for SkipFieldAttribute { /// [`Error`]ors for legacy syntax: `#[into(types(i32, "&str"))]`. fn check_legacy_syntax<'a, F>(tokens: ParseStream<'_>, fields: &'a F) -> syn::Result<()> where - F: FieldsExt, + F: FieldsExt + ?Sized, &'a F: IntoIterator, { let span = tokens.span(); diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 95c3f915..b7277668 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1443,7 +1443,7 @@ mod fields_ext { } } - impl Len for Vec { + impl Len for [T] { fn len(&self) -> usize { self.len() } From df13fc96ae63331069cc5e3f5a8d4870028bd882 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Thu, 10 Aug 2023 14:19:14 +0300 Subject: [PATCH 04/44] Implement field attribute parsing --- impl/src/into.rs | 91 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index b379d9ab..e9cca753 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -144,19 +144,11 @@ impl StructAttribute { .iter() .filter(|attr| attr.path().is_ident("into")) .try_fold(None, |mut attrs, attr| { - let merge = |out: &mut Option<_>, tys| match (out.as_mut(), tys) { - (None, Some(tys)) => { - *out = Some::>(tys); - } - (Some(out), Some(tys)) => out.extend(tys), - (Some(_), None) | (None, None) => {} - }; - let field_attr = Self::parse_attr(attr, fields)?; let out = attrs.get_or_insert_with(Self::default); - merge(&mut out.owned, field_attr.owned); - merge(&mut out.r#ref, field_attr.r#ref); - merge(&mut out.ref_mut, field_attr.ref_mut); + merge_tys(&mut out.owned, field_attr.owned); + merge_tys(&mut out.r#ref, field_attr.r#ref); + merge_tys(&mut out.ref_mut, field_attr.ref_mut); Ok(attrs) }) @@ -165,11 +157,7 @@ impl StructAttribute { /// Parses a single [`StructAttribute`] fn parse_attr(attr: &syn::Attribute, fields: &syn::Fields) -> syn::Result { if matches!(attr.meta, syn::Meta::Path(_)) { - Ok(Self { - owned: Some(Punctuated::new()), - r#ref: None, - ref_mut: None, - }) + Ok(Self::all_owned()) } else { attr.parse_args_with(|content: ParseStream<'_>| { Self::parse(content, fields) @@ -178,7 +166,11 @@ impl StructAttribute { } /// Parses a single [`StructAttribute`]'s arguments - fn parse(content: ParseStream<'_>, fields: &syn::Fields) -> syn::Result { + fn parse<'a, F>(content: ParseStream<'_>, fields: &'a F) -> syn::Result + where + F: FieldsExt + ?Sized, + &'a F: IntoIterator, + { check_legacy_syntax(content, fields)?; let mut out = Self::default(); @@ -258,6 +250,14 @@ impl StructAttribute { Ok(out) } } + + fn all_owned() -> Self { + Self { + owned: Some(Punctuated::new()), + r#ref: None, + ref_mut: None, + } + } } enum FieldAttribute { @@ -266,12 +266,65 @@ enum FieldAttribute { } impl FieldAttribute { + fn parse_attrs( + attrs: impl AsRef<[syn::Attribute]>, + field: &Field, + ) -> syn::Result> { + attrs + .as_ref() + .iter() + .filter(|attr| attr.path().is_ident("into")) + .try_fold(None, |attrs, attr| { + let field_attr = Self::parse_attr(attr, field)?; + match (attrs, field_attr) { + (Some(Self::Args(mut args)), Self::Args(more)) => { + merge_tys(&mut args.owned, more.owned); + merge_tys(&mut args.r#ref, more.r#ref); + merge_tys(&mut args.ref_mut, more.ref_mut); + Ok(Some(Self::Args(args))) + } + (None, field_attr) => Ok(Some(field_attr)), + (Some(_), _) => Err(syn::Error::new( + attr.path().span(), + "only single `#[into(...)]` attribute is allowed here", + )), + } + }) + } + + fn parse_attr(attr: &syn::Attribute, field: &Field) -> syn::Result { + if matches!(attr.meta, syn::Meta::Path(_)) { + Ok(Self::Args(StructAttribute::all_owned())) + } else { + attr.parse_args_with(|content: ParseStream| Self::parse(content, field)) + } + } + fn parse(content: ParseStream, field: &Field) -> syn::Result { - check_legacy_syntax(content, std::slice::from_ref(field))?; - todo!() + let ahead = content.fork(); + match ahead.parse::() { + Ok(p) if p.is_ident("skip") | p.is_ident("ignore") => Ok(Self::Skip), + _ => { + let fields = std::slice::from_ref(field); + StructAttribute::parse(content, fields).map(Self::Args) + } + } } } +fn merge_tys( + out: &mut Option>, + tys: Option>, +) { + match (out.as_mut(), tys) { + (None, Some(tys)) => { + *out = Some::>(tys); + } + (Some(out), Some(tys)) => out.extend(tys), + (Some(_), None) | (None, None) => {} + }; +} + /// `#[into(skip)]` field attribute. struct SkipFieldAttribute; From 37671b59c3f9e9570b442915398684f7db06e2f6 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 11 Aug 2023 12:30:23 +0300 Subject: [PATCH 05/44] Extract expand_attr function --- impl/src/into.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index e9cca753..469c382d 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -54,9 +54,18 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result>>()?; let (fields_tys, fields_idents): (Vec<_>, Vec<_>) = fields.into_iter().unzip(); - let (fields_tys, fields_idents) = (&fields_tys, &fields_idents); - let expand = |tys: Option>, r: bool, m: bool| { + expand_attr(&input.generics, ident, &fields_tys, &fields_idents, attr).collect() +} + +fn expand_attr<'a>( + generics: &'a syn::Generics, + ident: &'a Ident, + fields_tys: &'a [&syn::Type], + fields_idents: &'a [Either<&'a Ident, syn::Index>], + attr: StructAttribute, +) -> impl Iterator> + 'a { + let expand_one = |tys: Option>, r: bool, m: bool| { let Some(tys) = tys else { return Either::Left(iter::empty()); }; @@ -67,11 +76,11 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result>(); let (impl_gens, _, where_clause) = gens.split_for_impl(); - let (_, ty_gens, _) = input.generics.split_for_impl(); + let (_, ty_gens, _) = generics.split_for_impl(); Ok(quote! { #[automatically_derived] @@ -103,15 +112,13 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result Date: Fri, 11 Aug 2023 13:29:39 +0300 Subject: [PATCH 06/44] Expand both struct and field attrs --- impl/src/into.rs | 132 ++++++++++++++++++++++++---------------------- impl/src/utils.rs | 1 + 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 469c382d..40be0f16 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -31,40 +31,82 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result Some(Ok(( - &f.ty, - f.ident - .as_ref() - .map_or_else(|| Either::Right(syn::Index::from(i)), Either::Left), - ))), - Ok(Some(_)) => None, - Err(e) => Some(Err(e)), + .filter_map(|(i, f)| { + let field_attr = match FieldAttribute::parse_attrs(&f.attrs, f) { + Ok(field_attr) => field_attr, + Err(err) => return Some(Err(err)), + }; + + let args = match field_attr { + Some(FieldAttribute::Skip) => return None, + Some(FieldAttribute::Args(args)) => Some(args), + None => None, + }; + + let ident = f + .ident + .as_ref() + .map_or_else(|| Either::Right(syn::Index::from(i)), Either::Left); + + Some(Ok(((&f.ty, ident), args))) }) - .collect::>>()?; - let (fields_tys, fields_idents): (Vec<_>, Vec<_>) = fields.into_iter().unzip(); + .collect::>>()? + .into_iter() + .unzip::<_, _, Vec<_>, Vec<_>>(); + + let struct_attr = struct_attr.or_else(|| { + args.iter() + .all(|arg| arg.is_none()) + .then(StructAttribute::all_owned) + }); + + let mut expands = fields + .iter() + .zip(args) + .filter_map(|((field_ty, ident), attr)| { + attr.map(|attr| { + expand_attr( + &input.generics, + &input.ident, + std::slice::from_ref(field_ty), + std::slice::from_ref(ident), + attr, + ) + }) + }) + .collect::>()?; + + if let Some(struct_attr) = struct_attr { + let (fields_tys, fields_idents) = + fields.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); + + let struct_expand = expand_attr( + &input.generics, + &input.ident, + &fields_tys, + &fields_idents, + struct_attr, + )?; - expand_attr(&input.generics, ident, &fields_tys, &fields_idents, attr).collect() + expands.extend(struct_expand); + } + + Ok(expands) } -fn expand_attr<'a>( - generics: &'a syn::Generics, - ident: &'a Ident, - fields_tys: &'a [&syn::Type], - fields_idents: &'a [Either<&'a Ident, syn::Index>], +fn expand_attr( + generics: &syn::Generics, + ident: &Ident, + fields_tys: &[&syn::Type], + fields_idents: &[Either<&Ident, syn::Index>], attr: StructAttribute, -) -> impl Iterator> + 'a { +) -> syn::Result { let expand_one = |tys: Option>, r: bool, m: bool| { let Some(tys) = tys else { return Either::Left(iter::empty()); @@ -119,6 +161,7 @@ fn expand_attr<'a>( ] .into_iter() .flatten() + .collect() } /// Representation of an [`Into`] derive macro struct container attribute. @@ -332,43 +375,6 @@ fn merge_tys( }; } -/// `#[into(skip)]` field attribute. -struct SkipFieldAttribute; - -impl SkipFieldAttribute { - /// Parses a [`SkipFieldAttribute`] from the provided [`syn::Attribute`]s. - fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result> { - Ok(attrs - .as_ref() - .iter() - .filter(|attr| attr.path().is_ident("into")) - .try_fold(None, |mut attrs, attr| { - let field_attr = attr.parse_args::()?; - if let Some((path, _)) = attrs.replace((attr.path(), field_attr)) { - Err(syn::Error::new( - path.span(), - "only single `#[into(...)]` attribute is allowed here", - )) - } else { - Ok(attrs) - } - })? - .map(|(_, attr)| attr)) - } -} - -impl Parse for SkipFieldAttribute { - fn parse(content: ParseStream) -> syn::Result { - match content.parse::()? { - p if p.is_ident("skip") | p.is_ident("ignore") => Ok(Self), - p => Err(syn::Error::new( - p.span(), - format!("expected `skip`, found: `{}`", p.into_token_stream()), - )), - } - } -} - /// [`Error`]ors for legacy syntax: `#[into(types(i32, "&str"))]`. fn check_legacy_syntax<'a, F>(tokens: ParseStream<'_>, fields: &'a F) -> syn::Result<()> where diff --git a/impl/src/utils.rs b/impl/src/utils.rs index b7277668..b2b393fa 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1369,6 +1369,7 @@ mod either { /// /// [`Left`]: Either::Left /// [`Right`]: Either::Right + #[derive(Clone)] pub(crate) enum Either { /// Left variant. Left(L), From 203663aa50de9077c349d1e53f36f68b4ca5bb89 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 11 Aug 2023 13:44:38 +0300 Subject: [PATCH 07/44] Fix skip parsing --- impl/src/into.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 40be0f16..a5aab1a6 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -310,6 +310,7 @@ impl StructAttribute { } } +#[derive(Debug)] enum FieldAttribute { Skip, Args(StructAttribute), @@ -353,7 +354,10 @@ impl FieldAttribute { fn parse(content: ParseStream, field: &Field) -> syn::Result { let ahead = content.fork(); match ahead.parse::() { - Ok(p) if p.is_ident("skip") | p.is_ident("ignore") => Ok(Self::Skip), + Ok(p) if p.is_ident("skip") | p.is_ident("ignore") => { + content.advance_to(&ahead); + Ok(Self::Skip) + } _ => { let fields = std::slice::from_ref(field); StructAttribute::parse(content, fields).map(Self::Args) From 9821fbfbafc49f364e567e0ce8c921651918f21a Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 11 Aug 2023 13:54:14 +0300 Subject: [PATCH 08/44] Separate IntoArgs from StructAttribute --- impl/src/into.rs | 63 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index a5aab1a6..a1b036f3 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -63,20 +63,21 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result, Vec<_>>(); - let struct_expand = expand_attr( + let struct_expand = expand_args( &input.generics, &input.ident, &fields_tys, &fields_idents, - struct_attr, + struct_attr.args, )?; expands.extend(struct_expand); @@ -100,12 +101,12 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result], - attr: StructAttribute, + args: IntoArgs, ) -> syn::Result { let expand_one = |tys: Option>, r: bool, m: bool| { let Some(tys) = tys else { @@ -155,9 +156,9 @@ fn expand_attr( ) }; [ - expand_one(attr.owned, false, false), - expand_one(attr.r#ref, true, false), - expand_one(attr.ref_mut, true, true), + expand_one(args.owned, false, false), + expand_one(args.r#ref, true, false), + expand_one(args.ref_mut, true, true), ] .into_iter() .flatten() @@ -173,6 +174,11 @@ fn expand_attr( /// ``` #[derive(Debug, Default)] struct StructAttribute { + args: IntoArgs, +} + +#[derive(Debug, Default)] +struct IntoArgs { /// [`Type`]s wrapped into `owned(...)` or simply `#[into(...)]`. owned: Option>, @@ -196,9 +202,9 @@ impl StructAttribute { .try_fold(None, |mut attrs, attr| { let field_attr = Self::parse_attr(attr, fields)?; let out = attrs.get_or_insert_with(Self::default); - merge_tys(&mut out.owned, field_attr.owned); - merge_tys(&mut out.r#ref, field_attr.r#ref); - merge_tys(&mut out.ref_mut, field_attr.ref_mut); + merge_tys(&mut out.args.owned, field_attr.args.owned); + merge_tys(&mut out.args.r#ref, field_attr.args.r#ref); + merge_tys(&mut out.args.ref_mut, field_attr.args.ref_mut); Ok(attrs) }) @@ -207,15 +213,19 @@ impl StructAttribute { /// Parses a single [`StructAttribute`] fn parse_attr(attr: &syn::Attribute, fields: &syn::Fields) -> syn::Result { if matches!(attr.meta, syn::Meta::Path(_)) { - Ok(Self::all_owned()) + Ok(Self { + args: IntoArgs::all_owned(), + }) } else { attr.parse_args_with(|content: ParseStream<'_>| { - Self::parse(content, fields) + IntoArgs::parse(content, fields).map(|args| Self { args }) }) } } +} - /// Parses a single [`StructAttribute`]'s arguments +impl IntoArgs { + /// Parses a set of [`IntoArgs`] fn parse<'a, F>(content: ParseStream<'_>, fields: &'a F) -> syn::Result where F: FieldsExt + ?Sized, @@ -310,13 +320,22 @@ impl StructAttribute { } } +/// Representation of an [`Into`] derive macro field attribute. +/// +/// ```rust,ignore +/// #[into] +/// #[into(skip)] +/// #[into()] +/// #[into(owned(), ref(), ref_mut())] +/// ``` #[derive(Debug)] enum FieldAttribute { Skip, - Args(StructAttribute), + Args(IntoArgs), } impl FieldAttribute { + /// Parses a [`FieldAttribute`] from the provided [`syn::Attribute`]s. fn parse_attrs( attrs: impl AsRef<[syn::Attribute]>, field: &Field, @@ -343,14 +362,16 @@ impl FieldAttribute { }) } + /// Parses a single [`FieldAttribute`] fn parse_attr(attr: &syn::Attribute, field: &Field) -> syn::Result { if matches!(attr.meta, syn::Meta::Path(_)) { - Ok(Self::Args(StructAttribute::all_owned())) + Ok(Self::Args(IntoArgs::all_owned())) } else { attr.parse_args_with(|content: ParseStream| Self::parse(content, field)) } } + /// Parses a single [`FieldAttribute`]'s args fn parse(content: ParseStream, field: &Field) -> syn::Result { let ahead = content.fork(); match ahead.parse::() { @@ -360,7 +381,7 @@ impl FieldAttribute { } _ => { let fields = std::slice::from_ref(field); - StructAttribute::parse(content, fields).map(Self::Args) + IntoArgs::parse(content, fields).map(Self::Args) } } } From dcf82c3021d0905efc79285037353f234bc6f2b8 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 11 Aug 2023 13:58:14 +0300 Subject: [PATCH 09/44] Add comment explaining all-fields logic --- impl/src/into.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/impl/src/into.rs b/impl/src/into.rs index a1b036f3..4e77fa1a 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -60,6 +60,9 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result, Vec<_>>(); + // Expand the version with all non-skipped fields if either + // there's an explicit struct attribute + // or there are no conversions into specific fields let struct_attr = struct_attr.or_else(|| { args.iter() .all(|arg| arg.is_none()) From 47200dc6a1a0b4cfef97cc1e2aaf00422f3da049 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 11 Aug 2023 16:02:22 +0300 Subject: [PATCH 10/44] Add Into field attribute tests --- tests/into.rs | 363 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) diff --git a/tests/into.rs b/tests/into.rs index 95799d2f..327006ae 100644 --- a/tests/into.rs +++ b/tests/into.rs @@ -1043,3 +1043,366 @@ mod multi_field { } } } + +mod field_attr { + use super::*; + + mod fields_only { + use super::*; + + #[derive(Debug, Into, Clone, Copy)] + struct Tuple(#[into] i32, f64, #[into] f64); + + impl From for (i32, f64, f64) { + fn from(value: Tuple) -> Self { + (value.0, value.1, value.2) + } + } + + impl From for (i32, f64) { + fn from(value: Tuple) -> Self { + (value.0, value.2) + } + } + + #[test] + fn tuple() { + let foo = Tuple(1, 2.0, 3.0); + + assert_eq!(1, foo.into()); + assert_eq!(3.0, foo.into()); + } + + #[derive(Debug, Into, Clone, Copy)] + struct Struct { + #[into] + a: i32, + b: f64, + #[into] + c: f64, + } + + impl From for (i32, f64, f64) { + fn from(value: Struct) -> Self { + (value.a, value.b, value.c) + } + } + + impl From for (i32, f64) { + fn from(value: Struct) -> Self { + (value.a, value.c) + } + } + + #[test] + fn named() { + let foo = Struct { + a: 1, + b: 2.0, + c: 3.0, + }; + + assert_eq!(1, foo.into()); + assert_eq!(3.0, foo.into()); + } + + mod types { + use super::*; + + #[derive(Debug, Into, Clone)] + struct Tuple( + #[into(Box, Cow<'_, str>)] String, + f64, + #[into(f32, f64)] f32, + ); + + impl From for String { + fn from(value: Tuple) -> Self { + value.0 + } + } + + impl From for (String, f64, f32) { + fn from(value: Tuple) -> Self { + (value.0, value.1, value.2) + } + } + + #[test] + fn tuple() { + let foo = Tuple("1".to_owned(), 2.0, 3.0); + + assert_eq!(Box::::from("1".to_owned()), foo.clone().into()); + assert_eq!(Cow::Borrowed("1"), Cow::::from(foo.clone())); + assert_eq!(3.0f32, foo.clone().into()); + assert_eq!(3.0f64, foo.into()); + } + + #[derive(Debug, Into, Clone)] + struct Struct { + #[into(Box, Cow<'_, str>)] + a: String, + b: f64, + #[into(f32, f64)] + c: f32, + } + + impl From for String { + fn from(value: Struct) -> Self { + value.a + } + } + + impl From for (String, f64, f32) { + fn from(value: Struct) -> Self { + (value.a, value.b, value.c) + } + } + + impl From for (Box, f32) { + fn from(value: Struct) -> Self { + (value.a.into(), value.c) + } + } + + #[test] + fn named() { + let foo = Struct { + a: "1".to_owned(), + b: 2.0, + c: 3.0, + }; + + assert_eq!(Box::::from("1".to_owned()), foo.clone().into()); + assert_eq!(Cow::Borrowed("1"), Cow::::from(foo.clone())); + assert_eq!(3.0f32, foo.clone().into()); + assert_eq!(3.0f64, foo.into()); + } + + mod ref_ { + use super::*; + + #[derive(Debug, Into)] + struct Tuple(#[into(ref)] String, f64, #[into(ref)] f64); + + impl<'a> From<&'a Tuple> for (&'a String, &'a f64, &'a f64) { + fn from(value: &'a Tuple) -> Self { + (&value.0, &value.1, &value.2) + } + } + + #[test] + fn tuple() { + let foo = Tuple("1".to_owned(), 2.0, 3.0); + + assert_eq!(&"1".to_owned(), <&String>::from(&foo)); + assert_eq!(&3.0, <&f64>::from(&foo)); + } + + #[derive(Debug, Into)] + struct Struct { + #[into(ref)] + a: String, + b: f64, + #[into(ref)] + c: f64, + } + + impl<'a> From<&'a Struct> for (&'a String, &'a f64, &'a f64) { + fn from(value: &'a Struct) -> Self { + (&value.a, &value.b, &value.c) + } + } + + impl<'a> From<&'a Struct> for (&'a String, &'a f64) { + fn from(value: &'a Struct) -> Self { + (&value.a, &value.c) + } + } + + #[test] + fn named() { + let foo = Struct { + a: "1".to_owned(), + b: 2.0, + c: 3.0, + }; + + assert_eq!(&"1".to_owned(), <&String>::from(&foo)); + assert_eq!(&3.0, <&f64>::from(&foo)); + } + + mod types { + use super::*; + + #[derive(Debug, Into)] + struct Tuple( + #[into(ref(Transmuted))] Wrapped, + #[into(ref(Wrapped))] Wrapped, + ); + + #[test] + fn tuple() { + let foo = Tuple(Wrapped(1), Wrapped(2)); + + assert_eq!(&Transmuted(1), <&Transmuted>::from(&foo)); + assert_eq!(&Wrapped(2), <&Wrapped>::from(&foo)); + } + + #[derive(Debug, Into)] + struct Struct { + #[into(ref(Transmuted))] + a: Wrapped, + #[into(ref(Wrapped))] + b: Wrapped, + } + + #[test] + fn named() { + let foo = Struct { + a: Wrapped(1), + b: Wrapped(2), + }; + + assert_eq!(&Transmuted(1), <&Transmuted>::from(&foo)); + assert_eq!(&Wrapped(2), <&Wrapped>::from(&foo)); + } + } + + mod ref_mut { + use super::*; + + #[derive(Debug, Into)] + struct Tuple(#[into(ref_mut)] i32, f64, #[into(ref_mut)] f64); + + #[test] + fn tuple() { + let mut foo = Tuple(1, 2.0, 3.0); + + assert_eq!(&mut 1, <&mut i32>::from(&mut foo)); + assert_eq!(&mut 3.0, <&mut f64>::from(&mut foo)); + } + + #[derive(Debug, Into)] + struct Struct { + #[into(ref_mut)] + a: i32, + b: f64, + #[into(ref_mut)] + c: f64, + } + + #[test] + fn named() { + let mut foo = Struct { + a: 1, + b: 2.0, + c: 3.0, + }; + + assert_eq!(&mut 1, <&mut i32>::from(&mut foo)); + assert_eq!(&mut 3.0, <&mut f64>::from(&mut foo)); + } + + mod types { + use super::*; + + #[derive(Debug, Into)] + struct Tuple( + #[into(ref_mut(Transmuted))] Wrapped, + #[into(ref_mut(Wrapped))] Wrapped, + ); + + #[test] + fn tuple() { + let mut foo = Tuple(Wrapped(1), Wrapped(2)); + + assert_eq!( + &mut Transmuted(1), + <&mut Transmuted>::from(&mut foo) + ); + assert_eq!( + &mut Wrapped(2), + <&mut Wrapped>::from(&mut foo) + ); + } + + #[derive(Debug, Into)] + struct Struct { + #[into(ref_mut(Transmuted))] + a: Wrapped, + #[into(ref_mut(Wrapped))] + b: Wrapped, + } + + #[test] + fn named() { + let mut foo = Struct { + a: Wrapped(1), + b: Wrapped(2), + }; + + assert_eq!( + &mut Transmuted(1), + <&mut Transmuted>::from(&mut foo) + ); + assert_eq!( + &mut Wrapped(2), + <&mut Wrapped>::from(&mut foo) + ); + } + } + } + } + } + } + + mod both { + use super::*; + + #[derive(Debug, Into)] + #[into(ref((Wrapped, Transmuted)))] + struct Tuple( + #[into(owned, ref(Transmuted))] Wrapped, + #[into(skip)] Wrapped, + #[into(ref_mut(Wrapped, Transmuted))] Wrapped, + ); + + #[test] + fn tuple() { + let mut foo = Tuple(Wrapped(1), Wrapped(2.0), Wrapped(3.0)); + + assert_eq!(&Transmuted(1), <&Transmuted>::from(&foo)); + assert_eq!(&mut Transmuted(3.0), <&mut Transmuted>::from(&mut foo)); + assert_eq!(&mut Wrapped(3.0), <&mut Wrapped>::from(&mut foo)); + assert_eq!((&Wrapped(1), &Transmuted(3.0)), (&foo).into()); + assert_eq!(Wrapped(1), foo.into()); + } + + #[derive(Debug, Into)] + #[into(ref((Wrapped, Transmuted)))] + struct Struct { + #[into(owned, ref(Transmuted))] + a: Wrapped, + #[into(skip)] + b: Wrapped, + #[into(ref_mut(Wrapped, Transmuted))] + c: Wrapped, + } + + #[test] + fn named() { + let mut foo = Struct { + a: Wrapped(1), + b: Wrapped(2.0), + c: Wrapped(3.0), + }; + + assert_eq!(&Transmuted(1), <&Transmuted>::from(&foo)); + assert_eq!(&mut Transmuted(3.0), <&mut Transmuted>::from(&mut foo)); + assert_eq!(&mut Wrapped(3.0), <&mut Wrapped>::from(&mut foo)); + assert_eq!((&Wrapped(1), &Transmuted(3.0)), (&foo).into()); + assert_eq!(Wrapped(1), foo.into()); + } + } +} From e38b899bb83ea3cb69421e814f0fee89851ae4fc Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 11 Aug 2023 16:18:21 +0300 Subject: [PATCH 11/44] Clean up naming --- impl/src/into.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 4e77fa1a..dcec226f 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -67,7 +67,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result Self { + Self { args } + } + /// Parses a [`StructAttribute`] from the provided [`syn::Attribute`]s. fn parse_attrs( attrs: impl AsRef<[syn::Attribute]>, @@ -203,11 +207,11 @@ impl StructAttribute { .iter() .filter(|attr| attr.path().is_ident("into")) .try_fold(None, |mut attrs, attr| { - let field_attr = Self::parse_attr(attr, fields)?; + let attr = Self::parse_attr(attr, fields)?; let out = attrs.get_or_insert_with(Self::default); - merge_tys(&mut out.args.owned, field_attr.args.owned); - merge_tys(&mut out.args.r#ref, field_attr.args.r#ref); - merge_tys(&mut out.args.ref_mut, field_attr.args.ref_mut); + merge_tys(&mut out.args.owned, attr.args.owned); + merge_tys(&mut out.args.r#ref, attr.args.r#ref); + merge_tys(&mut out.args.ref_mut, attr.args.ref_mut); Ok(attrs) }) @@ -216,12 +220,10 @@ impl StructAttribute { /// Parses a single [`StructAttribute`] fn parse_attr(attr: &syn::Attribute, fields: &syn::Fields) -> syn::Result { if matches!(attr.meta, syn::Meta::Path(_)) { - Ok(Self { - args: IntoArgs::all_owned(), - }) + Ok(Self::new(IntoArgs::all_owned())) } else { attr.parse_args_with(|content: ParseStream<'_>| { - IntoArgs::parse(content, fields).map(|args| Self { args }) + IntoArgs::parse(content, fields).map(Self::new) }) } } From 358ff3216bb7f74a94ba6a9890d5baa5fa8e8b0d Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 11 Aug 2023 16:21:14 +0300 Subject: [PATCH 12/44] Add compile_fail tests for new errors --- tests/compile_fail/into/mixed_field_skip_types.rs | 7 +++++++ tests/compile_fail/into/mixed_field_skip_types.stderr | 5 +++++ tests/compile_fail/into/multiple_skip.rs | 8 ++++++++ tests/compile_fail/into/multiple_skip.stderr | 5 +++++ 4 files changed, 25 insertions(+) create mode 100644 tests/compile_fail/into/mixed_field_skip_types.rs create mode 100644 tests/compile_fail/into/mixed_field_skip_types.stderr create mode 100644 tests/compile_fail/into/multiple_skip.rs create mode 100644 tests/compile_fail/into/multiple_skip.stderr diff --git a/tests/compile_fail/into/mixed_field_skip_types.rs b/tests/compile_fail/into/mixed_field_skip_types.rs new file mode 100644 index 00000000..5214e57a --- /dev/null +++ b/tests/compile_fail/into/mixed_field_skip_types.rs @@ -0,0 +1,7 @@ +#[derive(derive_more::Into)] +struct Foo { + #[into(skip, i32)] + a: i32, +} + +fn main() {} diff --git a/tests/compile_fail/into/mixed_field_skip_types.stderr b/tests/compile_fail/into/mixed_field_skip_types.stderr new file mode 100644 index 00000000..a15b2982 --- /dev/null +++ b/tests/compile_fail/into/mixed_field_skip_types.stderr @@ -0,0 +1,5 @@ +error: unexpected token + --> tests/compile_fail/into/mixed_field_skip_types.rs:3:16 + | +3 | #[into(skip, i32)] + | ^ diff --git a/tests/compile_fail/into/multiple_skip.rs b/tests/compile_fail/into/multiple_skip.rs new file mode 100644 index 00000000..e8f3b9fc --- /dev/null +++ b/tests/compile_fail/into/multiple_skip.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::Into)] +struct Foo { + #[into(skip)] + #[into(skip)] + a: i32, +} + +fn main() {} diff --git a/tests/compile_fail/into/multiple_skip.stderr b/tests/compile_fail/into/multiple_skip.stderr new file mode 100644 index 00000000..401da20a --- /dev/null +++ b/tests/compile_fail/into/multiple_skip.stderr @@ -0,0 +1,5 @@ +error: only single `#[into(...)]` attribute is allowed here + --> tests/compile_fail/into/multiple_skip.rs:4:7 + | +4 | #[into(skip)] + | ^^^^ From 167f9ed2ec93dcd6fb52074fb48c5c419f0b8f41 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 11:07:05 +0300 Subject: [PATCH 13/44] Add comment explaining manual impls in tests --- tests/into.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/into.rs b/tests/into.rs index 327006ae..adb5dcff 100644 --- a/tests/into.rs +++ b/tests/into.rs @@ -1050,6 +1050,8 @@ mod field_attr { mod fields_only { use super::*; + // The manual impls are there to test that no extra ones were generated + #[derive(Debug, Into, Clone, Copy)] struct Tuple(#[into] i32, f64, #[into] f64); From 4f8e0c5f447f75a6487e1ec1e368a8d3d4da7537 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 11:48:55 +0300 Subject: [PATCH 14/44] Document Into field attribute usage --- impl/doc/into.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/impl/doc/into.md b/impl/doc/into.md index 0ca676d9..4523d6f0 100644 --- a/impl/doc/into.md +++ b/impl/doc/into.md @@ -108,6 +108,65 @@ assert_eq!(5_i128, Mass::::new(5).into()); # } ``` +The `#[into(...)]` attribute can also be applied to specific fields + +```rust +# use derive_more::Into; + +#[derive(Into)] +struct Data { + id: i32, + #[into] + raw: f64 +} + +assert_eq!(42.0, Data { id: 1, raw: 42.0 }.into()); +``` + +In this case no conversion into a tuple of all fields is generated unless +an explicit struct attribute is present + +```rust +# use derive_more::Into; + +#[derive(Into)] +#[into] +struct Data { + id: i32, + #[into] + raw: f64 +} + +assert_eq!(42.0, Data { id: 1, raw: 42.0 }.into()); +assert_eq!((1, 42.0), Data { id: 1, raw: 42.0 }.into()); +``` + +The same `#[into()]` syntax can be used in field attributes as well + +```rust +# use std::marker::PhantomData; +# use derive_more::Into; +# struct Whatever; + +#[derive(Into, Clone)] +#[into(owned, ref((u8, str)), ref_mut)] +struct Foo { + #[into(owned(u64), ref)] + a: u8, + b: String, + #[into(skip)] + _c: PhantomData, +} + +let mut foo = Foo { a: 1, b: "string".to_owned(), _c: PhantomData }; + +assert_eq!((1_u8, "string".to_owned()), foo.clone().into()); +assert_eq!((&1_u8, "string"), <(&u8, &str)>::from(&foo)); +assert_eq!((&mut 1_u8, &mut "string".to_owned()), <(&mut u8, &mut String)>::from(&mut foo)); +assert_eq!(1_u64, foo.clone().into()); +assert_eq!(&1_u8, <&u8>::from(&foo)); +``` + ## Enums Deriving `Into` for enums is not supported as it would not always be successful, From b50602d5aaf48b7287cdbcdf9c81ad24789741bb Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 12:06:14 +0300 Subject: [PATCH 15/44] Clean up fields data processing with unzip3 utility function --- impl/src/into.rs | 24 ++++++++++++------------ impl/src/utils.rs | 21 +++++++++++++++++++++ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index dcec226f..546f9a5e 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -14,7 +14,7 @@ use syn::{ use crate::{ parsing::Type, - utils::{polyfill, Either, FieldsExt}, + utils::{polyfill, unzip3, Either, FieldsExt}, }; /// Expands an [`Into`] derive macro. @@ -33,7 +33,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result>>()? - .into_iter() - .unzip::<_, _, Vec<_>, Vec<_>>(); + .collect::>>()?; + + let (fields_tys, fields_idents, fields_args) = + unzip3::<_, _, _, Vec<_>, Vec<_>, Vec<_>, _>(fields_data); // Expand the version with all non-skipped fields if either // there's an explicit struct attribute // or there are no conversions into specific fields let struct_attr = struct_attr.or_else(|| { - args.iter() + fields_args + .iter() .all(|arg| arg.is_none()) .then(IntoArgs::all_owned) .map(StructAttribute::new) }); - let mut expands = fields + let mut expands = fields_tys .iter() - .zip(args) + .zip(&fields_idents) + .zip(fields_args) .filter_map(|((field_ty, ident), args)| { args.map(|args| { expand_args( @@ -87,9 +90,6 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result>()?; if let Some(struct_attr) = struct_attr { - let (fields_tys, fields_idents) = - fields.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); - let struct_expand = expand_args( &input.generics, &input.ident, diff --git a/impl/src/utils.rs b/impl/src/utils.rs index b2b393fa..35a3e982 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1540,3 +1540,24 @@ mod fields_ext { impl FieldsExt for T {} } + +#[cfg(feature = "into")] +pub fn unzip3(it: I) -> (FromA, FromB, FromC) +where + I: IntoIterator, + FromA: Default + Extend, + FromB: Default + Extend, + FromC: Default + Extend, +{ + use std::iter::once; + + it.into_iter().fold( + (Default::default(), Default::default(), Default::default()), + |(mut as_, mut bs, mut cs), (a, b, c)| { + as_.extend(once(a)); + bs.extend(once(b)); + cs.extend(once(c)); + (as_, bs, cs) + }, + ) +} From 8bc380f20174cd28623930019c01d1258b6ff3d3 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 14:05:44 +0300 Subject: [PATCH 16/44] Add support for skipping fields for which separate conversions exist --- impl/src/into.rs | 83 ++++++++++++++++++++++++++++++----------------- impl/src/utils.rs | 14 +++++--- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 546f9a5e..2f3d3041 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -14,7 +14,7 @@ use syn::{ use crate::{ parsing::Type, - utils::{polyfill, unzip3, Either, FieldsExt}, + utils::{polyfill, unzip4, Either, FieldsExt}, }; /// Expands an [`Into`] derive macro. @@ -37,29 +37,24 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result field_attr, - Err(err) => return Some(Err(err)), - }; + .map(|(i, f)| { + let field_attr = FieldAttribute::parse_attrs(&f.attrs, f)?; - let args = match field_attr { - Some(FieldAttribute::Skip) => return None, - Some(FieldAttribute::Args(args)) => Some(args), - None => None, - }; + let skip = field_attr.as_ref().map(|attr| attr.skip).unwrap_or(false); + + let args = field_attr.and_then(|attr| attr.args); let ident = f .ident .as_ref() .map_or_else(|| Either::Right(syn::Index::from(i)), Either::Left); - Some(Ok((&f.ty, ident, args))) + Ok((&f.ty, ident, skip, args)) }) .collect::>>()?; - let (fields_tys, fields_idents, fields_args) = - unzip3::<_, _, _, Vec<_>, Vec<_>, Vec<_>, _>(fields_data); + let (fields_tys, fields_idents, skips, fields_args) = + unzip4::<_, _, _, _, Vec<_>, Vec<_>, Vec<_>, Vec<_>, _>(fields_data); // Expand the version with all non-skipped fields if either // there's an explicit struct attribute @@ -67,7 +62,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result>()?; if let Some(struct_attr) = struct_attr { + let (fields_tys, fields_idents) = fields_tys + .iter() + .zip(fields_idents) + .zip(skips) + .filter_map(|(pair, skip)| (!skip).then_some(pair)) + .unzip::<_, _, Vec<_>, Vec<_>>(); + let struct_expand = expand_args( &input.generics, &input.ident, @@ -333,10 +335,10 @@ impl IntoArgs { /// #[into()] /// #[into(owned(), ref(), ref_mut())] /// ``` -#[derive(Debug)] -enum FieldAttribute { - Skip, - Args(IntoArgs), +#[derive(Debug, Default)] +struct FieldAttribute { + skip: bool, + args: Option, } impl FieldAttribute { @@ -349,28 +351,41 @@ impl FieldAttribute { .as_ref() .iter() .filter(|attr| attr.path().is_ident("into")) - .try_fold(None, |attrs, attr| { + .try_fold(None, |mut attrs, attr| { let field_attr = Self::parse_attr(attr, field)?; - match (attrs, field_attr) { - (Some(Self::Args(mut args)), Self::Args(more)) => { + let prev_attrs: &mut FieldAttribute = + attrs.get_or_insert_with(Default::default); + + match (prev_attrs.args.as_mut(), field_attr.args) { + (Some(args), Some(more)) => { merge_tys(&mut args.owned, more.owned); merge_tys(&mut args.r#ref, more.r#ref); merge_tys(&mut args.ref_mut, more.ref_mut); - Ok(Some(Self::Args(args))) } - (None, field_attr) => Ok(Some(field_attr)), - (Some(_), _) => Err(syn::Error::new( + (None, Some(args)) => prev_attrs.args = Some(args), + (_, None) => {} + }; + + if prev_attrs.skip && field_attr.skip { + return Err(syn::Error::new( attr.path().span(), - "only single `#[into(...)]` attribute is allowed here", - )), + "only a single `#[into(skip)] attribute is allowed`", + )); } + + prev_attrs.skip |= field_attr.skip; + + Ok(attrs) }) } /// Parses a single [`FieldAttribute`] fn parse_attr(attr: &syn::Attribute, field: &Field) -> syn::Result { if matches!(attr.meta, syn::Meta::Path(_)) { - Ok(Self::Args(IntoArgs::all_owned())) + Ok(Self { + skip: false, + args: Some(IntoArgs::all_owned()), + }) } else { attr.parse_args_with(|content: ParseStream| Self::parse(content, field)) } @@ -382,11 +397,19 @@ impl FieldAttribute { match ahead.parse::() { Ok(p) if p.is_ident("skip") | p.is_ident("ignore") => { content.advance_to(&ahead); - Ok(Self::Skip) + Ok(Self { + skip: true, + args: None, + }) } _ => { let fields = std::slice::from_ref(field); - IntoArgs::parse(content, fields).map(Self::Args) + let args = IntoArgs::parse(content, fields)?; + + Ok(Self { + skip: false, + args: Some(args), + }) } } } diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 753d5919..fe18bd1d 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1521,22 +1521,26 @@ mod fields_ext { } #[cfg(feature = "into")] -pub fn unzip3(it: I) -> (FromA, FromB, FromC) +pub fn unzip4( + it: I, +) -> (FromA, FromB, FromC, FromD) where - I: IntoIterator, + I: IntoIterator, FromA: Default + Extend, FromB: Default + Extend, FromC: Default + Extend, + FromD: Default + Extend, { use std::iter::once; it.into_iter().fold( - (Default::default(), Default::default(), Default::default()), - |(mut as_, mut bs, mut cs), (a, b, c)| { + Default::default(), + |(mut as_, mut bs, mut cs, mut ds), (a, b, c, d)| { as_.extend(once(a)); bs.extend(once(b)); cs.extend(once(c)); - (as_, bs, cs) + ds.extend(once(d)); + (as_, bs, cs, ds) }, ) } From 714e803061373a547a71298cd010ecb9d6534a36 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 14:07:21 +0300 Subject: [PATCH 17/44] Update compile_fail test to reflect changes --- tests/compile_fail/into/multiple_skip.stderr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/compile_fail/into/multiple_skip.stderr b/tests/compile_fail/into/multiple_skip.stderr index 401da20a..ec3efcd7 100644 --- a/tests/compile_fail/into/multiple_skip.stderr +++ b/tests/compile_fail/into/multiple_skip.stderr @@ -1,4 +1,4 @@ -error: only single `#[into(...)]` attribute is allowed here +error: only a single `#[into(skip)] attribute is allowed` --> tests/compile_fail/into/multiple_skip.rs:4:7 | 4 | #[into(skip)] From 06861989b0ed16a4f9038cfe938341e47d06cd29 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 14:09:41 +0300 Subject: [PATCH 18/44] Update test with multi-attribute case --- tests/into.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/into.rs b/tests/into.rs index adb5dcff..8e9b9eb0 100644 --- a/tests/into.rs +++ b/tests/into.rs @@ -1366,7 +1366,9 @@ mod field_attr { #[into(ref((Wrapped, Transmuted)))] struct Tuple( #[into(owned, ref(Transmuted))] Wrapped, - #[into(skip)] Wrapped, + #[into(skip)] + #[into(ref)] + Wrapped, #[into(ref_mut(Wrapped, Transmuted))] Wrapped, ); @@ -1378,6 +1380,7 @@ mod field_attr { assert_eq!(&mut Transmuted(3.0), <&mut Transmuted>::from(&mut foo)); assert_eq!(&mut Wrapped(3.0), <&mut Wrapped>::from(&mut foo)); assert_eq!((&Wrapped(1), &Transmuted(3.0)), (&foo).into()); + assert_eq!(&Wrapped(2.0), <&Wrapped>::from(&foo)); assert_eq!(Wrapped(1), foo.into()); } @@ -1387,6 +1390,7 @@ mod field_attr { #[into(owned, ref(Transmuted))] a: Wrapped, #[into(skip)] + #[into(ref)] b: Wrapped, #[into(ref_mut(Wrapped, Transmuted))] c: Wrapped, @@ -1404,6 +1408,7 @@ mod field_attr { assert_eq!(&mut Transmuted(3.0), <&mut Transmuted>::from(&mut foo)); assert_eq!(&mut Wrapped(3.0), <&mut Wrapped>::from(&mut foo)); assert_eq!((&Wrapped(1), &Transmuted(3.0)), (&foo).into()); + assert_eq!(&Wrapped(2.0), <&Wrapped>::from(&foo)); assert_eq!(Wrapped(1), foo.into()); } } From be14ca77ced968d570150e3cae8776c2b93ce21c Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 14:17:51 +0300 Subject: [PATCH 19/44] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51133030..ef6ba286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). and ignores field type itself. - The `Into` derive now uses `#[into()]` instead of `#[into(types())]` and ignores field type itself. +- The `Into` derive now generates separate impls for each field with the `#[into(...)]` + attribute applied to it. - Importing a derive macro now also import its corresponding trait. ### Added From ee920b192b91793ef81262b7a7f321b00cff7cf0 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 14:23:55 +0300 Subject: [PATCH 20/44] Document behavior of skip with other field attributes --- impl/doc/into.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/impl/doc/into.md b/impl/doc/into.md index 4523d6f0..1581235f 100644 --- a/impl/doc/into.md +++ b/impl/doc/into.md @@ -167,6 +167,27 @@ assert_eq!(1_u64, foo.clone().into()); assert_eq!(&1_u8, <&u8>::from(&foo)); ``` +Fields that have specific conversions into them can also be skipped + +```rust +# use derive_more::Into; + +#[derive(Into)] +#[into(ref((str, f64)))] +struct Foo { + #[into(ref)] + #[into(skip)] + a: u8, + b: String, + c: f64, +} + +let foo = Foo { a: 1, b: "string".to_owned(), c: 3.0 }; + +assert_eq!(("string", &3.0), (&foo).into()); +assert_eq!(&1_u8, <&u8>::from(&foo)); +``` + ## Enums Deriving `Into` for enums is not supported as it would not always be successful, From 091790f6e957edc6dfcc24c63384b14f05fa5b77 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 14 Aug 2023 14:37:03 +0300 Subject: [PATCH 21/44] Improve parameter naming in expand_args --- impl/src/into.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 2f3d3041..ed02dd36 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -107,8 +107,8 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result], args: IntoArgs, @@ -124,11 +124,11 @@ fn expand_args( let m = m.then(token::Mut::default); let gens = if let Some(lf) = lf.clone() { - let mut gens = generics.clone(); + let mut gens = input_generics.clone(); gens.params.push(syn::LifetimeParam::new(lf).into()); Cow::Owned(gens) } else { - Cow::Borrowed(generics) + Cow::Borrowed(input_generics) }; Either::Right( @@ -140,15 +140,15 @@ fn expand_args( .map(move |ty| { let tys = fields_tys.validate_type(&ty)?.collect::>(); let (impl_gens, _, where_clause) = gens.split_for_impl(); - let (_, ty_gens, _) = generics.split_for_impl(); + let (_, ty_gens, _) = input_generics.split_for_impl(); Ok(quote! { #[automatically_derived] - impl #impl_gens ::core::convert::From<#r #lf #m #ident #ty_gens> + impl #impl_gens ::core::convert::From<#r #lf #m #input_ident #ty_gens> for ( #( #r #lf #m #tys ),* ) #where_clause { #[inline] - fn from(value: #r #lf #m #ident #ty_gens) -> Self { + fn from(value: #r #lf #m #input_ident #ty_gens) -> Self { (#( <#r #m #tys as ::core::convert::From<_>>::from( #r #m value. #fields_idents From a410e7b743fff47aeefe05892c437fda93bd6338 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Wed, 16 Aug 2023 23:02:53 +0300 Subject: [PATCH 22/44] Document Into macro parsing helpers, use less unqualified syn uses --- impl/src/into.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index ed02dd36..dcb87fad 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -9,7 +9,7 @@ use syn::{ parse::{discouraged::Speculative as _, Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned as _, - token, Field, Ident, + token, }; use crate::{ @@ -106,11 +106,12 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result], + fields_idents: &[Either<&syn::Ident, syn::Index>], args: IntoArgs, ) -> syn::Result { let expand_one = |tys: Option>, r: bool, m: bool| { @@ -182,6 +183,11 @@ struct StructAttribute { args: IntoArgs, } +/// A set of type arguments for a set of fields +/// +/// For +/// [`None`] represents no conversions of the given type +/// An empty [`Punctuated`] represents a conversion into the field types #[derive(Debug, Default)] struct IntoArgs { /// [`Type`]s wrapped into `owned(...)` or simply `#[into(...)]`. @@ -236,7 +242,7 @@ impl IntoArgs { fn parse<'a, F>(content: ParseStream<'_>, fields: &'a F) -> syn::Result where F: FieldsExt + ?Sized, - &'a F: IntoIterator, + &'a F: IntoIterator, { check_legacy_syntax(content, fields)?; @@ -271,8 +277,8 @@ impl IntoArgs { while !content.is_empty() { let ahead = content.fork(); - let res = if ahead.peek(Ident::peek_any) { - ahead.call(Ident::parse_any).map(Into::into) + let res = if ahead.peek(syn::Ident::peek_any) { + ahead.call(syn::Ident::parse_any).map(Into::into) } else { ahead.parse::() }; @@ -345,7 +351,7 @@ impl FieldAttribute { /// Parses a [`FieldAttribute`] from the provided [`syn::Attribute`]s. fn parse_attrs( attrs: impl AsRef<[syn::Attribute]>, - field: &Field, + field: &syn::Field, ) -> syn::Result> { attrs .as_ref() @@ -380,7 +386,7 @@ impl FieldAttribute { } /// Parses a single [`FieldAttribute`] - fn parse_attr(attr: &syn::Attribute, field: &Field) -> syn::Result { + fn parse_attr(attr: &syn::Attribute, field: &syn::Field) -> syn::Result { if matches!(attr.meta, syn::Meta::Path(_)) { Ok(Self { skip: false, @@ -392,7 +398,7 @@ impl FieldAttribute { } /// Parses a single [`FieldAttribute`]'s args - fn parse(content: ParseStream, field: &Field) -> syn::Result { + fn parse(content: ParseStream, field: &syn::Field) -> syn::Result { let ahead = content.fork(); match ahead.parse::() { Ok(p) if p.is_ident("skip") | p.is_ident("ignore") => { @@ -432,7 +438,7 @@ fn merge_tys( fn check_legacy_syntax<'a, F>(tokens: ParseStream<'_>, fields: &'a F) -> syn::Result<()> where F: FieldsExt + ?Sized, - &'a F: IntoIterator, + &'a F: IntoIterator, { let span = tokens.span(); let tokens = tokens.fork(); From ec38941e55804293925d59ef00a92431310240ca Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Wed, 16 Aug 2023 23:16:48 +0300 Subject: [PATCH 23/44] Reorganize Into expand to not require unzip4 helper --- impl/src/into.rs | 22 ++++++++++------------ impl/src/utils.rs | 25 ------------------------- 2 files changed, 10 insertions(+), 37 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index dcb87fad..630dfbf1 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -14,7 +14,7 @@ use syn::{ use crate::{ parsing::Type, - utils::{polyfill, unzip4, Either, FieldsExt}, + utils::{polyfill, Either, FieldsExt}, }; /// Expands an [`Into`] derive macro. @@ -49,12 +49,11 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result>>()?; - let (fields_tys, fields_idents, skips, fields_args) = - unzip4::<_, _, _, _, Vec<_>, Vec<_>, Vec<_>, Vec<_>, _>(fields_data); + let (fields, fields_args) = fields_data.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); // Expand the version with all non-skipped fields if either // there's an explicit struct attribute @@ -67,11 +66,10 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result>()?; if let Some(struct_attr) = struct_attr { - let (fields_tys, fields_idents) = fields_tys - .iter() - .zip(fields_idents) - .zip(skips) - .filter_map(|(pair, skip)| (!skip).then_some(pair)) + let (fields_tys, fields_idents) = fields + .into_iter() + .filter_map(|(field_ty, field_ident, skip)| { + (!skip).then_some((field_ty, field_ident)) + }) .unzip::<_, _, Vec<_>, Vec<_>>(); let struct_expand = expand_args( diff --git a/impl/src/utils.rs b/impl/src/utils.rs index fe18bd1d..6eb7bd94 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1519,28 +1519,3 @@ mod fields_ext { impl FieldsExt for T {} } - -#[cfg(feature = "into")] -pub fn unzip4( - it: I, -) -> (FromA, FromB, FromC, FromD) -where - I: IntoIterator, - FromA: Default + Extend, - FromB: Default + Extend, - FromC: Default + Extend, - FromD: Default + Extend, -{ - use std::iter::once; - - it.into_iter().fold( - Default::default(), - |(mut as_, mut bs, mut cs, mut ds), (a, b, c, d)| { - as_.extend(once(a)); - bs.extend(once(b)); - cs.extend(once(c)); - ds.extend(once(d)); - (as_, bs, cs, ds) - }, - ) -} From 1697a6afbe452a0d9dbaf4e9277e6b7c4b065b9f Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Thu, 17 Aug 2023 12:26:05 +0300 Subject: [PATCH 24/44] [skip ci] Fix typo --- impl/src/into.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 630dfbf1..1c3801e7 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -183,7 +183,6 @@ struct StructAttribute { /// A set of type arguments for a set of fields /// -/// For /// [`None`] represents no conversions of the given type /// An empty [`Punctuated`] represents a conversion into the field types #[derive(Debug, Default)] From ab4570915698337b23025f536d89a88700cdf43e Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 21 Aug 2023 16:04:55 +0300 Subject: [PATCH 25/44] Refactor Into derive to use the Expansion struct pattern --- impl/src/into.rs | 212 ++++++++++++++++++++++++----------------------- 1 file changed, 108 insertions(+), 104 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 1c3801e7..c0620f19 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -44,129 +44,46 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result>>()?; let (fields, fields_args) = fields_data.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); - // Expand the version with all non-skipped fields if either - // there's an explicit struct attribute - // or there are no conversions into specific fields let struct_attr = struct_attr.or_else(|| { fields_args .iter() - .all(|args| args.is_none()) + .all(Option::is_none) .then(IntoArgs::all_owned) .map(StructAttribute::new) }); - let mut expands = fields + let mut expansions: Vec<_> = fields .iter() .zip(fields_args) - .filter_map(|((field_ty, ident, _), args)| { - args.map(|args| { - expand_args( - &input.generics, - &input.ident, - std::slice::from_ref(field_ty), - std::slice::from_ref(ident), - args, - ) + .filter_map(|(&(i, field, _), args)| { + args.map(|args| Expansion { + input_ident: &input.ident, + input_generics: &input.generics, + fields: vec![(i, field)], + args, }) }) - .collect::>()?; - - if let Some(struct_attr) = struct_attr { - let (fields_tys, fields_idents) = fields - .into_iter() - .filter_map(|(field_ty, field_ident, skip)| { - (!skip).then_some((field_ty, field_ident)) - }) - .unzip::<_, _, Vec<_>, Vec<_>>(); - - let struct_expand = expand_args( - &input.generics, - &input.ident, - &fields_tys, - &fields_idents, - struct_attr.args, - )?; + .collect(); - expands.extend(struct_expand); + if let Some(attr) = struct_attr { + expansions.push(Expansion { + input_ident: &input.ident, + input_generics: &input.generics, + fields: fields + .into_iter() + .filter_map(|(i, f, skip)| (!skip).then_some((i, f))) + .collect(), + args: attr.args, + }); } - Ok(expands) -} - -/// Expands [`From`] impls for a set of fields with the given `[IntoArgs]` -fn expand_args( - input_generics: &syn::Generics, - input_ident: &syn::Ident, - fields_tys: &[&syn::Type], - fields_idents: &[Either<&syn::Ident, syn::Index>], - args: IntoArgs, -) -> syn::Result { - let expand_one = |tys: Option>, r: bool, m: bool| { - let Some(tys) = tys else { - return Either::Left(iter::empty()); - }; - - let lf = - r.then(|| syn::Lifetime::new("'__derive_more_into", Span::call_site())); - let r = r.then(token::And::default); - let m = m.then(token::Mut::default); - - let gens = if let Some(lf) = lf.clone() { - let mut gens = input_generics.clone(); - gens.params.push(syn::LifetimeParam::new(lf).into()); - Cow::Owned(gens) - } else { - Cow::Borrowed(input_generics) - }; - - Either::Right( - if tys.is_empty() { - Either::Left(iter::once(Type::tuple(fields_tys.clone()))) - } else { - Either::Right(tys.into_iter()) - } - .map(move |ty| { - let tys = fields_tys.validate_type(&ty)?.collect::>(); - let (impl_gens, _, where_clause) = gens.split_for_impl(); - let (_, ty_gens, _) = input_generics.split_for_impl(); - - Ok(quote! { - #[automatically_derived] - impl #impl_gens ::core::convert::From<#r #lf #m #input_ident #ty_gens> - for ( #( #r #lf #m #tys ),* ) #where_clause - { - #[inline] - fn from(value: #r #lf #m #input_ident #ty_gens) -> Self { - (#( - <#r #m #tys as ::core::convert::From<_>>::from( - #r #m value. #fields_idents - ) - ),*) - } - } - }) - }), - ) - }; - [ - expand_one(args.owned, false, false), - expand_one(args.r#ref, true, false), - expand_one(args.ref_mut, true, true), - ] - .into_iter() - .flatten() - .collect() + expansions.into_iter().map(Expansion::expand).collect() } /// Representation of an [`Into`] derive macro struct container attribute. @@ -431,6 +348,93 @@ fn merge_tys( }; } +/// Expansion of a macro for generating [`From`] implementations +struct Expansion<'a> { + /// Struct [`Ident`] + input_ident: &'a syn::Ident, + /// Struct [`Generics`] + input_generics: &'a syn::Generics, + /// Fields to convert from, with their indices + fields: Vec<(usize, &'a syn::Field)>, + /// Arguments specifying conversions + args: IntoArgs, +} + +impl<'a> Expansion<'a> { + fn expand(self) -> syn::Result { + let Self { + input_ident, + input_generics, + fields, + args, + } = self; + + let fields_idents: Vec<_> = fields + .iter() + .map(|(i, f)| { + f.ident + .as_ref() + .map_or_else(|| Either::Left(syn::Index::from(*i)), Either::Right) + }) + .collect(); + let fields_tys: Vec<_> = fields.iter().map(|(_, f)| &f.ty).collect(); + let fields_tuple = Type::tuple(fields_tys.clone()); + + [ + (&args.owned, false, false), + (&args.r#ref, true, false), + (&args.ref_mut, true, true), + ] + .into_iter() + .filter_map(|(out_tys, r, m)| { + out_tys.as_ref().map(|out_tys| { + let lf = r.then(|| { + syn::Lifetime::new("'__derive_more_into", Span::call_site()) + }); + let r = r.then(token::And::default); + let m = m.then(token::Mut::default); + + let gens = if let Some(lf) = lf.clone() { + let mut gens = input_generics.clone(); + gens.params.push(syn::LifetimeParam::new(lf).into()); + Cow::Owned(gens) + } else { + Cow::Borrowed(input_generics) + }; + + let (impl_gens, _, where_clause) = gens.split_for_impl(); + let (_, ty_gens, _) = input_generics.split_for_impl(); + + if out_tys.is_empty() { + Either::Left(iter::once(&fields_tuple)) + } else { + Either::Right(out_tys.iter()) + }.map(|out_ty| { + let tys: Vec<_> = fields_tys.validate_type(out_ty)?.collect(); + + Ok(quote! { + #[automatically_derived] + impl #impl_gens ::core::convert::From<#r #lf #m #input_ident #ty_gens> + for ( #( #r #lf #m #tys ),* ) #where_clause + { + #[inline] + fn from(value: #r #lf #m #input_ident #ty_gens) -> Self { + (#( + <#r #m #tys as ::core::convert::From<_>>::from( + #r #m value. #fields_idents + ) + ),*) + } + } + }) + }) + .collect::>() + }) + }) + .collect() + } +} + /// [`Error`]ors for legacy syntax: `#[into(types(i32, "&str"))]`. fn check_legacy_syntax<'a, F>(tokens: ParseStream<'_>, fields: &'a F) -> syn::Result<()> where From 6945bc0ef82fcd59345f62fe835120dded2fa9d7 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 25 Aug 2023 10:06:53 +0300 Subject: [PATCH 26/44] Fix comments/errors to reflect both skip and ignore are valid arguments --- impl/src/into.rs | 4 ++-- tests/compile_fail/into/multiple_skip.stderr | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index deacfbbf..636b3fbc 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -254,7 +254,7 @@ impl IntoArgs { /// /// ```rust,ignore /// #[into] -/// #[into(skip)] +/// #[into(skip)] #[into(ignore)] /// #[into()] /// #[into(owned(), ref(), ref_mut())] /// ``` @@ -293,7 +293,7 @@ impl FieldAttribute { if prev_attrs.skip.replace(skip).is_some() { return Err(syn::Error::new( attr.path().span(), - "only a single `#[into(skip)] attribute is allowed`", + "only a single `#[into(skip)]`/`#[into(ignore)]` attribute is allowed`", )); } } diff --git a/tests/compile_fail/into/multiple_skip.stderr b/tests/compile_fail/into/multiple_skip.stderr index ec3efcd7..c8a76eb0 100644 --- a/tests/compile_fail/into/multiple_skip.stderr +++ b/tests/compile_fail/into/multiple_skip.stderr @@ -1,4 +1,4 @@ -error: only a single `#[into(skip)] attribute is allowed` +error: only a single `#[into(skip)]`/`#[into(ignore)]` attribute is allowed` --> tests/compile_fail/into/multiple_skip.rs:4:7 | 4 | #[into(skip)] From 7f3a6a4f67900abcc14b4e3b7e4efe2ead9a281c Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 25 Aug 2023 10:14:39 +0300 Subject: [PATCH 27/44] Remove unused parser --- impl/src/utils.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 6ee983df..17cdaae8 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1387,8 +1387,6 @@ pub(crate) mod skip { spanned::Spanned as _, }; - use super::Spanning; - /// Representation of a `skip`/`ignore` attribute. /// /// ```rust,ignore @@ -1416,33 +1414,6 @@ pub(crate) mod skip { pub(crate) const fn name(&self) -> &'static str { self.0 } - - /// Parses an [`Attribute`] from the provided [`syn::Attribute`]s, preserving its [`Span`]. - /// - /// [`Span`]: proc_macro2::Span - pub(crate) fn parse_attrs( - attrs: impl AsRef<[syn::Attribute]>, - attr_ident: &syn::Ident, - ) -> syn::Result>> { - attrs - .as_ref() - .iter() - .filter(|attr| attr.path().is_ident(attr_ident)) - .try_fold(None, |mut attrs, attr| { - let parsed = Spanning::new(attr.parse_args()?, attr.span()); - if attrs.replace(parsed).is_some() { - Err(syn::Error::new( - attr.span(), - format!( - "only single `#[{attr_ident}(skip)]`/`#[{attr_ident}(ignore)]` \ - attribute is allowed here", - ), - )) - } else { - Ok(attrs) - } - }) - } } } From fb4c646842b5389937a44c06ae59a81f4f068682 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Fri, 25 Aug 2023 10:14:50 +0300 Subject: [PATCH 28/44] Use try_fold in parsing utils --- impl/src/parsing.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/impl/src/parsing.rs b/impl/src/parsing.rs index 42b66376..6e038cf4 100644 --- a/impl/src/parsing.rs +++ b/impl/src/parsing.rs @@ -243,15 +243,15 @@ pub fn seq( mut parsers: [&mut dyn FnMut(Cursor<'_>) -> ParsingResult<'_>; N], ) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> + '_ { move |c| { - parsers - .iter_mut() - .fold(Some((TokenStream::new(), c)), |out, parser| { - let (mut out, mut c) = out?; + parsers.iter_mut().try_fold( + (TokenStream::new(), c), + |(mut out, mut c), parser| { let (stream, cursor) = parser(c)?; out.extend(stream); c = cursor; Some((out, c)) - }) + }, + ) } } From a358d0bb946c4d424198c4cb418cc05336e7e5cb Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 4 Sep 2023 17:17:39 +0300 Subject: [PATCH 29/44] Remove extra newline --- impl/src/utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 17cdaae8..3549a18e 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1433,7 +1433,6 @@ mod either { /// /// [`Left`]: Either::Left /// [`Right`]: Either::Right - #[derive(Clone, Copy, Debug)] pub(crate) enum Either { /// Left variant. From e10f7f41776f623c7504d704fdea7dd95357e830 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 13:34:27 +0300 Subject: [PATCH 30/44] Add needed parsing utils --- impl/src/utils.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 1cac865b..6d8812a3 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1626,6 +1626,64 @@ pub(crate) mod attr { } } + pub(crate) struct Alt(pub(crate) Option); + + impl Parse for Alt { + fn parse(input: ParseStream) -> syn::Result { + Ok(Alt(input.parse().ok())) + } + } + + impl ParseMultiple for Alt { + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + Ok(match (prev.item.0, new.item.0) { + (Some(p), Some(n)) => T::merge_attrs( + Spanning::new(p, prev.span), + Spanning::new(n, new.span), + name, + )? + .map(|value| Alt(Some(value))), + (Some(p), None) => Spanning::new(Alt(Some(p)), prev.span), + (None, Some(n)) => Spanning::new(Alt(Some(n)), new.span), + (None, None) => Spanning::new( + Alt(None), + prev.span.join(new.span).unwrap_or(prev.span), + ), + }) + } + } + + pub(crate) struct Pair { + pub(crate) left: L, + pub(crate) right: R, + } + + impl Pair { + pub(crate) fn new(left: L, right: R) -> Self { + Pair { left, right } + } + } + + impl Parse for Pair { + fn parse(input: ParseStream) -> syn::Result { + Ok(if let Ok(left) = input.parse() { + Pair { + left, + right: R::default(), + } + } else { + Pair { + left: L::default(), + right: input.parse()?, + } + }) + } + } + #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] mod empty { use syn::{ From a0514ebfd9502395d31b9f4b347c398c81b3a698 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 13:50:37 +0300 Subject: [PATCH 31/44] Finish attribute parsing --- impl/src/into.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++--- impl/src/utils.rs | 46 +++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 9eb9bed7..357c2262 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -127,9 +127,20 @@ impl StructAttribute { } } +impl From> for StructAttribute { + fn from(value: Either) -> Self { + Self::new(match value { + Either::Left(_) => IntoArgs::all_owned(), + Either::Right(args) => args, + }) + } +} + impl Parse for StructAttribute { fn parse(input: ParseStream) -> syn::Result { - todo!() + input + .parse::>() + .map(Self::from) } } @@ -149,13 +160,63 @@ struct FieldAttribute { args: Option, } +impl From>> for FieldAttribute { + fn from(value: Either>) -> Self { + match value { + Either::Left(skip) => Self { + skip: Some(skip), + args: None, + }, + Either::Right(args) => Self { + skip: None, + args: Some(match args { + Either::Left(_) => IntoArgs::all_owned(), + Either::Right(args) => args, + }), + }, + } + } +} + impl Parse for FieldAttribute { fn parse(input: ParseStream) -> syn::Result { - todo!() + input + .parse::>>() + .map(Self::from) + } +} + +impl From for attr::Pair, attr::Alt> { + fn from(value: FieldAttribute) -> Self { + attr::Pair::new(attr::Alt::new(value.skip), attr::Alt::new(value.args)) + } +} + +impl From, attr::Alt>> for FieldAttribute { + fn from(value: attr::Pair, attr::Alt>) -> Self { + Self { + skip: value.left.into_inner(), + args: value.right.into_inner(), + } } } -impl attr::ParseMultiple for FieldAttribute {} +impl attr::ParseMultiple for FieldAttribute { + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + Ok( + , attr::Alt>>::merge_attrs( + prev.map(Self::into), + new.map(Self::into), + name, + )? + .map(Self::from), + ) + } +} /// A set of type arguments for a set of fields /// diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 6d8812a3..7cd3daf2 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1626,7 +1626,23 @@ pub(crate) mod attr { } } - pub(crate) struct Alt(pub(crate) Option); + pub(crate) struct Alt(Option); + + impl Alt { + pub(crate) fn new(value: Option) -> Self { + Self(value) + } + + pub(crate) fn into_inner(self) -> Option { + self.0 + } + } + + impl Default for Alt { + fn default() -> Self { + Self(None) + } + } impl Parse for Alt { fn parse(input: ParseStream) -> syn::Result { @@ -1684,6 +1700,34 @@ pub(crate) mod attr { } } + impl ParseMultiple + for Pair + { + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + let left = L::merge_attrs( + Spanning::new(prev.item.left, prev.span), + Spanning::new(new.item.left, new.span), + name, + )? + .into_inner(); + + let right = R::merge_attrs( + Spanning::new(prev.item.right, prev.span), + Spanning::new(new.item.right, new.span), + name, + )? + .into_inner(); + + let span = prev.span.join(new.span).unwrap_or(prev.span); + + Ok(Spanning::new(Pair { left, right }, span)) + } + } + #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] mod empty { use syn::{ From 74baa84c15b7be6a62c2902052d3853ea579fc20 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 13:53:25 +0300 Subject: [PATCH 32/44] Fix conditional compilation --- impl/src/utils.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 7cd3daf2..a955cc46 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1497,6 +1497,13 @@ pub(crate) mod attr { use super::{Either, Spanning}; + #[cfg(any( + feature = "as_ref", + feature = "into", + feature = "from", + feature = "try_from" + ))] + pub(crate) use self::empty::Empty; #[cfg(any( feature = "as_ref", feature = "debug", @@ -1505,12 +1512,12 @@ pub(crate) mod attr { feature = "into", ))] pub(crate) use self::skip::Skip; + #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] + pub(crate) use self::types::Types; #[cfg(any(feature = "as_ref", feature = "from"))] pub(crate) use self::{ conversion::Conversion, field_conversion::FieldConversion, forward::Forward, }; - #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] - pub(crate) use self::{empty::Empty, types::Types}; #[cfg(feature = "try_from")] pub(crate) use self::{repr_conversion::ReprConversion, repr_int::ReprInt}; @@ -1728,7 +1735,12 @@ pub(crate) mod attr { } } - #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] + #[cfg(any( + feature = "as_ref", + feature = "into", + feature = "from", + feature = "try_from" + ))] mod empty { use syn::{ parse::{Parse, ParseStream}, From 61a93b1990f39093de0bcbba797c16e025e5bf02 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 13:58:02 +0300 Subject: [PATCH 33/44] Add missing merge --- impl/src/into.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 357c2262..75c7b32d 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -144,7 +144,20 @@ impl Parse for StructAttribute { } } -impl attr::ParseMultiple for StructAttribute {} +impl attr::ParseMultiple for StructAttribute { + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + Ok(IntoArgs::merge_attrs( + prev.map(|attr| attr.args), + new.map(|attr| attr.args), + name, + )? + .map(Self::new)) + } +} /// Representation of an [`Into`] derive macro field attribute. /// From eae11e74eb7eda9c589d7022b53bbe5ee86ac11a Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 14:12:11 +0300 Subject: [PATCH 34/44] Fix empty attribute parsing --- impl/src/into.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 75c7b32d..9144cebf 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -125,6 +125,10 @@ impl StructAttribute { fn new(args: IntoArgs) -> Self { Self { args } } + + fn into_inner(self) -> IntoArgs { + self.args + } } impl From> for StructAttribute { @@ -145,14 +149,21 @@ impl Parse for StructAttribute { } impl attr::ParseMultiple for StructAttribute { + fn parse_attr_with( + attr: &syn::Attribute, + parser: &P, + ) -> syn::Result { + >::parse_attr_with(attr, parser).map(Self::from) + } + fn merge_attrs( prev: Spanning, new: Spanning, name: &syn::Ident, ) -> syn::Result> { Ok(IntoArgs::merge_attrs( - prev.map(|attr| attr.args), - new.map(|attr| attr.args), + prev.map(Self::into_inner), + new.map(Self::into_inner), name, )? .map(Self::new)) @@ -215,6 +226,16 @@ impl From, attr::Alt>> for FieldAttri } impl attr::ParseMultiple for FieldAttribute { + fn parse_attr_with( + attr: &syn::Attribute, + parser: &P, + ) -> syn::Result { + >>::parse_attr_with( + attr, parser, + ) + .map(Self::from) + } + fn merge_attrs( prev: Spanning, new: Spanning, From 0dcf3d5e800c0e54d1cb5f2a3892854d60d1702d Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 14:33:51 +0300 Subject: [PATCH 35/44] Put field and struct attributes in submodules --- impl/src/into.rs | 236 +++++++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 112 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 9144cebf..5684c992 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -21,6 +21,8 @@ use crate::utils::{ polyfill, Either, FieldsExt, Spanning, }; +use self::{field_attr::FieldAttribute, struct_attr::StructAttribute}; + /// Expands an [`Into`] derive macro. pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { let attr_name = format_ident!("into"); @@ -109,146 +111,156 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result)] -/// #[into(owned(), ref(), ref_mut())] -/// ``` -#[derive(Debug, Default)] -struct StructAttribute { - args: IntoArgs, -} - -impl StructAttribute { - fn new(args: IntoArgs) -> Self { - Self { args } +mod struct_attr { + use super::IntoArgs; + use crate::utils::{attr, Either, Spanning}; + use syn::parse::{Parse, ParseStream}; + + /// Representation of an [`Into`] derive macro struct container attribute. + /// + /// ```rust,ignore + /// #[into] + /// #[into()] + /// #[into(owned(), ref(), ref_mut())] + /// ``` + #[derive(Debug, Default)] + pub(super) struct StructAttribute { + pub(super) args: IntoArgs, } - fn into_inner(self) -> IntoArgs { - self.args - } -} + impl StructAttribute { + pub(super) fn new(args: IntoArgs) -> Self { + Self { args } + } -impl From> for StructAttribute { - fn from(value: Either) -> Self { - Self::new(match value { - Either::Left(_) => IntoArgs::all_owned(), - Either::Right(args) => args, - }) + fn into_inner(self) -> IntoArgs { + self.args + } } -} -impl Parse for StructAttribute { - fn parse(input: ParseStream) -> syn::Result { - input - .parse::>() - .map(Self::from) - } -} + type UntypedParse = Either; -impl attr::ParseMultiple for StructAttribute { - fn parse_attr_with( - attr: &syn::Attribute, - parser: &P, - ) -> syn::Result { - >::parse_attr_with(attr, parser).map(Self::from) + impl From for StructAttribute { + fn from(value: Either) -> Self { + Self::new(match value { + Either::Left(_) => IntoArgs::all_owned(), + Either::Right(args) => args, + }) + } } - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - Ok(IntoArgs::merge_attrs( - prev.map(Self::into_inner), - new.map(Self::into_inner), - name, - )? - .map(Self::new)) + impl Parse for StructAttribute { + fn parse(input: ParseStream) -> syn::Result { + input.parse::().map(Self::from) + } } -} -/// Representation of an [`Into`] derive macro field attribute. -/// -/// ```rust,ignore -/// #[into] -/// #[into(skip)] #[into(ignore)] -/// #[into()] -/// #[into(owned(), ref(), ref_mut())] -/// ``` -#[derive(Default)] -struct FieldAttribute { - skip: Option, - args: Option, -} + impl attr::ParseMultiple for StructAttribute { + fn parse_attr_with( + attr: &syn::Attribute, + parser: &P, + ) -> syn::Result { + >::parse_attr_with(attr, parser) + .map(Self::from) + } -impl From>> for FieldAttribute { - fn from(value: Either>) -> Self { - match value { - Either::Left(skip) => Self { - skip: Some(skip), - args: None, - }, - Either::Right(args) => Self { - skip: None, - args: Some(match args { - Either::Left(_) => IntoArgs::all_owned(), - Either::Right(args) => args, - }), - }, + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + Ok(IntoArgs::merge_attrs( + prev.map(Self::into_inner), + new.map(Self::into_inner), + name, + )? + .map(Self::new)) } } } -impl Parse for FieldAttribute { - fn parse(input: ParseStream) -> syn::Result { - input - .parse::>>() - .map(Self::from) +mod field_attr { + use super::IntoArgs; + use crate::utils::{attr, Either, Spanning}; + use syn::parse::{Parse, ParseStream}; + + /// Representation of an [`Into`] derive macro field attribute. + /// + /// ```rust,ignore + /// #[into] + /// #[into(skip)] #[into(ignore)] + /// #[into()] + /// #[into(owned(), ref(), ref_mut())] + /// ``` + #[derive(Default)] + pub(super) struct FieldAttribute { + pub(super) skip: Option, + pub(super) args: Option, } -} -impl From for attr::Pair, attr::Alt> { - fn from(value: FieldAttribute) -> Self { - attr::Pair::new(attr::Alt::new(value.skip), attr::Alt::new(value.args)) + type UntypedParse = Either>; + + impl From for FieldAttribute { + fn from(value: UntypedParse) -> Self { + match value { + Either::Left(skip) => Self { + skip: Some(skip), + args: None, + }, + Either::Right(args) => Self { + skip: None, + args: Some(match args { + Either::Left(_) => IntoArgs::all_owned(), + Either::Right(args) => args, + }), + }, + } + } } -} -impl From, attr::Alt>> for FieldAttribute { - fn from(value: attr::Pair, attr::Alt>) -> Self { - Self { - skip: value.left.into_inner(), - args: value.right.into_inner(), + impl Parse for FieldAttribute { + fn parse(input: ParseStream) -> syn::Result { + input.parse::().map(Self::from) } } -} -impl attr::ParseMultiple for FieldAttribute { - fn parse_attr_with( - attr: &syn::Attribute, - parser: &P, - ) -> syn::Result { - >>::parse_attr_with( - attr, parser, - ) - .map(Self::from) + type UntypedMerge = attr::Pair, attr::Alt>; + + impl From for UntypedMerge { + fn from(value: FieldAttribute) -> Self { + attr::Pair::new(attr::Alt::new(value.skip), attr::Alt::new(value.args)) + } } - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - Ok( - , attr::Alt>>::merge_attrs( + impl From for FieldAttribute { + fn from(value: UntypedMerge) -> Self { + Self { + skip: value.left.into_inner(), + args: value.right.into_inner(), + } + } + } + + impl attr::ParseMultiple for FieldAttribute { + fn parse_attr_with( + attr: &syn::Attribute, + parser: &P, + ) -> syn::Result { + UntypedParse::parse_attr_with(attr, parser).map(Self::from) + } + + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + Ok(UntypedMerge::merge_attrs( prev.map(Self::into), new.map(Self::into), name, )? - .map(Self::from), - ) + .map(Self::from)) + } } } From 7328496b7d4c7a21075856394cb17cb3cda6f73a Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 15:08:05 +0300 Subject: [PATCH 36/44] Add some doc comments, add more structure in utils --- impl/src/into.rs | 9 ++ impl/src/utils.rs | 206 +++++++++++++++++++++++++++------------------- 2 files changed, 132 insertions(+), 83 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 5684c992..407505d9 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -138,6 +138,9 @@ mod struct_attr { } } + /// Untyped analogue of a [`StructAttribute`], recreating its type structure via [`Either`]. + /// + /// Used to piggyback [`Parse::parse`] and [`ParseMultiple::parse_attr_with`] impls to [`Either`]. type UntypedParse = Either; impl From for StructAttribute { @@ -198,6 +201,9 @@ mod field_attr { pub(super) args: Option, } + /// Untyped analogue of a [`StructAttribute`], recreating its type structure via [`Either`]. + /// + /// Used to piggyback [`Parse::parse`] and [`ParseMultiple::parse_attr_with`] impls to [`Either`]. type UntypedParse = Either>; impl From for FieldAttribute { @@ -224,6 +230,9 @@ mod field_attr { } } + /// Untyped analogue of a [`FieldAttribute`], recreating its type structure via [`attr::Pair`] and [`attr::Alt`] + /// + /// Used to piggyback [`ParseMultiple::merge_attrs`] impl to [`attr::Pair`] and [`attr::Alt`] type UntypedMerge = attr::Pair, attr::Alt>; impl From for UntypedMerge { diff --git a/impl/src/utils.rs b/impl/src/utils.rs index a955cc46..6cf6c9af 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1514,6 +1514,8 @@ pub(crate) mod attr { pub(crate) use self::skip::Skip; #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] pub(crate) use self::types::Types; + #[cfg(feature = "into")] + pub(crate) use self::{alt::Alt, pair::Pair}; #[cfg(any(feature = "as_ref", feature = "from"))] pub(crate) use self::{ conversion::Conversion, field_conversion::FieldConversion, forward::Forward, @@ -1633,105 +1635,143 @@ pub(crate) mod attr { } } - pub(crate) struct Alt(Option); + #[cfg(feature = "into")] + mod alt { + use super::{ParseMultiple, Parser, Spanning}; + use syn::parse::{Parse, ParseStream}; - impl Alt { - pub(crate) fn new(value: Option) -> Self { - Self(value) - } + /// Newtype for parsing and merging optional values + pub(crate) struct Alt(Option); - pub(crate) fn into_inner(self) -> Option { - self.0 + impl Alt { + pub(crate) fn new(value: Option) -> Self { + Self(value) + } + + pub(crate) fn into_inner(self) -> Option { + self.0 + } } - } - impl Default for Alt { - fn default() -> Self { - Self(None) + impl Default for Alt { + fn default() -> Self { + Self(None) + } } - } - impl Parse for Alt { - fn parse(input: ParseStream) -> syn::Result { - Ok(Alt(input.parse().ok())) + impl Parse for Alt { + fn parse(input: ParseStream) -> syn::Result { + Ok(Alt(input.parse().ok())) + } } - } - impl ParseMultiple for Alt { - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - Ok(match (prev.item.0, new.item.0) { - (Some(p), Some(n)) => T::merge_attrs( - Spanning::new(p, prev.span), - Spanning::new(n, new.span), - name, - )? - .map(|value| Alt(Some(value))), - (Some(p), None) => Spanning::new(Alt(Some(p)), prev.span), - (None, Some(n)) => Spanning::new(Alt(Some(n)), new.span), - (None, None) => Spanning::new( - Alt(None), - prev.span.join(new.span).unwrap_or(prev.span), - ), - }) + impl ParseMultiple for Alt { + fn parse_attr_with( + attr: &syn::Attribute, + parser: &P, + ) -> syn::Result { + Ok(Alt(T::parse_attr_with(attr, parser).ok())) + } + + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + Ok(match (prev.item.0, new.item.0) { + (Some(p), Some(n)) => T::merge_attrs( + Spanning::new(p, prev.span), + Spanning::new(n, new.span), + name, + )? + .map(|value| Alt(Some(value))), + (Some(p), None) => Spanning::new(Alt(Some(p)), prev.span), + (None, Some(n)) => Spanning::new(Alt(Some(n)), new.span), + (None, None) => Spanning::new( + Alt(None), + prev.span.join(new.span).unwrap_or(prev.span), + ), + }) + } } } - pub(crate) struct Pair { - pub(crate) left: L, - pub(crate) right: R, - } + #[cfg(feature = "into")] + mod pair { + use super::{ParseMultiple, Parser, Spanning}; + use syn::parse::{Parse, ParseStream}; - impl Pair { - pub(crate) fn new(left: L, right: R) -> Self { - Pair { left, right } + /// Used to encode product types in a generic way + pub(crate) struct Pair { + pub(crate) left: L, + pub(crate) right: R, } - } - impl Parse for Pair { - fn parse(input: ParseStream) -> syn::Result { - Ok(if let Ok(left) = input.parse() { - Pair { - left, - right: R::default(), - } - } else { - Pair { - left: L::default(), - right: input.parse()?, - } - }) + impl Pair { + pub(crate) fn new(left: L, right: R) -> Self { + Pair { left, right } + } } - } - impl ParseMultiple - for Pair - { - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - let left = L::merge_attrs( - Spanning::new(prev.item.left, prev.span), - Spanning::new(new.item.left, new.span), - name, - )? - .into_inner(); - - let right = R::merge_attrs( - Spanning::new(prev.item.right, prev.span), - Spanning::new(new.item.right, new.span), - name, - )? - .into_inner(); - - let span = prev.span.join(new.span).unwrap_or(prev.span); - - Ok(Spanning::new(Pair { left, right }, span)) + impl Parse for Pair { + fn parse(input: ParseStream) -> syn::Result { + Ok(if let Ok(left) = input.parse() { + Pair { + left, + right: R::default(), + } + } else { + Pair { + left: L::default(), + right: input.parse()?, + } + }) + } + } + + impl ParseMultiple + for Pair + { + fn parse_attr_with( + attr: &syn::Attribute, + parser: &P, + ) -> syn::Result { + Ok(if let Ok(left) = L::parse_attr_with(attr, parser) { + Pair { + left, + right: R::default(), + } + } else { + Pair { + left: L::default(), + right: R::parse_attr_with(attr, parser)?, + } + }) + } + + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + let left = L::merge_attrs( + Spanning::new(prev.item.left, prev.span), + Spanning::new(new.item.left, new.span), + name, + )? + .into_inner(); + + let right = R::merge_attrs( + Spanning::new(prev.item.right, prev.span), + Spanning::new(new.item.right, new.span), + name, + )? + .into_inner(); + + let span = prev.span.join(new.span).unwrap_or(prev.span); + + Ok(Spanning::new(Pair { left, right }, span)) + } } } From 64974c7413d5229f3d43a92c01049e1390df9909 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Sun, 8 Oct 2023 15:29:49 +0300 Subject: [PATCH 37/44] Fix legacy syntax check and error messages --- impl/src/into.rs | 16 ++++++++++------ .../into/mixed_field_skip_types.stderr | 6 +++--- tests/compile_fail/into/multiple_skip.stderr | 6 +++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 407505d9..15c7a132 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -57,7 +57,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { - /// [`syn::Fields`] of a struct, the [`StructAttribute`] is parsed for. - fields: &'a syn::Fields, +struct ConsiderLegacySyntax { + /// [`syn::Field`]s the [`StructAttribute`] or [`FieldAttribute`] is parsed for. + fields: F, } -impl attr::Parser for ConsiderLegacySyntax<'_> { +impl<'a, F> attr::Parser for ConsiderLegacySyntax<&'a F> +where + F: FieldsExt + ?Sized, + &'a F: IntoIterator, +{ fn parse(&self, input: ParseStream<'_>) -> syn::Result { - if TypeId::of::() == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { check_legacy_syntax(input, self.fields)?; } T::parse(input) diff --git a/tests/compile_fail/into/mixed_field_skip_types.stderr b/tests/compile_fail/into/mixed_field_skip_types.stderr index a15b2982..d36a37ba 100644 --- a/tests/compile_fail/into/mixed_field_skip_types.stderr +++ b/tests/compile_fail/into/mixed_field_skip_types.stderr @@ -1,5 +1,5 @@ -error: unexpected token - --> tests/compile_fail/into/mixed_field_skip_types.rs:3:16 +error[E0412]: cannot find type `skip` in this scope + --> tests/compile_fail/into/mixed_field_skip_types.rs:3:12 | 3 | #[into(skip, i32)] - | ^ + | ^^^^ not found in this scope diff --git a/tests/compile_fail/into/multiple_skip.stderr b/tests/compile_fail/into/multiple_skip.stderr index c8a76eb0..cce7720c 100644 --- a/tests/compile_fail/into/multiple_skip.stderr +++ b/tests/compile_fail/into/multiple_skip.stderr @@ -1,5 +1,5 @@ -error: only a single `#[into(skip)]`/`#[into(ignore)]` attribute is allowed` - --> tests/compile_fail/into/multiple_skip.rs:4:7 +error: only single `#[into(skip)]`/`#[into(ignore)]` attribute is allowed here + --> tests/compile_fail/into/multiple_skip.rs:4:5 | 4 | #[into(skip)] - | ^^^^ + | ^ From e67d735e99f704957245530afd53fc392feb7121 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 9 Oct 2023 12:38:56 +0300 Subject: [PATCH 38/44] Rename IntoArgs into IntoConversions --- impl/src/into.rs | 87 ++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index 15c7a132..fac923b2 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -67,31 +67,32 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result>>()?; - let (fields, fields_args) = fields_data.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); + let (fields, fields_convs) = + fields_data.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); let struct_attr = struct_attr.or_else(|| { - fields_args + fields_convs .iter() .all(Option::is_none) - .then(IntoArgs::all_owned) + .then(IntoConversions::all_owned) .map(StructAttribute::new) }); let mut expansions: Vec<_> = fields .iter() - .zip(fields_args) - .filter_map(|(&(i, field, _), args)| { - args.map(|args| Expansion { + .zip(fields_convs) + .filter_map(|(&(i, field, _), convs)| { + convs.map(|convs| Expansion { input_ident: &input.ident, input_generics: &input.generics, fields: vec![(i, field)], - args, + convs, }) }) .collect(); @@ -104,7 +105,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result Self { - Self { args } + pub(super) fn new(convs: IntoConversions) -> Self { + Self { convs } } - fn into_inner(self) -> IntoArgs { - self.args + fn into_inner(self) -> IntoConversions { + self.convs } } /// Untyped analogue of a [`StructAttribute`], recreating its type structure via [`Either`]. /// /// Used to piggyback [`Parse::parse`] and [`ParseMultiple::parse_attr_with`] impls to [`Either`]. - type UntypedParse = Either; + type UntypedParse = Either; impl From for StructAttribute { - fn from(value: Either) -> Self { + fn from(value: Either) -> Self { Self::new(match value { - Either::Left(_) => IntoArgs::all_owned(), - Either::Right(args) => args, + Either::Left(_) => IntoConversions::all_owned(), + Either::Right(convs) => convs, }) } } @@ -163,7 +164,7 @@ mod struct_attr { attr: &syn::Attribute, parser: &P, ) -> syn::Result { - >::parse_attr_with(attr, parser) + >::parse_attr_with(attr, parser) .map(Self::from) } @@ -172,7 +173,7 @@ mod struct_attr { new: Spanning, name: &syn::Ident, ) -> syn::Result> { - Ok(IntoArgs::merge_attrs( + Ok(IntoConversions::merge_attrs( prev.map(Self::into_inner), new.map(Self::into_inner), name, @@ -183,7 +184,7 @@ mod struct_attr { } mod field_attr { - use super::IntoArgs; + use super::IntoConversions; use crate::utils::{attr, Either, Spanning}; use syn::parse::{Parse, ParseStream}; @@ -198,26 +199,26 @@ mod field_attr { #[derive(Default)] pub(super) struct FieldAttribute { pub(super) skip: Option, - pub(super) args: Option, + pub(super) convs: Option, } /// Untyped analogue of a [`StructAttribute`], recreating its type structure via [`Either`]. /// /// Used to piggyback [`Parse::parse`] and [`ParseMultiple::parse_attr_with`] impls to [`Either`]. - type UntypedParse = Either>; + type UntypedParse = Either>; impl From for FieldAttribute { fn from(value: UntypedParse) -> Self { match value { Either::Left(skip) => Self { skip: Some(skip), - args: None, + convs: None, }, - Either::Right(args) => Self { + Either::Right(convs) => Self { skip: None, - args: Some(match args { - Either::Left(_) => IntoArgs::all_owned(), - Either::Right(args) => args, + convs: Some(match convs { + Either::Left(_) => IntoConversions::all_owned(), + Either::Right(convs) => convs, }), }, } @@ -233,11 +234,11 @@ mod field_attr { /// Untyped analogue of a [`FieldAttribute`], recreating its type structure via [`attr::Pair`] and [`attr::Alt`] /// /// Used to piggyback [`ParseMultiple::merge_attrs`] impl to [`attr::Pair`] and [`attr::Alt`] - type UntypedMerge = attr::Pair, attr::Alt>; + type UntypedMerge = attr::Pair, attr::Alt>; impl From for UntypedMerge { fn from(value: FieldAttribute) -> Self { - attr::Pair::new(attr::Alt::new(value.skip), attr::Alt::new(value.args)) + attr::Pair::new(attr::Alt::new(value.skip), attr::Alt::new(value.convs)) } } @@ -245,7 +246,7 @@ mod field_attr { fn from(value: UntypedMerge) -> Self { Self { skip: value.left.into_inner(), - args: value.right.into_inner(), + convs: value.right.into_inner(), } } } @@ -278,7 +279,7 @@ mod field_attr { /// [`None`] represents no conversions of the given type /// An empty [`Punctuated`] represents a conversion into the field types #[derive(Debug, Default)] -struct IntoArgs { +struct IntoConversions { /// [`Type`]s wrapped into `owned(...)` or simply `#[into(...)]`. owned: Option>, @@ -289,7 +290,7 @@ struct IntoArgs { ref_mut: Option>, } -impl IntoArgs { +impl IntoConversions { fn all_owned() -> Self { Self { owned: Some(Punctuated::new()), @@ -299,7 +300,7 @@ impl IntoArgs { } } -impl Parse for IntoArgs { +impl Parse for IntoConversions { fn parse(input: ParseStream<'_>) -> syn::Result { let mut out = Self::default(); @@ -388,7 +389,7 @@ struct Expansion<'a> { /// Fields to convert from, with their indices fields: Vec<(usize, &'a syn::Field)>, /// Arguments specifying conversions - args: IntoArgs, + convs: IntoConversions, } impl<'a> Expansion<'a> { @@ -397,7 +398,7 @@ impl<'a> Expansion<'a> { input_ident, input_generics, fields, - args, + convs, } = self; let fields_idents: Vec<_> = fields @@ -415,9 +416,9 @@ impl<'a> Expansion<'a> { }); [ - (&args.owned, false, false), - (&args.r#ref, true, false), - (&args.ref_mut, true, true), + (&convs.owned, false, false), + (&convs.r#ref, true, false), + (&convs.ref_mut, true, true), ] .into_iter() .filter_map(|(out_tys, r, m)| { @@ -469,7 +470,7 @@ impl<'a> Expansion<'a> { } } -impl attr::ParseMultiple for IntoArgs { +impl attr::ParseMultiple for IntoConversions { fn merge_attrs( prev: Spanning, new: Spanning, @@ -516,7 +517,7 @@ where &'a F: IntoIterator, { fn parse(&self, input: ParseStream<'_>) -> syn::Result { - if TypeId::of::() == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { check_legacy_syntax(input, self.fields)?; } T::parse(input) From 78b9fc1d1060c876547e95254d1ea6a26785b5b3 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 9 Oct 2023 12:56:47 +0300 Subject: [PATCH 39/44] Make minor comment corrections --- impl/src/into.rs | 1 + impl/src/utils.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/impl/src/into.rs b/impl/src/into.rs index fac923b2..a37c2e48 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -276,6 +276,7 @@ mod field_attr { /// A set of type arguments for a set of fields /// +/// For each field: /// [`None`] represents no conversions of the given type /// An empty [`Punctuated`] represents a conversion into the field types #[derive(Debug, Default)] diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 6cf6c9af..647c8903 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1702,6 +1702,10 @@ pub(crate) mod attr { use syn::parse::{Parse, ParseStream}; /// Used to encode product types in a generic way + /// + /// Either `left` or `right` can be parsed from a single attribute + /// The other is filled using [`Default`] + /// Both get merged pub(crate) struct Pair { pub(crate) left: L, pub(crate) right: R, From e0f6046b9beee8bc451198a0cf62122276dd20dd Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 9 Oct 2023 12:59:10 +0300 Subject: [PATCH 40/44] Add PR link to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a94e1d59..0c2a80c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). and ignores field type itself. - The `Into` derive now generates separate impls for each field with the `#[into(...)]` attribute applied to it. + ([#291](https://github.com/JelteF/derive_more/pull/291)) - Importing a derive macro now also import its corresponding trait. - The `Error` derive is updated with changes to the `error_generic_member_access` unstable feature for nightly users. ([#200](https://github.com/JelteF/derive_more/pull/200), From beda92c9b301bd299601c449b0c036748fb206f7 Mon Sep 17 00:00:00 2001 From: MegaBluejay Date: Mon, 9 Oct 2023 13:04:07 +0300 Subject: [PATCH 41/44] Make expanatory comments in tests match other tests more closely --- tests/into.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/into.rs b/tests/into.rs index 8e9b9eb0..ae52218f 100644 --- a/tests/into.rs +++ b/tests/into.rs @@ -1050,11 +1050,13 @@ mod field_attr { mod fields_only { use super::*; - // The manual impls are there to test that no extra ones were generated - #[derive(Debug, Into, Clone, Copy)] struct Tuple(#[into] i32, f64, #[into] f64); + // Asserts that macro expansion doesn't generate impls not specified, + // by producing a trait implementations conflict error during compilation, + // if it does. + impl From for (i32, f64, f64) { fn from(value: Tuple) -> Self { (value.0, value.1, value.2) @@ -1084,6 +1086,10 @@ mod field_attr { c: f64, } + // Asserts that macro expansion doesn't generate impls not specified, + // by producing a trait implementations conflict error during compilation, + // if it does. + impl From for (i32, f64, f64) { fn from(value: Struct) -> Self { (value.a, value.b, value.c) @@ -1118,6 +1124,10 @@ mod field_attr { #[into(f32, f64)] f32, ); + // Asserts that macro expansion doesn't generate impls not specified, + // by producing a trait implementations conflict error during compilation, + // if it does. + impl From for String { fn from(value: Tuple) -> Self { value.0 @@ -1149,6 +1159,10 @@ mod field_attr { c: f32, } + // Asserts that macro expansion doesn't generate impls not specified, + // by producing a trait implementations conflict error during compilation, + // if it does. + impl From for String { fn from(value: Struct) -> Self { value.a @@ -1187,6 +1201,10 @@ mod field_attr { #[derive(Debug, Into)] struct Tuple(#[into(ref)] String, f64, #[into(ref)] f64); + // Asserts that macro expansion doesn't generate impls not specified, + // by producing a trait implementations conflict error during compilation, + // if it does. + impl<'a> From<&'a Tuple> for (&'a String, &'a f64, &'a f64) { fn from(value: &'a Tuple) -> Self { (&value.0, &value.1, &value.2) @@ -1210,6 +1228,10 @@ mod field_attr { c: f64, } + // Asserts that macro expansion doesn't generate impls not specified, + // by producing a trait implementations conflict error during compilation, + // if it does. + impl<'a> From<&'a Struct> for (&'a String, &'a f64, &'a f64) { fn from(value: &'a Struct) -> Self { (&value.a, &value.b, &value.c) From 53803d1d953d85a5db363977ea20d8822be9ffd1 Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 11 Oct 2023 17:50:59 +0300 Subject: [PATCH 42/44] Correct docs --- impl/doc/into.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/impl/doc/into.md b/impl/doc/into.md index f49878c0..81d47308 100644 --- a/impl/doc/into.md +++ b/impl/doc/into.md @@ -108,11 +108,14 @@ assert_eq!(5_i128, Mass::::new(5).into()); # } ``` -The `#[into(...)]` attribute can also be applied to specific fields + +### Fields + +The `#[into]` attribute can also be applied to specific fields of a struct. ```rust # use derive_more::Into; - +# #[derive(Into)] struct Data { id: i32, @@ -123,12 +126,12 @@ struct Data { assert_eq!(42.0, Data { id: 1, raw: 42.0 }.into()); ``` -In this case no conversion into a tuple of all fields is generated unless -an explicit struct attribute is present +In such cases, no conversion into a tuple of all fields is generated, unless +an explicit struct attribute is present. ```rust # use derive_more::Into; - +# #[derive(Into)] #[into] struct Data { @@ -141,13 +144,13 @@ assert_eq!(42.0, Data { id: 1, raw: 42.0 }.into()); assert_eq!((1, 42.0), Data { id: 1, raw: 42.0 }.into()); ``` -The same `#[into()]` syntax can be used in field attributes as well +The `#[into()]` syntax can be used on fields as well. ```rust # use std::marker::PhantomData; # use derive_more::Into; # struct Whatever; - +# #[derive(Into, Clone)] #[into(owned, ref((u8, str)), ref_mut)] struct Foo { @@ -167,7 +170,8 @@ assert_eq!(1_u64, foo.clone().into()); assert_eq!(&1_u8, <&u8>::from(&foo)); ``` -Fields that have specific conversions into them can also be skipped +Fields, having specific conversions into them, can also be skipped for top-level +tuple conversions. ```rust # use derive_more::Into; From 0240f80672634fa2c6cc304ce42b4a2a6a8f1a77 Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Oct 2023 14:03:50 +0300 Subject: [PATCH 43/44] Simplify implementation --- impl/src/into.rs | 436 ++++++++++++++++++++-------------------------- impl/src/utils.rs | 183 ++++--------------- tests/into.rs | 72 ++++---- 3 files changed, 256 insertions(+), 435 deletions(-) diff --git a/impl/src/into.rs b/impl/src/into.rs index a37c2e48..85f75ab0 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -3,7 +3,7 @@ use std::{ any::{Any, TypeId}, borrow::Cow, - iter, + iter, slice, }; use proc_macro2::{Span, TokenStream}; @@ -21,8 +21,6 @@ use crate::utils::{ polyfill, Either, FieldsExt, Spanning, }; -use self::{field_attr::FieldAttribute, struct_attr::StructAttribute}; - /// Expands an [`Into`] derive macro. pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { let attr_name = format_ident!("into"); @@ -57,7 +55,7 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result>>()?; - - let (fields, fields_convs) = - fields_data.into_iter().unzip::<_, _, Vec<_>, Vec<_>>(); + let (fields, fields_convs): (Vec<_>, Vec<_>) = fields_data.into_iter().unzip(); let struct_attr = struct_attr.or_else(|| { fields_convs .iter() .all(Option::is_none) - .then(IntoConversions::all_owned) - .map(StructAttribute::new) + .then(ConversionsAttribute::default) + .map(Either::Right) }); let mut expansions: Vec<_> = fields @@ -96,7 +92,6 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result syn::Result)] - /// #[into(owned(), ref(), ref_mut())] - /// ``` - #[derive(Debug, Default)] - pub(super) struct StructAttribute { - pub(super) convs: IntoConversions, - } +/// Expansion of an [`Into`] derive macro, generating [`From`] implementations for a struct. +struct Expansion<'a> { + /// [`syn::Ident`] of the struct. + input_ident: &'a syn::Ident, - impl StructAttribute { - pub(super) fn new(convs: IntoConversions) -> Self { - Self { convs } - } + /// [`syn::Generics`] of the struct. + input_generics: &'a syn::Generics, - fn into_inner(self) -> IntoConversions { - self.convs - } - } + /// Fields to convert from, along with their indices. + fields: Vec<(usize, &'a syn::Field)>, - /// Untyped analogue of a [`StructAttribute`], recreating its type structure via [`Either`]. - /// - /// Used to piggyback [`Parse::parse`] and [`ParseMultiple::parse_attr_with`] impls to [`Either`]. - type UntypedParse = Either; + /// Conversions to be generated. + convs: ConversionsAttribute, +} - impl From for StructAttribute { - fn from(value: Either) -> Self { - Self::new(match value { - Either::Left(_) => IntoConversions::all_owned(), - Either::Right(convs) => convs, - }) - } - } +impl<'a> Expansion<'a> { + fn expand(self) -> syn::Result { + let Self { + input_ident, + input_generics, + fields, + convs, + } = self; - impl Parse for StructAttribute { - fn parse(input: ParseStream) -> syn::Result { - input.parse::().map(Self::from) - } - } + let fields_idents: Vec<_> = fields + .iter() + .map(|(i, f)| { + f.ident + .as_ref() + .map_or_else(|| Either::Left(syn::Index::from(*i)), Either::Right) + }) + .collect(); + let fields_tys: Vec<_> = fields.iter().map(|(_, f)| &f.ty).collect(); + let fields_tuple = syn::Type::Tuple(syn::TypeTuple { + paren_token: token::Paren::default(), + elems: fields_tys.iter().cloned().cloned().collect(), + }); - impl attr::ParseMultiple for StructAttribute { - fn parse_attr_with( - attr: &syn::Attribute, - parser: &P, - ) -> syn::Result { - >::parse_attr_with(attr, parser) - .map(Self::from) - } + [ + (&convs.owned, false, false), + (&convs.r#ref, true, false), + (&convs.ref_mut, true, true), + ] + .into_iter() + .filter_map(|(out_tys, ref_, mut_)| { + out_tys.as_ref().map(|out_tys| { + let lf = ref_.then(|| { + syn::Lifetime::new("'__derive_more_into", Span::call_site()) + }); + let r = ref_.then(token::And::default); + let m = mut_.then(token::Mut::default); - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - Ok(IntoConversions::merge_attrs( - prev.map(Self::into_inner), - new.map(Self::into_inner), - name, - )? - .map(Self::new)) - } - } -} + let gens = if let Some(lf) = lf.clone() { + let mut gens = input_generics.clone(); + gens.params.push(syn::LifetimeParam::new(lf).into()); + Cow::Owned(gens) + } else { + Cow::Borrowed(input_generics) + }; + let (impl_gens, _, where_clause) = gens.split_for_impl(); + let (_, ty_gens, _) = input_generics.split_for_impl(); -mod field_attr { - use super::IntoConversions; - use crate::utils::{attr, Either, Spanning}; - use syn::parse::{Parse, ParseStream}; - - /// Representation of an [`Into`] derive macro field attribute. - /// - /// ```rust,ignore - /// #[into] - /// #[into(skip)] #[into(ignore)] - /// #[into()] - /// #[into(owned(), ref(), ref_mut())] - /// ``` - #[derive(Default)] - pub(super) struct FieldAttribute { - pub(super) skip: Option, - pub(super) convs: Option, - } + if out_tys.is_empty() { + Either::Left(iter::once(&fields_tuple)) + } else { + Either::Right(out_tys.iter()) + }.map(|out_ty| { + let tys: Vec<_> = fields_tys.validate_type(out_ty)?.collect(); - /// Untyped analogue of a [`StructAttribute`], recreating its type structure via [`Either`]. - /// - /// Used to piggyback [`Parse::parse`] and [`ParseMultiple::parse_attr_with`] impls to [`Either`]. - type UntypedParse = Either>; - - impl From for FieldAttribute { - fn from(value: UntypedParse) -> Self { - match value { - Either::Left(skip) => Self { - skip: Some(skip), - convs: None, - }, - Either::Right(convs) => Self { - skip: None, - convs: Some(match convs { - Either::Left(_) => IntoConversions::all_owned(), - Either::Right(convs) => convs, - }), - }, - } - } + Ok(quote! { + #[automatically_derived] + impl #impl_gens ::core::convert::From<#r #lf #m #input_ident #ty_gens> + for ( #( #r #lf #m #tys ),* ) #where_clause + { + #[inline] + fn from(value: #r #lf #m #input_ident #ty_gens) -> Self { + (#( + <#r #m #tys as ::core::convert::From<_>>::from( + #r #m value. #fields_idents + ) + ),*) + } + } + }) + }) + .collect::>() + }) + }) + .collect() } +} - impl Parse for FieldAttribute { - fn parse(input: ParseStream) -> syn::Result { - input.parse::().map(Self::from) +/// Representation of an [`Into`] derive macro struct container attribute. +/// +/// ```rust,ignore +/// #[into] +/// #[into()] +/// #[into(owned(), ref(), ref_mut())] +/// ``` +type StructAttribute = Either; + +impl From for ConversionsAttribute { + fn from(v: StructAttribute) -> Self { + match v { + Either::Left(_) => ConversionsAttribute::default(), + Either::Right(c) => c, } } +} - /// Untyped analogue of a [`FieldAttribute`], recreating its type structure via [`attr::Pair`] and [`attr::Alt`] - /// - /// Used to piggyback [`ParseMultiple::merge_attrs`] impl to [`attr::Pair`] and [`attr::Alt`] - type UntypedMerge = attr::Pair, attr::Alt>; +/// Representation of an [`Into`] derive macro field attribute. +/// +/// ```rust,ignore +/// #[into] +/// #[into()] +/// #[into(owned(), ref(), ref_mut())] +/// #[into(skip)] #[into(ignore)] +/// ``` +#[derive(Clone, Debug)] +struct FieldAttribute { + skip: Option, + convs: Option, +} - impl From for UntypedMerge { - fn from(value: FieldAttribute) -> Self { - attr::Pair::new(attr::Alt::new(value.skip), attr::Alt::new(value.convs)) - } +impl Parse for FieldAttribute { + fn parse(_: ParseStream<'_>) -> syn::Result { + unreachable!("call `attr::ParseMultiple::parse_attr_with()` instead") } +} - impl From for FieldAttribute { - fn from(value: UntypedMerge) -> Self { - Self { - skip: value.left.into_inner(), - convs: value.right.into_inner(), +impl attr::ParseMultiple for FieldAttribute { + fn parse_attr_with( + attr: &syn::Attribute, + parser: &P, + ) -> syn::Result { + type Untyped = Either>; + impl From for FieldAttribute { + fn from(v: Untyped) -> Self { + match v { + Untyped::Left(skip) => Self { + skip: Some(skip), + convs: None, + }, + Untyped::Right(c) => Self { + skip: None, + convs: Some(match c { + Either::Left(_empty) => ConversionsAttribute::default(), + Either::Right(convs) => convs, + }), + }, + } } } + + Untyped::parse_attr_with(attr, parser).map(Self::from) } - impl attr::ParseMultiple for FieldAttribute { - fn parse_attr_with( - attr: &syn::Attribute, - parser: &P, - ) -> syn::Result { - UntypedParse::parse_attr_with(attr, parser).map(Self::from) - } + fn merge_attrs( + prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + let skip = attr::Skip::merge_opt_attrs( + prev.clone().map(|v| v.skip).transpose(), + new.clone().map(|v| v.skip).transpose(), + name, + )? + .map(Spanning::into_inner); + + let convs = ConversionsAttribute::merge_opt_attrs( + prev.clone().map(|v| v.convs).transpose(), + new.clone().map(|v| v.convs).transpose(), + name, + )? + .map(Spanning::into_inner); - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - Ok(UntypedMerge::merge_attrs( - prev.map(Self::into), - new.map(Self::into), - name, - )? - .map(Self::from)) - } + Ok(Spanning::new( + Self { skip, convs }, + prev.span.join(new.span).unwrap_or(prev.span), + )) } } -/// A set of type arguments for a set of fields +/// Representation of an [`Into`] derive macro attribute describing specified [`Into`] conversions. +/// +/// ```rust,ignore +/// #[into()] +/// #[into(owned(), ref(), ref_mut())] +/// ``` /// /// For each field: -/// [`None`] represents no conversions of the given type -/// An empty [`Punctuated`] represents a conversion into the field types -#[derive(Debug, Default)] -struct IntoConversions { +/// - [`None`] represents no conversions. +/// - Empty [`Punctuated`] represents a conversion into the field type. +#[derive(Clone, Debug)] +struct ConversionsAttribute { /// [`Type`]s wrapped into `owned(...)` or simply `#[into(...)]`. owned: Option>, @@ -291,8 +311,8 @@ struct IntoConversions { ref_mut: Option>, } -impl IntoConversions { - fn all_owned() -> Self { +impl Default for ConversionsAttribute { + fn default() -> Self { Self { owned: Some(Punctuated::new()), r#ref: None, @@ -301,9 +321,13 @@ impl IntoConversions { } } -impl Parse for IntoConversions { +impl Parse for ConversionsAttribute { fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Self::default(); + let mut out = Self { + owned: None, + r#ref: None, + ref_mut: None, + }; let parse_inner = |ahead, types: &mut Option<_>| { input.advance_to(&ahead); @@ -381,97 +405,7 @@ impl Parse for IntoConversions { } } -/// Expansion of a macro for generating [`From`] implementations -struct Expansion<'a> { - /// Struct [`Ident`] - input_ident: &'a syn::Ident, - /// Struct [`Generics`] - input_generics: &'a syn::Generics, - /// Fields to convert from, with their indices - fields: Vec<(usize, &'a syn::Field)>, - /// Arguments specifying conversions - convs: IntoConversions, -} - -impl<'a> Expansion<'a> { - fn expand(self) -> syn::Result { - let Self { - input_ident, - input_generics, - fields, - convs, - } = self; - - let fields_idents: Vec<_> = fields - .iter() - .map(|(i, f)| { - f.ident - .as_ref() - .map_or_else(|| Either::Left(syn::Index::from(*i)), Either::Right) - }) - .collect(); - let fields_tys: Vec<_> = fields.iter().map(|(_, f)| &f.ty).collect(); - let fields_tuple = syn::Type::Tuple(syn::TypeTuple { - paren_token: Default::default(), - elems: fields_tys.iter().cloned().cloned().collect(), - }); - - [ - (&convs.owned, false, false), - (&convs.r#ref, true, false), - (&convs.ref_mut, true, true), - ] - .into_iter() - .filter_map(|(out_tys, r, m)| { - out_tys.as_ref().map(|out_tys| { - let lf = r.then(|| { - syn::Lifetime::new("'__derive_more_into", Span::call_site()) - }); - let r = r.then(token::And::default); - let m = m.then(token::Mut::default); - - let gens = if let Some(lf) = lf.clone() { - let mut gens = input_generics.clone(); - gens.params.push(syn::LifetimeParam::new(lf).into()); - Cow::Owned(gens) - } else { - Cow::Borrowed(input_generics) - }; - - let (impl_gens, _, where_clause) = gens.split_for_impl(); - let (_, ty_gens, _) = input_generics.split_for_impl(); - - if out_tys.is_empty() { - Either::Left(iter::once(&fields_tuple)) - } else { - Either::Right(out_tys.iter()) - }.map(|out_ty| { - let tys: Vec<_> = fields_tys.validate_type(out_ty)?.collect(); - - Ok(quote! { - #[automatically_derived] - impl #impl_gens ::core::convert::From<#r #lf #m #input_ident #ty_gens> - for ( #( #r #lf #m #tys ),* ) #where_clause - { - #[inline] - fn from(value: #r #lf #m #input_ident #ty_gens) -> Self { - (#( - <#r #m #tys as ::core::convert::From<_>>::from( - #r #m value. #fields_idents - ) - ),*) - } - } - }) - }) - .collect::>() - }) - }) - .collect() - } -} - -impl attr::ParseMultiple for IntoConversions { +impl attr::ParseMultiple for ConversionsAttribute { fn merge_attrs( prev: Spanning, new: Spanning, @@ -506,7 +440,7 @@ impl attr::ParseMultiple for IntoConversions { } /// [`attr::Parser`] considering legacy syntax and performing [`check_legacy_syntax()`] for a -/// [`StructAttribute`]. +/// [`StructAttribute`] or a [`FieldAttribute`]. struct ConsiderLegacySyntax { /// [`syn::Field`]s the [`StructAttribute`] or [`FieldAttribute`] is parsed for. fields: F, @@ -518,7 +452,7 @@ where &'a F: IntoIterator, { fn parse(&self, input: ParseStream<'_>) -> syn::Result { - if TypeId::of::() == TypeId::of::() { + if TypeId::of::() == TypeId::of::() { check_legacy_syntax(input, self.fields)?; } T::parse(input) diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 647c8903..72f11eb4 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1464,6 +1464,19 @@ mod spanning { } } + #[cfg(feature = "into")] + impl Spanning> { + pub(crate) fn transpose(self) -> Option> { + match self.item { + Some(item) => Some(Spanning { + item, + span: self.span, + }), + None => None, + } + } + } + impl Deref for Spanning { type Target = T; @@ -1499,8 +1512,8 @@ pub(crate) mod attr { #[cfg(any( feature = "as_ref", - feature = "into", feature = "from", + feature = "into", feature = "try_from" ))] pub(crate) use self::empty::Empty; @@ -1514,8 +1527,6 @@ pub(crate) mod attr { pub(crate) use self::skip::Skip; #[cfg(any(feature = "as_ref", feature = "from", feature = "try_from"))] pub(crate) use self::types::Types; - #[cfg(feature = "into")] - pub(crate) use self::{alt::Alt, pair::Pair}; #[cfg(any(feature = "as_ref", feature = "from"))] pub(crate) use self::{ conversion::Conversion, field_conversion::FieldConversion, forward::Forward, @@ -1565,6 +1576,23 @@ pub(crate) mod attr { )) } + /// Merges multiple [`Option`]al values of this attribute into a single one. + /// + /// Default implementation uses [`ParseMultiple::merge_attrs()`] when both `prev` and `new` + /// are [`Some`]. + fn merge_opt_attrs( + prev: Option>, + new: Option>, + name: &syn::Ident, + ) -> syn::Result>> { + Ok(match (prev, new) { + (Some(p), Some(n)) => Some(Self::merge_attrs(p, n, name)?), + (Some(p), None) => Some(p), + (None, Some(n)) => Some(n), + (None, None) => None, + }) + } + /// Parses this attribute from the provided multiple [`syn::Attribute`]s with the provided /// [`Parser`], merging them, and preserving their [`Span`]. /// @@ -1635,154 +1663,10 @@ pub(crate) mod attr { } } - #[cfg(feature = "into")] - mod alt { - use super::{ParseMultiple, Parser, Spanning}; - use syn::parse::{Parse, ParseStream}; - - /// Newtype for parsing and merging optional values - pub(crate) struct Alt(Option); - - impl Alt { - pub(crate) fn new(value: Option) -> Self { - Self(value) - } - - pub(crate) fn into_inner(self) -> Option { - self.0 - } - } - - impl Default for Alt { - fn default() -> Self { - Self(None) - } - } - - impl Parse for Alt { - fn parse(input: ParseStream) -> syn::Result { - Ok(Alt(input.parse().ok())) - } - } - - impl ParseMultiple for Alt { - fn parse_attr_with( - attr: &syn::Attribute, - parser: &P, - ) -> syn::Result { - Ok(Alt(T::parse_attr_with(attr, parser).ok())) - } - - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - Ok(match (prev.item.0, new.item.0) { - (Some(p), Some(n)) => T::merge_attrs( - Spanning::new(p, prev.span), - Spanning::new(n, new.span), - name, - )? - .map(|value| Alt(Some(value))), - (Some(p), None) => Spanning::new(Alt(Some(p)), prev.span), - (None, Some(n)) => Spanning::new(Alt(Some(n)), new.span), - (None, None) => Spanning::new( - Alt(None), - prev.span.join(new.span).unwrap_or(prev.span), - ), - }) - } - } - } - - #[cfg(feature = "into")] - mod pair { - use super::{ParseMultiple, Parser, Spanning}; - use syn::parse::{Parse, ParseStream}; - - /// Used to encode product types in a generic way - /// - /// Either `left` or `right` can be parsed from a single attribute - /// The other is filled using [`Default`] - /// Both get merged - pub(crate) struct Pair { - pub(crate) left: L, - pub(crate) right: R, - } - - impl Pair { - pub(crate) fn new(left: L, right: R) -> Self { - Pair { left, right } - } - } - - impl Parse for Pair { - fn parse(input: ParseStream) -> syn::Result { - Ok(if let Ok(left) = input.parse() { - Pair { - left, - right: R::default(), - } - } else { - Pair { - left: L::default(), - right: input.parse()?, - } - }) - } - } - - impl ParseMultiple - for Pair - { - fn parse_attr_with( - attr: &syn::Attribute, - parser: &P, - ) -> syn::Result { - Ok(if let Ok(left) = L::parse_attr_with(attr, parser) { - Pair { - left, - right: R::default(), - } - } else { - Pair { - left: L::default(), - right: R::parse_attr_with(attr, parser)?, - } - }) - } - - fn merge_attrs( - prev: Spanning, - new: Spanning, - name: &syn::Ident, - ) -> syn::Result> { - let left = L::merge_attrs( - Spanning::new(prev.item.left, prev.span), - Spanning::new(new.item.left, new.span), - name, - )? - .into_inner(); - - let right = R::merge_attrs( - Spanning::new(prev.item.right, prev.span), - Spanning::new(new.item.right, new.span), - name, - )? - .into_inner(); - - let span = prev.span.join(new.span).unwrap_or(prev.span); - - Ok(Spanning::new(Pair { left, right }, span)) - } - } - } - #[cfg(any( feature = "as_ref", - feature = "into", feature = "from", + feature = "into", feature = "try_from" ))] mod empty { @@ -1798,6 +1682,7 @@ pub(crate) mod attr { /// ```rust,ignore /// #[] /// ``` + #[derive(Clone, Copy, Debug)] pub(crate) struct Empty; impl Parse for Empty { @@ -1855,6 +1740,7 @@ pub(crate) mod attr { /// ```rust,ignore /// #[(forward)] /// ``` + #[derive(Clone, Copy, Debug)] pub(crate) struct Forward; impl Parse for Forward { @@ -1983,6 +1869,7 @@ pub(crate) mod attr { /// #[(skip)] /// #[(ignore)] /// ``` + #[derive(Clone, Copy, Debug)] pub(crate) struct Skip(&'static str); impl Parse for Skip { diff --git a/tests/into.rs b/tests/into.rs index ae52218f..c77c81c2 100644 --- a/tests/into.rs +++ b/tests/into.rs @@ -1044,25 +1044,25 @@ mod multi_field { } } -mod field_attr { +mod with_fields { use super::*; - mod fields_only { + mod only { use super::*; - #[derive(Debug, Into, Clone, Copy)] + #[derive(Clone, Copy, Debug, Into)] struct Tuple(#[into] i32, f64, #[into] f64); - // Asserts that macro expansion doesn't generate impls not specified, - // by producing a trait implementations conflict error during compilation, - // if it does. - + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for (i32, f64, f64) { fn from(value: Tuple) -> Self { (value.0, value.1, value.2) } } + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for (i32, f64) { fn from(value: Tuple) -> Self { (value.0, value.2) @@ -1077,7 +1077,7 @@ mod field_attr { assert_eq!(3.0, foo.into()); } - #[derive(Debug, Into, Clone, Copy)] + #[derive(Clone, Copy, Debug, Into)] struct Struct { #[into] a: i32, @@ -1086,16 +1086,16 @@ mod field_attr { c: f64, } - // Asserts that macro expansion doesn't generate impls not specified, - // by producing a trait implementations conflict error during compilation, - // if it does. - + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for (i32, f64, f64) { fn from(value: Struct) -> Self { (value.a, value.b, value.c) } } + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for (i32, f64) { fn from(value: Struct) -> Self { (value.a, value.c) @@ -1117,23 +1117,23 @@ mod field_attr { mod types { use super::*; - #[derive(Debug, Into, Clone)] + #[derive(Clone, Debug, Into)] struct Tuple( #[into(Box, Cow<'_, str>)] String, f64, #[into(f32, f64)] f32, ); - // Asserts that macro expansion doesn't generate impls not specified, - // by producing a trait implementations conflict error during compilation, - // if it does. - + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for String { fn from(value: Tuple) -> Self { value.0 } } + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for (String, f64, f32) { fn from(value: Tuple) -> Self { (value.0, value.1, value.2) @@ -1150,7 +1150,7 @@ mod field_attr { assert_eq!(3.0f64, foo.into()); } - #[derive(Debug, Into, Clone)] + #[derive(Clone, Debug, Into)] struct Struct { #[into(Box, Cow<'_, str>)] a: String, @@ -1159,22 +1159,24 @@ mod field_attr { c: f32, } - // Asserts that macro expansion doesn't generate impls not specified, - // by producing a trait implementations conflict error during compilation, - // if it does. - + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for String { fn from(value: Struct) -> Self { value.a } } + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for (String, f64, f32) { fn from(value: Struct) -> Self { (value.a, value.b, value.c) } } + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl From for (Box, f32) { fn from(value: Struct) -> Self { (value.a.into(), value.c) @@ -1195,16 +1197,14 @@ mod field_attr { assert_eq!(3.0f64, foo.into()); } - mod ref_ { + mod r#ref { use super::*; #[derive(Debug, Into)] struct Tuple(#[into(ref)] String, f64, #[into(ref)] f64); - // Asserts that macro expansion doesn't generate impls not specified, - // by producing a trait implementations conflict error during compilation, - // if it does. - + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl<'a> From<&'a Tuple> for (&'a String, &'a f64, &'a f64) { fn from(value: &'a Tuple) -> Self { (&value.0, &value.1, &value.2) @@ -1228,16 +1228,16 @@ mod field_attr { c: f64, } - // Asserts that macro expansion doesn't generate impls not specified, - // by producing a trait implementations conflict error during compilation, - // if it does. - + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl<'a> From<&'a Struct> for (&'a String, &'a f64, &'a f64) { fn from(value: &'a Struct) -> Self { (&value.a, &value.b, &value.c) } } + // Asserts that macro expansion doesn't generate this impl, by producing a trait + // implementations conflict error during compilation, if it does. impl<'a> From<&'a Struct> for (&'a String, &'a f64) { fn from(value: &'a Struct) -> Self { (&value.a, &value.c) @@ -1343,11 +1343,11 @@ mod field_attr { assert_eq!( &mut Transmuted(1), - <&mut Transmuted>::from(&mut foo) + <&mut Transmuted>::from(&mut foo), ); assert_eq!( &mut Wrapped(2), - <&mut Wrapped>::from(&mut foo) + <&mut Wrapped>::from(&mut foo), ); } @@ -1368,11 +1368,11 @@ mod field_attr { assert_eq!( &mut Transmuted(1), - <&mut Transmuted>::from(&mut foo) + <&mut Transmuted>::from(&mut foo), ); assert_eq!( &mut Wrapped(2), - <&mut Wrapped>::from(&mut foo) + <&mut Wrapped>::from(&mut foo), ); } } @@ -1381,7 +1381,7 @@ mod field_attr { } } - mod both { + mod mixed { use super::*; #[derive(Debug, Into)] From d17be4705d191c1abd21b14acb72204418d4403a Mon Sep 17 00:00:00 2001 From: tyranron Date: Thu, 12 Oct 2023 14:05:24 +0300 Subject: [PATCH 44/44] Bikeshed CHANGELOG --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c2a80c2..89ac783c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,9 +35,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). and ignores field type itself. - The `Into` derive now uses `#[into()]` instead of `#[into(types())]` and ignores field type itself. -- The `Into` derive now generates separate impls for each field with the `#[into(...)]` - attribute applied to it. - ([#291](https://github.com/JelteF/derive_more/pull/291)) +- The `Into` derive now generates separate impls for each field whenever the `#[into(...)]` + attribute is applied to it. ([#291](https://github.com/JelteF/derive_more/pull/291)) - Importing a derive macro now also import its corresponding trait. - The `Error` derive is updated with changes to the `error_generic_member_access` unstable feature for nightly users. ([#200](https://github.com/JelteF/derive_more/pull/200),