From fdf7fe18067a92ee8d9afc764c5aab8a46dc66d9 Mon Sep 17 00:00:00 2001 From: Ilya Salauyeu <47687266+ilslv@users.noreply.github.com> Date: Sat, 15 Jul 2023 12:15:33 +0200 Subject: [PATCH] Redesign `Into` derive macro (#248) ## Synopsis This PR is a part of replacing all attributes having `syn::Meta` syntax to custom parsing. ## Solution Replace `#[into(types(i32, "&str"))]` with `#[from(i32, &str)]` and add support for deriving multi-field structs and enum variants with `#[from((), (), ...)]`. Co-authored-by: tyranron --- CHANGELOG.md | 2 + Cargo.toml | 2 +- impl/doc/into.md | 187 +-- impl/src/from.rs | 121 +- impl/src/into.rs | 509 ++++++-- impl/src/lib.rs | 7 +- impl/src/parsing.rs | 12 +- impl/src/utils.rs | 180 ++- tests/compile_fail/into/enum.rs | 6 + tests/compile_fail/into/enum.stderr | 5 + .../into/legacy_complex_attribute.rs | 5 + .../into/legacy_complex_attribute.stderr | 5 + .../into/legacy_types_attribute.rs | 5 + .../into/legacy_types_attribute.stderr | 5 + .../into/mixed_regular_and_wrapped_types.rs | 7 + .../mixed_regular_and_wrapped_types.stderr | 5 + tests/compile_fail/into/tuple_no_parens.rs | 8 + .../compile_fail/into/tuple_no_parens.stderr | 5 + tests/compile_fail/into/tuple_too_long.rs | 8 + tests/compile_fail/into/tuple_too_long.stderr | 5 + tests/compile_fail/into/tuple_too_short.rs | 8 + .../compile_fail/into/tuple_too_short.stderr | 5 + tests/compile_fail/into/union.rs | 6 + tests/compile_fail/into/union.stderr | 5 + tests/into.rs | 1137 +++++++++++++++-- 25 files changed, 1816 insertions(+), 434 deletions(-) create mode 100644 tests/compile_fail/into/enum.rs create mode 100644 tests/compile_fail/into/enum.stderr create mode 100644 tests/compile_fail/into/legacy_complex_attribute.rs create mode 100644 tests/compile_fail/into/legacy_complex_attribute.stderr create mode 100644 tests/compile_fail/into/legacy_types_attribute.rs create mode 100644 tests/compile_fail/into/legacy_types_attribute.stderr create mode 100644 tests/compile_fail/into/mixed_regular_and_wrapped_types.rs create mode 100644 tests/compile_fail/into/mixed_regular_and_wrapped_types.stderr create mode 100644 tests/compile_fail/into/tuple_no_parens.rs create mode 100644 tests/compile_fail/into/tuple_no_parens.stderr create mode 100644 tests/compile_fail/into/tuple_too_long.rs create mode 100644 tests/compile_fail/into/tuple_too_long.stderr create mode 100644 tests/compile_fail/into/tuple_too_short.rs create mode 100644 tests/compile_fail/into/tuple_too_short.stderr create mode 100644 tests/compile_fail/into/union.rs create mode 100644 tests/compile_fail/into/union.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 10ca0695..1105d765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). practice. - The `From` derive now uses `#[from()]` instead of `#[from(types())]` and ignores field type itself. +- The `Into` derive now uses `#[into()]` instead of `#[into(types())]` + and ignores field type itself. ### Added diff --git a/Cargo.toml b/Cargo.toml index deede6e6..32999fb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -230,7 +230,7 @@ required-features = ["try_unwrap"] [[test]] name = "compile_fail" path = "tests/compile_fail/mod.rs" -required-features = ["debug", "display", "from"] +required-features = ["debug", "display", "from", "into"] [[test]] name = "no_std" diff --git a/impl/doc/into.md b/impl/doc/into.md index 16d0c2b8..0ca676d9 100644 --- a/impl/doc/into.md +++ b/impl/doc/into.md @@ -1,166 +1,119 @@ # What `#[derive(Into)]` generates -This derive creates the the exact opposite of [`#[derive(From)]`](crate::From). +This derive creates the exact opposite of `#[derive(From)]`. Instead of allowing you to create a new instance of the struct from the values -it should contain, it allows you to extract the values from the struct. -One thing to note is that this derive doesn't actually generate an -implementation for the `Into` trait. -Instead it derives `From` for the values contained in the struct and thus has an -indirect implementation of `Into` as recommended by the -[docs](https://doc.rust-lang.org/core/convert/trait.Into.html). +it should contain, it allows you to extract the values from the struct. One +thing to note is that this derive doesn't actually generate an implementation +for the `Into` trait. Instead, it derives `From` for the values contained in +the struct and thus has an indirect implementation of `Into` as +[recommended by the docs][1]. -## Example usage +## Structs + +For structs with a single field you can call `.into()` to extract the inner type. ```rust # use derive_more::Into; # -// Allow converting into i32 -#[derive(Into, PartialEq)] -struct MyInt(i32); - -// Additionally convert refs to the inner type refs -#[derive(Into, PartialEq)] -#[into(owned, ref, ref_mut)] -struct MyInt64(i64); - -// Specify additional conversions -#[derive(Into, PartialEq)] -#[into(types(i16, i32))] -struct MyInt8(i8); - -// Even for ref types -#[derive(Into, PartialEq)] -#[into(owned, ref(types(i64)))] -struct MyInt64Wrapped(MyInt64); - -assert!(i32::from(MyInt(2)) == 2i32); -assert!(i64::from(MyInt64(6)) == 6i64); -assert!(<&i64>::from(&MyInt64(6)) == &6i64); -assert!(<&mut i64>::from(&mut MyInt64(6)) == &mut 6i64); -assert!(i8::from(MyInt8(7)) == 7i8); -assert!(i16::from(MyInt8(7)) == 7i16); -assert!(i32::from(MyInt8(7)) == 7i32); -assert!(MyInt64::from(MyInt64Wrapped(MyInt64(1))) == MyInt64(1)); -assert!(<&MyInt64>::from(&MyInt64Wrapped(MyInt64(1))) == &MyInt64(1)); -assert!(<&i64>::from(&MyInt64Wrapped(MyInt64(1))) == &1i64); -``` - - - +#[derive(Debug, Into, PartialEq)] +struct Int(i32); -## Tuple structs +assert_eq!(2, Int(2).into()); +``` -When deriving `Into` for a tuple struct with a single field (i.e. a newtype) like this: +For structs having multiple fields, `.into()` extracts a tuple containing the +desired content for each field. ```rust # use derive_more::Into; # -#[derive(Into)] -struct MyInt(i32); -``` - -Code like this will be generated: +#[derive(Debug, Into, PartialEq)] +struct Point(i32, i32); -```rust -# struct MyInt(i32); -impl ::core::convert::From for (i32) { - fn from(original: MyInt) -> (i32) { - (original.0) - } -} +assert_eq!((1, 2), Point(1, 2).into()); ``` -The behaviour is a bit different when deriving for a struct with multiple -fields, since it returns a tuple. For instance when deriving for a tuple struct -with two fields like this: +To specify concrete types for deriving conversions into, use `#[into()]`. ```rust +# use std::borrow::Cow; +# # use derive_more::Into; # -#[derive(Into)] -struct MyInts(i32, i32); -``` +#[derive(Debug, Into, PartialEq)] +#[into(Cow<'static, str>, String)] +struct Str(Cow<'static, str>); -Code like this will be generated: +assert_eq!("String".to_owned(), String::from(Str("String".into()))); +assert_eq!(Cow::Borrowed("Cow"), >::from(Str("Cow".into()))); -```rust -# struct MyInts(i32, i32); -impl ::core::convert::From for (i32, i32) { - fn from(original: MyInts) -> (i32, i32) { - (original.0, original.1) - } +#[derive(Debug, Into, PartialEq)] +#[into((i64, i64), (i32, i32))] +struct Point { + x: i32, + y: i32, } -``` - - +assert_eq!((1_i64, 2_i64), Point { x: 1_i32, y: 2_i32 }.into()); +assert_eq!((3_i32, 4_i32), Point { x: 3_i32, y: 4_i32 }.into()); +``` -## Regular structs - -For regular structs almost the same code is generated as for tuple structs -except in the way the field values are assigned to the new struct. -When deriving for a regular struct with a single field like this: +In addition to converting to owned types, this macro supports deriving into +reference (mutable or not) via `#[into(ref(...))]`/`#[into(ref_mut(...))]`. ```rust # use derive_more::Into; # -#[derive(Into)] -struct Point1D { - x: i32, -} -``` +#[derive(Debug, Into, PartialEq)] +#[into(owned, ref(i32), ref_mut)] +struct Int(i32); -Code like this will be generated: - -```rust -# struct Point1D { -# x: i32, -# } -impl ::core::convert::From for (i32) { - fn from(original: Point1D) -> (i32) { - (original.x) - } -} +assert_eq!(2, Int(2).into()); +assert_eq!(&2, <&i32>::from(&Int(2))); +assert_eq!(&mut 2, <&mut i32>::from(&mut Int(2))); ``` -The behaviour is again a bit different when deriving for a struct with multiple -fields, because this also returns a tuple. For instance when deriving for a -tuple struct with two fields like this: +In case there are fields, that shouldn't be included in the conversion, use the +`#[into(skip)]` attribute. ```rust +# use std::marker::PhantomData; +# # use derive_more::Into; # -#[derive(Into)] -struct Point2D { - x: i32, - y: i32, +# struct Gram; +# +#[derive(Debug, Into, PartialEq)] +#[into(i32, i64, i128)] +struct Mass { + value: i32, + #[into(skip)] + _unit: PhantomData, } -``` - -Code like this will be generated: - -```rust -# struct Point2D { -# x: i32, -# y: i32, +assert_eq!(5, Mass::::new(5).into()); +assert_eq!(5_i64, Mass::::new(5).into()); +assert_eq!(5_i128, Mass::::new(5).into()); +# +# impl Mass { +# fn new(value: i32) -> Self { +# Self { +# value, +# _unit: PhantomData, +# } +# } # } -impl ::core::convert::From for (i32, i32) { - fn from(original: Point2D) -> (i32, i32) { - (original.x, original.y) - } -} ``` +## Enums + +Deriving `Into` for enums is not supported as it would not always be successful, +so `TryInto` should be used instead. -## Enums -Deriving `Into` for enums is not supported as it would not always be successful. -This is what the currently unstable -[`TryInto`](https://doc.rust-lang.org/core/convert/trait.TryInto.html) should be -used for, which is currently not supported by this library. +[1]: https://doc.rust-lang.org/core/convert/trait.Into.html diff --git a/impl/src/from.rs b/impl/src/from.rs index 4397f946..cefab9ba 100644 --- a/impl/src/from.rs +++ b/impl/src/from.rs @@ -1,6 +1,6 @@ //! Implementation of a [`From`] derive macro. -use std::{cmp, iter}; +use std::iter; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote, ToTokens as _, TokenStreamExt as _}; @@ -12,7 +12,10 @@ use syn::{ token, Ident, }; -use crate::{parsing::Type, utils::polyfill}; +use crate::{ + parsing::Type, + utils::{polyfill, Either}, +}; /// Expands a [`From`] derive macro. pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { @@ -250,6 +253,8 @@ struct Expansion<'a> { impl<'a> Expansion<'a> { /// Expands [`From`] implementations for a struct or an enum variant. fn expand(&self) -> syn::Result { + use crate::utils::FieldsExt as _; + let ident = self.ident; let field_tys = self.fields.iter().map(|f| &f.ty).collect::>(); let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); @@ -261,7 +266,7 @@ impl<'a> Expansion<'a> { tys.iter().map(|ty| { let variant = self.variant.iter(); - let mut from_tys = self.validate_type(ty)?; + let mut from_tys = self.fields.validate_type(ty)?; let init = self.expand_fields(|ident, ty, index| { let ident = ident.into_iter(); let index = index.into_iter(); @@ -408,116 +413,6 @@ impl<'a> Expansion<'a> { }) .unwrap_or_default() } - - /// Validates the provided [`Type`] against [`syn::Fields`]. - fn validate_type<'t>( - &self, - ty: &'t Type, - ) -> syn::Result> { - match ty { - Type::Tuple { items, .. } if self.fields.len() > 1 => { - match self.fields.len().cmp(&items.len()) { - cmp::Ordering::Greater => { - return Err(syn::Error::new( - ty.span(), - format!( - "wrong tuple length: expected {}, found {}. \ - Consider adding {} more type{}: `({})`", - self.fields.len(), - items.len(), - self.fields.len() - items.len(), - if self.fields.len() - items.len() > 1 { - "s" - } else { - "" - }, - items - .iter() - .map(|item| item.to_string()) - .chain( - (0..(self.fields.len() - items.len())) - .map(|_| "_".to_string()) - ) - .collect::>() - .join(", "), - ), - )); - } - cmp::Ordering::Less => { - return Err(syn::Error::new( - ty.span(), - format!( - "wrong tuple length: expected {}, found {}. \ - Consider removing last {} type{}: `({})`", - self.fields.len(), - items.len(), - items.len() - self.fields.len(), - if items.len() - self.fields.len() > 1 { - "s" - } else { - "" - }, - items - .iter() - .take(self.fields.len()) - .map(|item| item.to_string()) - .collect::>() - .join(", "), - ), - )); - } - cmp::Ordering::Equal => {} - } - } - Type::Other(other) if self.fields.len() > 1 => { - if self.fields.len() > 1 { - return Err(syn::Error::new( - other.span(), - format!( - "expected tuple: `({}, {})`", - other, - (0..(self.fields.len() - 1)) - .map(|_| "_") - .collect::>() - .join(", "), - ), - )); - } - } - Type::Tuple { .. } | Type::Other(_) => {} - } - Ok(match ty { - Type::Tuple { items, .. } => Either::Left(items.iter()), - Type::Other(other) => Either::Right(iter::once(other)), - }) - } -} - -/// Either [`Left`] or [`Right`]. -/// -/// [`Left`]: Either::Left -/// [`Right`]: Either::Right -enum Either { - /// Left variant. - Left(L), - - /// Right variant. - Right(R), -} - -impl Iterator for Either -where - L: Iterator, - R: Iterator, -{ - type Item = T; - - fn next(&mut self) -> Option { - match self { - Either::Left(left) => left.next(), - Either::Right(right) => right.next(), - } - } } /// Constructs a [`syn::Error`] for legacy syntax: `#[from(types(i32, "&str"))]`. diff --git a/impl/src/into.rs b/impl/src/into.rs index 20e897e1..91e23bf4 100644 --- a/impl/src/into.rs +++ b/impl/src/into.rs @@ -1,85 +1,448 @@ -use std::iter; - -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{parse::Result, DeriveInput}; - -use crate::utils::{add_extra_generic_param, AttrParams, MultiFieldData, State}; - -/// Provides the hook to expand `#[derive(Into)]` into an implementation of `Into` -pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result { - let state = State::with_attr_params( - input, - trait_name, - quote! { ::core::convert }, - trait_name.to_lowercase(), - AttrParams { - enum_: vec!["ignore", "owned", "ref", "ref_mut"], - variant: vec!["ignore", "owned", "ref", "ref_mut"], - struct_: vec!["ignore", "owned", "ref", "ref_mut", "types"], - field: vec!["ignore"], - }, - )?; - let MultiFieldData { - variant_info, - field_types, - field_idents, - input_type, - .. - } = state.enabled_fields_data(); - - let mut tokens = TokenStream::new(); - - for ref_type in variant_info.ref_types() { - let reference = ref_type.reference(); - let lifetime = ref_type.lifetime(); - let reference_with_lifetime = ref_type.reference_with_lifetime(); - - let generics_impl; - let (_, ty_generics, where_clause) = input.generics.split_for_impl(); - let (impl_generics, _, _) = if ref_type.is_ref() { - generics_impl = add_extra_generic_param(&input.generics, lifetime); - generics_impl.split_for_impl() +//! Implementation of an [`Into`] derive macro. + +use std::{borrow::Cow, iter}; + +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens as _}; +use syn::{ + ext::IdentExt as _, + parse::{discouraged::Speculative as _, Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned as _, + token, Ident, +}; + +use crate::{ + parsing::Type, + utils::{polyfill, Either, FieldsExt as _}, +}; + +/// Expands an [`Into`] derive macro. +pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { + let data = match &input.data { + syn::Data::Struct(data) => Ok(data), + syn::Data::Enum(e) => Err(syn::Error::new( + e.enum_token.span(), + "`Into` cannot be derived for enums", + )), + syn::Data::Union(u) => Err(syn::Error::new( + u.union_token.span(), + "`Into` cannot be derived for unions", + )), + }?; + + let attr = StructAttribute::parse_attrs(&input.attrs, &data.fields)? + .unwrap_or_else(|| StructAttribute { + owned: Some(Punctuated::new()), + r#ref: None, + ref_mut: None, + }); + let ident = &input.ident; + let fields = data + .fields + .iter() + .enumerate() + .filter_map(|(i, f)| match SkipFieldAttribute::parse_attrs(&f.attrs) { + Ok(None) => 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)), + }) + .collect::>>()?; + 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| { + 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 { - input.generics.split_for_impl() + Cow::Borrowed(&input.generics) }; - let additional_types = variant_info.additional_types(ref_type); - for explicit_type in iter::once(None).chain(additional_types.iter().map(Some)) { - let into_types: Vec<_> = field_types - .iter() - .map(|field_type| { - // No, `.unwrap_or()` won't work here, because we use different types. - if let Some(type_) = explicit_type { - quote! { #reference_with_lifetime #type_ } - } else { - quote! { #reference_with_lifetime #field_type } + 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 #ident #ty_gens> + for ( #( #r #lf #m #tys ),* ) #where_clause + { + #[inline] + fn from(value: #r #lf #m #ident #ty_gens) -> Self { + (#( + <#r #m #tys as ::core::convert::From<_>>::from( + #r #m value. #fields_idents + ) + ),*) + } } }) - .collect(); + }), + ) + }; - let initializers = field_idents.iter().map(|field_ident| { - if let Some(type_) = explicit_type { - quote! { <#reference #type_>::from(#reference original.#field_ident) } - } else { - quote! { #reference original.#field_ident } + [ + expand(attr.owned, false, false), + expand(attr.r#ref, true, false), + expand(attr.ref_mut, true, true), + ] + .into_iter() + .flatten() + .collect() +} + +/// Representation of an [`Into`] derive macro struct container attribute. +/// +/// ```rust,ignore +/// #[into()] +/// #[into(owned(), ref(), ref_mut())] +/// ``` +#[derive(Debug, Default)] +struct StructAttribute { + /// [`Type`]s wrapped into `owned(...)` or simply `#[into(...)]`. + owned: Option>, + + /// [`Type`]s wrapped into `ref(...)`. + r#ref: Option>, + + /// [`Type`]s wrapped into `ref_mut(...)`. + ref_mut: Option>, +} + +impl StructAttribute { + /// Parses a [`StructAttribute`] from the provided [`syn::Attribute`]s. + fn parse_attrs( + 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() + .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 = + attr.parse_args_with(infer(|stream| Self::parse(stream, 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); + + Ok(attrs) + }) + } + + /// Parses a single [`StructAttribute`]. + fn parse(content: ParseStream<'_>, fields: &syn::Fields) -> syn::Result { + check_legacy_syntax(content, fields)?; + + let mut out = Self::default(); + + let parse_inner = |ahead, types: &mut Option<_>| { + content.advance_to(&ahead); + + let types = types.get_or_insert_with(Punctuated::new); + if content.peek(token::Paren) { + let inner; + syn::parenthesized!(inner in content); + + types.extend( + inner + .parse_terminated(Type::parse, token::Comma)? + .into_pairs(), + ); + } + if content.peek(token::Comma) { + let comma = content.parse::()?; + if !types.empty_or_trailing() { + types.push_punct(comma); + } + } + + Ok(()) + }; + + let mut has_wrapped_type = false; + let mut top_level_type = None; + + while !content.is_empty() { + let ahead = content.fork(); + let res = if ahead.peek(Ident::peek_any) { + ahead.call(Ident::parse_any).map(Into::into) + } else { + ahead.parse::() + }; + match res { + Ok(p) if p.is_ident("owned") => { + has_wrapped_type = true; + parse_inner(ahead, &mut out.owned)?; + } + Ok(p) if p.is_ident("ref") => { + has_wrapped_type = true; + parse_inner(ahead, &mut out.r#ref)?; } - }); - - (quote! { - #[automatically_derived] - impl #impl_generics - ::core::convert::From<#reference_with_lifetime #input_type #ty_generics> for - (#(#into_types),*) - #where_clause - { - #[inline] - fn from(original: #reference_with_lifetime #input_type #ty_generics) -> Self { - (#(#initializers),*) + Ok(p) if p.is_ident("ref_mut") => { + has_wrapped_type = true; + parse_inner(ahead, &mut out.ref_mut)?; + } + _ => { + let ty = content.parse::()?; + let _ = top_level_type.get_or_insert_with(|| ty.clone()); + out.owned.get_or_insert_with(Punctuated::new).push_value(ty); + + if content.peek(token::Comma) { + out.owned + .get_or_insert_with(Punctuated::new) + .push_punct(content.parse::()?) } } - }).to_tokens(&mut tokens); + } + } + + if let Some(ty) = top_level_type.filter(|_| has_wrapped_type) { + Err(syn::Error::new( + ty.span(), + format!( + "mixing regular types with wrapped into \ + `owned`/`ref`/`ref_mut` is not allowed, try wrapping \ + this type into `owned({ty}), ref({ty}), ref_mut({ty})`", + ty = ty.into_token_stream(), + ), + )) + } else { + Ok(out) + } + } +} + +/// `#[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()), + )), } } - Ok(tokens) +} + +/// [`Error`]ors for legacy syntax: `#[into(types(i32, "&str"))]`. +fn check_legacy_syntax( + tokens: ParseStream<'_>, + fields: &syn::Fields, +) -> syn::Result<()> { + let span = tokens.span(); + let tokens = tokens.fork(); + + let map_ty = |s: String| { + if fields.len() > 1 { + format!( + "({})", + (0..fields.len()) + .map(|_| s.as_str()) + .collect::>() + .join(", ") + ) + } else { + s + } + }; + let field = match fields.len() { + 0 => None, + 1 => Some( + fields + .iter() + .next() + .unwrap_or_else(|| unreachable!("fields.len() == 1")) + .ty + .to_token_stream() + .to_string(), + ), + _ => Some(format!( + "({})", + fields + .iter() + .map(|f| f.ty.to_token_stream().to_string()) + .collect::>() + .join(", ") + )), + }; + + let Ok(metas) = tokens.parse_terminated(polyfill::Meta::parse, token::Comma) else { + return Ok(()); + }; + + let parse_list = |list: polyfill::MetaList, attrs: &mut Option>| { + if !list.path.is_ident("types") { + return None; + } + for meta in list + .parse_args_with(Punctuated::<_, token::Comma>::parse_terminated) + .ok()? + { + attrs.get_or_insert_with(Vec::new).push(match meta { + polyfill::NestedMeta::Lit(syn::Lit::Str(str)) => str.value(), + polyfill::NestedMeta::Meta(polyfill::Meta::Path(path)) => { + path.into_token_stream().to_string() + } + _ => return None, + }) + } + Some(()) + }; + + let Some((top_level, owned, ref_, ref_mut)) = metas + .into_iter() + .try_fold( + (None, None, None, None), + |(mut top_level, mut owned, mut ref_, mut ref_mut), meta| { + let is = |name| { + matches!(&meta, polyfill::Meta::Path(p) if p.is_ident(name)) + || matches!(&meta, polyfill::Meta::List(list) if list.path.is_ident(name)) + }; + let parse_inner = |meta, attrs: &mut Option<_>| { + match meta { + polyfill::Meta::Path(_) => { + let _ = attrs.get_or_insert_with(Vec::new); + Some(()) + } + polyfill::Meta::List(list) => { + if let polyfill::NestedMeta::Meta(polyfill::Meta::List(list)) = list + .parse_args_with(Punctuated::<_, token::Comma>::parse_terminated) + .ok()? + .pop()? + .into_value() + { + parse_list(list, attrs) + } else { + None + } + } + } + }; + + match meta { + meta if is("owned") => parse_inner(meta, &mut owned), + meta if is("ref") => parse_inner(meta, &mut ref_), + meta if is("ref_mut") => parse_inner(meta, &mut ref_mut), + polyfill::Meta::List(list) => parse_list(list, &mut top_level), + _ => None, + } + .map(|_| (top_level, owned, ref_, ref_mut)) + }, + ) + .filter(|(top_level, owned, ref_, ref_mut)| { + [top_level, owned, ref_, ref_mut] + .into_iter() + .any(|l| l.as_ref().map_or(false, |l| !l.is_empty())) + }) + else { + return Ok(()); + }; + + if [&owned, &ref_, &ref_mut].into_iter().any(Option::is_some) { + let format = |list: Option>, name: &str| match list { + Some(l) + if top_level.as_ref().map_or(true, Vec::is_empty) && l.is_empty() => + { + Some(name.to_owned()) + } + Some(l) => Some(format!( + "{}({})", + name, + l.into_iter() + .chain(top_level.clone().into_iter().flatten()) + .map(map_ty) + .chain(field.clone()) + .collect::>() + .join(", "), + )), + None => None, + }; + let format = [ + format(owned, "owned"), + format(ref_, "ref"), + format(ref_mut, "ref_mut"), + ] + .into_iter() + .flatten() + .collect::>() + .join(", "); + + Err(syn::Error::new( + span, + format!("legacy syntax, use `{format}` instead"), + )) + } else { + Err(syn::Error::new( + span, + format!( + "legacy syntax, remove `types` and use `{}` instead", + top_level.unwrap_or_else(|| unreachable!()).join(", "), + ), + )) + } } diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 751fba5f..8c028fe6 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -57,7 +57,12 @@ mod mul_helpers; mod mul_like; #[cfg(feature = "not")] mod not_like; -#[cfg(any(feature = "debug", feature = "display", feature = "from"))] +#[cfg(any( + feature = "debug", + feature = "display", + feature = "from", + feature = "into", +))] pub(crate) mod parsing; #[cfg(feature = "sum")] mod sum_like; diff --git a/impl/src/parsing.rs b/impl/src/parsing.rs index af571edc..42b66376 100644 --- a/impl/src/parsing.rs +++ b/impl/src/parsing.rs @@ -16,7 +16,7 @@ use syn::{ }; /// [`syn::Type`] [`Parse`]ing polyfill. -#[derive(Debug)] +#[derive(Clone, Debug)] pub(crate) enum Type { /// [`syn::Type::Tuple`] [`Parse`]ing polyfill. Tuple { @@ -28,6 +28,16 @@ pub(crate) enum Type { Other(TokenStream), } +impl Type { + /// Creates a [`Type::Tuple`] from the provided [`Iterator`] of [`TokenStream`]s. + pub(crate) fn tuple(items: impl IntoIterator) -> Self { + Self::Tuple { + paren: token::Paren::default(), + items: items.into_iter().map(ToTokens::into_token_stream).collect(), + } + } +} + impl Parse for Type { fn parse(input: ParseStream) -> Result { input.step(|c| { diff --git a/impl/src/utils.rs b/impl/src/utils.rs index f1922051..17289115 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -13,6 +13,9 @@ use syn::{ TypeParamBound, Variant, WhereClause, }; +#[cfg(any(feature = "from", feature = "into"))] +pub(crate) use self::{either::Either, fields_ext::FieldsExt}; + #[derive(Clone, Copy, Default)] pub struct DeterministicState; @@ -1145,7 +1148,7 @@ pub(crate) mod polyfill { } impl PathOrKeyword { - pub(super) fn is_ident(&self, ident: &I) -> bool + pub(crate) fn is_ident(&self, ident: &I) -> bool where syn::Ident: PartialEq, { @@ -1174,8 +1177,8 @@ pub(crate) mod polyfill { #[derive(Clone)] pub(crate) struct MetaList { - pub(super) path: PathOrKeyword, - pub(super) tokens: TokenStream, + pub(crate) path: PathOrKeyword, + pub(crate) tokens: TokenStream, } impl Parse for MetaList { @@ -1317,11 +1320,6 @@ impl FullMetaInfo { } ref_types } - - #[cfg(any(feature = "from", feature = "into"))] - pub fn additional_types(&self, ref_type: RefType) -> HashSet { - self.info.types.get(&ref_type).cloned().unwrap_or_default() - } } pub fn get_if_type_parameter_used_in_type( @@ -1378,3 +1376,169 @@ pub fn is_type_parameter_used_in_type( _ => false, } } + +#[cfg(any(feature = "from", feature = "into"))] +mod either { + use proc_macro2::TokenStream; + use quote::ToTokens; + + /// Either [`Left`] or [`Right`]. + /// + /// [`Left`]: Either::Left + /// [`Right`]: Either::Right + pub(crate) enum Either { + /// Left variant. + Left(L), + + /// Right variant. + Right(R), + } + + impl Iterator for Either + where + L: Iterator, + R: Iterator, + { + type Item = T; + + fn next(&mut self) -> Option { + match self { + Either::Left(left) => left.next(), + Either::Right(right) => right.next(), + } + } + } + + impl ToTokens for Either + where + L: ToTokens, + R: ToTokens, + { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Either::Left(l) => l.to_tokens(tokens), + Either::Right(r) => r.to_tokens(tokens), + } + } + } +} + +#[cfg(any(feature = "from", feature = "into"))] +mod fields_ext { + use std::{cmp, iter}; + + use proc_macro2::TokenStream; + use syn::{punctuated, spanned::Spanned as _}; + + use crate::parsing; + + use super::Either; + + /// Abstraction over `.len()` method to use it on type parameters. + pub(crate) trait Len { + /// Returns number of fields. + fn len(&self) -> usize; + } + + impl Len for syn::Fields { + fn len(&self) -> usize { + self.len() + } + } + + impl Len for Vec { + fn len(&self) -> usize { + self.len() + } + } + + /// [`syn::Fields`] extension. + pub(crate) trait FieldsExt: Len { + /// Validates the provided [`parsing::Type`] against these [`syn::Fields`]. + fn validate_type<'t>( + &self, + ty: &'t parsing::Type, + ) -> syn::Result< + Either, iter::Once<&'t TokenStream>>, + > { + match ty { + parsing::Type::Tuple { items, .. } if self.len() > 1 => { + match self.len().cmp(&items.len()) { + cmp::Ordering::Greater => { + return Err(syn::Error::new( + ty.span(), + format!( + "wrong tuple length: expected {}, found {}. \ + Consider adding {} more type{}: `({})`", + self.len(), + items.len(), + self.len() - items.len(), + if self.len() - items.len() > 1 { + "s" + } else { + "" + }, + items + .iter() + .map(|item| item.to_string()) + .chain( + (0..(self.len() - items.len())) + .map(|_| "_".to_string()) + ) + .collect::>() + .join(", "), + ), + )); + } + cmp::Ordering::Less => { + return Err(syn::Error::new( + ty.span(), + format!( + "wrong tuple length: expected {}, found {}. \ + Consider removing last {} type{}: `({})`", + self.len(), + items.len(), + items.len() - self.len(), + if items.len() - self.len() > 1 { + "s" + } else { + "" + }, + items + .iter() + .take(self.len()) + .map(ToString::to_string) + .collect::>() + .join(", "), + ), + )); + } + cmp::Ordering::Equal => {} + } + } + parsing::Type::Other(other) if self.len() > 1 => { + if self.len() > 1 { + return Err(syn::Error::new( + other.span(), + format!( + "expected tuple: `({}, {})`", + other, + (0..(self.len() - 1)) + .map(|_| "_") + .collect::>() + .join(", "), + ), + )); + } + } + parsing::Type::Tuple { .. } | parsing::Type::Other(_) => {} + } + Ok(match ty { + parsing::Type::Tuple { items, .. } => Either::Left(items.iter()), + parsing::Type::Other(other) => Either::Right(iter::once(other)), + }) + } + } + + impl FieldsExt for T {} +} diff --git a/tests/compile_fail/into/enum.rs b/tests/compile_fail/into/enum.rs new file mode 100644 index 00000000..33edf287 --- /dev/null +++ b/tests/compile_fail/into/enum.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::Into)] +enum Foo { + Foo(i32), +} + +fn main() {} diff --git a/tests/compile_fail/into/enum.stderr b/tests/compile_fail/into/enum.stderr new file mode 100644 index 00000000..74f15e71 --- /dev/null +++ b/tests/compile_fail/into/enum.stderr @@ -0,0 +1,5 @@ +error: `Into` cannot be derived for enums + --> tests/compile_fail/into/enum.rs:2:1 + | +2 | enum Foo { + | ^^^^ diff --git a/tests/compile_fail/into/legacy_complex_attribute.rs b/tests/compile_fail/into/legacy_complex_attribute.rs new file mode 100644 index 00000000..f58e7726 --- /dev/null +++ b/tests/compile_fail/into/legacy_complex_attribute.rs @@ -0,0 +1,5 @@ +#[derive(derive_more::Into)] +#[into(owned(types("Cow<'_ str>")), ref, ref_mut, types(i32, "&str"))] +struct Foo(String); + +fn main() {} diff --git a/tests/compile_fail/into/legacy_complex_attribute.stderr b/tests/compile_fail/into/legacy_complex_attribute.stderr new file mode 100644 index 00000000..36712240 --- /dev/null +++ b/tests/compile_fail/into/legacy_complex_attribute.stderr @@ -0,0 +1,5 @@ +error: legacy syntax, use `owned(Cow<'_ str>, i32, &str, String), ref(i32, &str, String), ref_mut(i32, &str, String)` instead + --> tests/compile_fail/into/legacy_complex_attribute.rs:2:8 + | +2 | #[into(owned(types("Cow<'_ str>")), ref, ref_mut, types(i32, "&str"))] + | ^^^^^ diff --git a/tests/compile_fail/into/legacy_types_attribute.rs b/tests/compile_fail/into/legacy_types_attribute.rs new file mode 100644 index 00000000..5b8a1ba8 --- /dev/null +++ b/tests/compile_fail/into/legacy_types_attribute.rs @@ -0,0 +1,5 @@ +#[derive(derive_more::Into)] +#[into(types(i32, "&str"))] +struct Foo(String); + +fn main() {} diff --git a/tests/compile_fail/into/legacy_types_attribute.stderr b/tests/compile_fail/into/legacy_types_attribute.stderr new file mode 100644 index 00000000..bfad0351 --- /dev/null +++ b/tests/compile_fail/into/legacy_types_attribute.stderr @@ -0,0 +1,5 @@ +error: legacy syntax, remove `types` and use `i32, &str` instead + --> tests/compile_fail/into/legacy_types_attribute.rs:2:8 + | +2 | #[into(types(i32, "&str"))] + | ^^^^^ diff --git a/tests/compile_fail/into/mixed_regular_and_wrapped_types.rs b/tests/compile_fail/into/mixed_regular_and_wrapped_types.rs new file mode 100644 index 00000000..43f81fb0 --- /dev/null +++ b/tests/compile_fail/into/mixed_regular_and_wrapped_types.rs @@ -0,0 +1,7 @@ +#[derive(derive_more::Into)] +#[into(owned, ref(i32), i32)] +struct Foo { + bar: i32, +} + +fn main() {} diff --git a/tests/compile_fail/into/mixed_regular_and_wrapped_types.stderr b/tests/compile_fail/into/mixed_regular_and_wrapped_types.stderr new file mode 100644 index 00000000..4c239577 --- /dev/null +++ b/tests/compile_fail/into/mixed_regular_and_wrapped_types.stderr @@ -0,0 +1,5 @@ +error: mixing regular types with wrapped into `owned`/`ref`/`ref_mut` is not allowed, try wrapping this type into `owned(i32), ref(i32), ref_mut(i32)` + --> tests/compile_fail/into/mixed_regular_and_wrapped_types.rs:2:25 + | +2 | #[into(owned, ref(i32), i32)] + | ^^^ diff --git a/tests/compile_fail/into/tuple_no_parens.rs b/tests/compile_fail/into/tuple_no_parens.rs new file mode 100644 index 00000000..7491e38d --- /dev/null +++ b/tests/compile_fail/into/tuple_no_parens.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::Into)] +#[into(i16, i16)] +struct Point { + x: i32, + y: i32, +} + +fn main() {} diff --git a/tests/compile_fail/into/tuple_no_parens.stderr b/tests/compile_fail/into/tuple_no_parens.stderr new file mode 100644 index 00000000..9be31ca3 --- /dev/null +++ b/tests/compile_fail/into/tuple_no_parens.stderr @@ -0,0 +1,5 @@ +error: expected tuple: `(i16, _)` + --> tests/compile_fail/into/tuple_no_parens.rs:2:8 + | +2 | #[into(i16, i16)] + | ^^^ diff --git a/tests/compile_fail/into/tuple_too_long.rs b/tests/compile_fail/into/tuple_too_long.rs new file mode 100644 index 00000000..cef55931 --- /dev/null +++ b/tests/compile_fail/into/tuple_too_long.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::Into)] +#[into((i16, i16, i16))] +struct Point { + x: i32, + y: i32, +} + +fn main() {} diff --git a/tests/compile_fail/into/tuple_too_long.stderr b/tests/compile_fail/into/tuple_too_long.stderr new file mode 100644 index 00000000..57033a5e --- /dev/null +++ b/tests/compile_fail/into/tuple_too_long.stderr @@ -0,0 +1,5 @@ +error: wrong tuple length: expected 2, found 3. Consider removing last 1 type: `(i16, i16)` + --> tests/compile_fail/into/tuple_too_long.rs:2:8 + | +2 | #[into((i16, i16, i16))] + | ^^^^^^^^^^^^^^^ diff --git a/tests/compile_fail/into/tuple_too_short.rs b/tests/compile_fail/into/tuple_too_short.rs new file mode 100644 index 00000000..89ee9246 --- /dev/null +++ b/tests/compile_fail/into/tuple_too_short.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::Into)] +#[into((i16,))] +struct Point { + x: i32, + y: i32, +} + +fn main() {} diff --git a/tests/compile_fail/into/tuple_too_short.stderr b/tests/compile_fail/into/tuple_too_short.stderr new file mode 100644 index 00000000..0e685453 --- /dev/null +++ b/tests/compile_fail/into/tuple_too_short.stderr @@ -0,0 +1,5 @@ +error: wrong tuple length: expected 2, found 1. Consider adding 1 more type: `(i16, _)` + --> tests/compile_fail/into/tuple_too_short.rs:2:8 + | +2 | #[into((i16,))] + | ^^^^^^ diff --git a/tests/compile_fail/into/union.rs b/tests/compile_fail/into/union.rs new file mode 100644 index 00000000..623989c0 --- /dev/null +++ b/tests/compile_fail/into/union.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::Into)] +pub union Foo { + bar: i32, +} + +fn main() {} diff --git a/tests/compile_fail/into/union.stderr b/tests/compile_fail/into/union.stderr new file mode 100644 index 00000000..f8db542e --- /dev/null +++ b/tests/compile_fail/into/union.stderr @@ -0,0 +1,5 @@ +error: `Into` cannot be derived for unions + --> tests/compile_fail/into/union.rs:2:5 + | +2 | pub union Foo { + | ^^^^^ diff --git a/tests/into.rs b/tests/into.rs index 7328d3a4..95799d2f 100644 --- a/tests/into.rs +++ b/tests/into.rs @@ -5,144 +5,1041 @@ extern crate alloc; #[cfg(not(feature = "std"))] -use alloc::{borrow::Cow, string::String}; +use alloc::{ + borrow::Cow, + borrow::ToOwned, + boxed::Box, + string::{String, ToString}, +}; +use core::mem; #[cfg(feature = "std")] use std::borrow::Cow; use derive_more::Into; +use static_assertions::assert_not_impl_any; -#[derive(Into)] -#[into(owned, ref, ref_mut)] -struct EmptyTuple(); - -#[derive(Into)] -#[into(owned, ref, ref_mut)] -struct EmptyStruct {} - -#[derive(Into)] -#[into(owned, ref, ref_mut)] -struct EmptyUnit; - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[derive(Into)] -#[into(owned(types(i64, i128)), ref, ref_mut)] -struct MyInt(i32); - -#[test] -fn explicit_types_struct_owned_only() { - assert_eq!(i32::from(MyInt(42)), 42i32); - assert_eq!(<&i32>::from(&MyInt(42)), &42i32); - assert_eq!(<&mut i32>::from(&mut MyInt(42)), &mut 42i32); - assert_eq!(i64::from(MyInt(42)), 42i64); - assert_eq!(i128::from(MyInt(42)), 42i128); +/// Nasty [`mem::transmute()`] that works in generic contexts +/// by [`mem::forget`]ing stuff. +/// +/// It's OK for tests! +unsafe fn transmute(from: From) -> To { + let to = unsafe { mem::transmute_copy(&from) }; + mem::forget(from); + to } -#[derive(Into)] -#[into(owned, ref, ref_mut)] -struct MyInts(i32, i32); +#[derive(Debug, PartialEq)] +#[repr(transparent)] +struct Wrapped(T); -#[derive(Into)] -#[into(owned, ref, ref_mut)] -struct Point1D { - x: i32, -} +#[derive(Debug, PartialEq)] +#[repr(transparent)] +struct Transmuted(T); -#[derive(Debug, Eq, PartialEq)] -#[derive(Into)] -#[into(owned, ref, ref_mut)] -struct Point2D { - x: i32, - y: i32, +impl From> for Transmuted { + fn from(from: Wrapped) -> Self { + // SAFETY: repr(transparent) + unsafe { transmute(from) } + } } -#[derive(Into)] -#[into(owned, ref, ref_mut)] -struct Point2DWithIgnored { - x: i32, - y: i32, - #[into(ignore)] - useless: bool, +impl From<&Wrapped> for &Transmuted { + fn from(from: &Wrapped) -> Self { + // SAFETY: repr(transparent) + unsafe { transmute(from) } + } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[derive(Into)] -#[into(owned(types(i64, i128)), ref, ref_mut, types(i32))] -struct MyIntExplicit(MyInt); - -#[test] -fn explicit_types_struct_all() { - let mut input = MyIntExplicit(MyInt(42)); - assert_eq!(MyInt::from(input), MyInt(42)); - assert_eq!(<&MyInt>::from(&input), &MyInt(42)); - assert_eq!(<&mut MyInt>::from(&mut input), &mut MyInt(42)); - assert_eq!(i32::from(input), 42i32); - assert_eq!(<&i32>::from(&input), &42i32); - assert_eq!(<&mut i32>::from(&mut input), &mut 42i32); - assert_eq!(i64::from(input), 42i64); - assert_eq!(i128::from(input), 42i128); +impl From<&mut Wrapped> for &mut Transmuted { + fn from(from: &mut Wrapped) -> Self { + // SAFETY: repr(transparent) + unsafe { transmute(from) } + } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[derive(Into)] -#[into(owned(types(i32, i64, i128)), ref(types(i32)), ref_mut(types(i32)))] -struct MyIntsExplicit(i32, MyInt, MyIntExplicit); - -#[test] -fn explicit_types_struct_tupled() { - let mut input = MyIntsExplicit(42i32, MyInt(42), MyIntExplicit(MyInt(42))); - assert_eq!( - <(i32, MyInt, MyIntExplicit)>::from(input), - (42i32, MyInt(42), MyIntExplicit(MyInt(42))), - ); - assert_eq!( - <(&i32, &MyInt, &MyIntExplicit)>::from(&input), - (&42i32, &MyInt(42), &MyIntExplicit(MyInt(42))), - ); - assert_eq!( - <(&mut i32, &mut MyInt, &mut MyIntExplicit)>::from(&mut input), - (&mut 42i32, &mut MyInt(42), &mut MyIntExplicit(MyInt(42))), - ); - assert_eq!(<(i32, i32, i32)>::from(input), (42i32, 42i32, 42i32)); - assert_eq!(<(&i32, &i32, &i32)>::from(&input), (&42i32, &42i32, &42i32)); - assert_eq!( - <(&mut i32, &mut i32, &mut i32)>::from(&mut input), - (&mut 42i32, &mut 42i32, &mut 42i32), - ); - assert_eq!(<(i64, i64, i64)>::from(input), (42i64, 42i64, 42i64)); - assert_eq!(<(i128, i128, i128)>::from(input), (42i128, 42i128, 42i128)); -} +mod unit { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Unit; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(); + + #[derive(Debug, Into, PartialEq)] + struct Struct {} + + #[test] + fn assert() { + assert_eq!((), Unit.into()); + assert_eq!((), Tuple().into()); + assert_eq!((), Struct {}.into()); + } + + mod generic { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Unit; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(); + + #[derive(Debug, Into, PartialEq)] + struct Struct {} -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[derive(Into)] -#[into(owned, ref, ref_mut, types(i32))] -struct Point2DExplicit { - x: MyInt, - y: MyInt, + #[test] + fn assert() { + assert_eq!((), Unit::<1>.into()); + assert_eq!((), Tuple::<1>().into()); + assert_eq!((), Struct::<1> {}.into()); + } + } } -#[test] -fn explicit_types_point_2d() { - let mut input = Point2DExplicit { - x: MyInt(42), - y: MyInt(42), - }; - assert_eq!(<(i32, i32)>::from(input), (42i32, 42i32)); - assert_eq!(<(&i32, &i32)>::from(&input), (&42i32, &42i32)); - assert_eq!( - <(&mut i32, &mut i32)>::from(&mut input), - (&mut 42i32, &mut 42i32) - ); +mod single_field { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(i32); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field: i32, + } + + #[test] + fn assert() { + assert_eq!(42, Tuple(42).into()); + assert_eq!(42, Struct { field: 42 }.into()); + } + + mod skip { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(#[into(skip)] i32); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + #[into(skip)] + field: i32, + } + + #[test] + fn assert() { + assert_eq!((), Tuple(42).into()); + assert_eq!((), Struct { field: 42 }.into()); + } + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(i64)] + #[into(i128)] + struct Tuple(i32); + + #[derive(Debug, Into, PartialEq)] + #[into(Box, Cow<'_, str>)] + struct Struct { + field: String, + } + + #[test] + fn assert() { + assert_not_impl_any!(Tuple: Into); + assert_not_impl_any!(Struct: Into); + + assert_eq!(42_i64, Tuple(42).into()); + assert_eq!(42_i128, Tuple(42).into()); + assert_eq!( + Box::::from("42".to_owned()), + Struct { + field: "42".to_string(), + } + .into(), + ); + assert_eq!( + Cow::Borrowed("42"), + Cow::::from(Struct { + field: "42".to_string(), + }), + ); + } + + mod ref_ { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Unnamed(i32); + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Named { + field: i32, + } + + #[test] + fn assert() { + assert_eq!(&42, <&i32>::from(&Unnamed(42))); + assert_eq!(&42, <&i32>::from(&Named { field: 42 })); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref(i32, Unnamed))] + struct Tuple(Unnamed); + + #[derive(Debug, Into, PartialEq)] + #[into(ref(i32, Named))] + struct Struct { + field: Named, + } + + #[test] + fn assert() { + assert_eq!(&42, <&i32>::from(&Tuple(Unnamed(42)))); + assert_eq!(&Unnamed(42), <&Unnamed>::from(&Tuple(Unnamed(42)))); + assert_eq!( + &42, + <&i32>::from(&Struct { + field: Named { field: 42 }, + }), + ); + assert_eq!( + &Named { field: 42 }, + <&Named>::from(&Struct { + field: Named { field: 42 }, + }), + ); + } + } + } + + mod ref_mut { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Unnamed(i32); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Named { + field: i32, + } + + #[test] + fn assert() { + assert_eq!(&mut 42, <&mut i32>::from(&mut Unnamed(42))); + assert_eq!(&mut 42, <&mut i32>::from(&mut Named { field: 42 })); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut(i32, Unnamed))] + struct Tuple(Unnamed); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut(i32, Named))] + struct Struct { + field: Named, + } + + #[test] + fn assert() { + assert_eq!(&mut 42, <&mut i32>::from(&mut Tuple(Unnamed(42)))); + assert_eq!( + &mut Unnamed(42), + <&mut Unnamed>::from(&mut Tuple(Unnamed(42))), + ); + assert_eq!( + &mut 42, + <&mut i32>::from(&mut Struct { + field: Named { field: 42 }, + }), + ); + assert_eq!( + &mut Named { field: 42 }, + <&mut Named>::from(&mut Struct { + field: Named { field: 42 }, + }), + ); + } + } + } + } + + mod generic { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!(Wrapped(42), Tuple(Wrapped(42)).into()); + assert_eq!(Wrapped(42), Struct { field: Wrapped(42) }.into()); + } + + mod skip { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(#[into(skip)] Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + #[into(skip)] + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!((), Tuple(Wrapped(42)).into()); + assert_eq!((), Struct { field: Wrapped(42) }.into()); + } + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(Transmuted)] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(Transmuted)] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!(Transmuted(42), Tuple(Wrapped(42)).into()); + assert_eq!(Transmuted(42), Struct { field: Wrapped(42) }.into()); + } + } + + mod ref_ { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!(&Wrapped(42), <&Wrapped<_>>::from(&Tuple(Wrapped(42)))); + assert_eq!( + &Wrapped(42), + <&Wrapped<_>>::from(&Struct { field: Wrapped(42) }) + ); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref(Transmuted))] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref(Transmuted))] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + &Transmuted(42), + <&Transmuted<_>>::from(&Tuple(Wrapped(42))), + ); + assert_eq!( + &Transmuted(42), + <&Transmuted<_>>::from(&Struct { field: Wrapped(42) }), + ); + } + } + } + + mod ref_mut { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + &mut Wrapped(42), + <&mut Wrapped<_>>::from(&mut Tuple(Wrapped(42))) + ); + assert_eq!( + &mut Wrapped(42), + <&mut Wrapped<_>>::from(&mut Struct { field: Wrapped(42) }), + ); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut(Transmuted))] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut(Transmuted))] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + &mut Transmuted(42), + <&mut Transmuted<_>>::from(&mut Tuple(Wrapped(42))), + ); + assert_eq!( + &mut Transmuted(42), + <&mut Transmuted<_>>::from(&mut Struct { field: Wrapped(42) }), + ); + } + } + } + + mod indirect { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(&'static Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field: &'static Wrapped, + } + + #[test] + fn assert() { + assert_eq!(&Wrapped(42), <&Wrapped<_>>::from(Tuple(&Wrapped(42)))); + assert_eq!( + &Wrapped(42), + <&Wrapped<_>>::from(Struct { + field: &Wrapped(42), + }), + ); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!(Wrapped(42), Tuple(Wrapped(42)).into()); + assert_eq!(Wrapped(42), Struct { field: Wrapped(42) }.into()); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field: Wrapped, + } + + #[test] + fn assert() { + assert_eq!(Wrapped(1), Tuple::<1, _>(Wrapped(1)).into()); + assert_eq!(Wrapped(1), Struct::<_, 1> { field: Wrapped(1) }.into()); + } + } + } } -#[derive(Clone, Debug, Eq, PartialEq)] -#[derive(Into)] -#[into(owned(types("Cow<'_, str>")))] -struct Name(String); - -#[test] -fn explicit_complex_types_name() { - let name = "Ñolofinwë"; - let input = Name(name.into()); - assert_eq!(String::from(input.clone()), name); - assert_eq!(Cow::from(input.clone()), Cow::Borrowed(name)); +mod multi_field { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(i32, i64); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field1: i32, + field2: i64, + } + + #[test] + fn assert() { + assert_eq!((1, 2_i64), Tuple(1, 2_i64).into()); + assert_eq!( + (1, 2_i64), + Struct { + field1: 1, + field2: 2_i64, + } + .into(), + ); + } + + mod skip { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(i32, #[into(skip)] i64); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + #[into(skip)] + field1: i32, + field2: i64, + } + + #[test] + fn assert() { + assert_eq!(1, Tuple(1, 2_i64).into()); + assert_eq!( + 2_i64, + Struct { + field1: 1, + field2: 2_i64, + } + .into(), + ); + } + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into((i32, i64))] + #[into((i64, i128))] + struct Tuple(i16, i32); + + #[derive(Debug, Into, PartialEq)] + #[into((Box, i32), (Cow<'_, str>, i64))] + struct Struct { + field1: String, + field2: i32, + } + + #[test] + fn assert() { + assert_not_impl_any!(Tuple: Into<(i16, i32)>); + assert_not_impl_any!(Struct: Into<(String, i32)>); + + assert_eq!((1, 2_i64), Tuple(1_i16, 2).into()); + assert_eq!((1_i64, 2_i128), Tuple(1_i16, 2).into()); + assert_eq!( + (Box::::from("42".to_owned()), 1), + Struct { + field1: "42".to_string(), + field2: 1, + } + .into(), + ); + assert_eq!( + (Cow::Borrowed("42"), 1_i64), + Struct { + field1: "42".to_string(), + field2: 1, + } + .into(), + ); + } + + mod ref_ { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Unnamed(i32, i64); + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Named { + field1: i32, + field2: i64, + } + + #[test] + fn assert() { + assert_eq!((&1, &2_i64), (&Unnamed(1, 2_i64)).into()); + assert_eq!( + (&1, &2_i64), + (&Named { + field1: 1, + field2: 2_i64, + }) + .into(), + ); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref( + (Transmuted, Transmuted), + (Transmuted, Wrapped)), + )] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref( + (Transmuted, Transmuted), + (Transmuted, Wrapped)), + )] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (&Transmuted(1), &Transmuted(2_i64)), + (&Tuple(Wrapped(1), Wrapped(2_i64))).into(), + ); + assert_eq!( + (&Transmuted(1), &Wrapped(2_i64)), + (&Tuple(Wrapped(1), Wrapped(2_i64))).into(), + ); + assert_eq!( + (&Transmuted(1), &Transmuted(2_i64)), + (&Struct { + field1: Wrapped(1), + field2: Wrapped(2_i64), + }) + .into(), + ); + assert_eq!( + (&Transmuted(1), &Wrapped(2_i64)), + (&Struct { + field1: Wrapped(1), + field2: Wrapped(2_i64), + }) + .into(), + ); + } + } + } + + mod ref_mut { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Unnamed(i32, i64); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Named { + field1: i32, + field2: i64, + } + + #[test] + fn assert() { + assert_eq!((&mut 1, &mut 2_i64), (&mut Unnamed(1, 2_i64)).into()); + assert_eq!( + (&mut 1, &mut 2_i64), + (&mut Named { + field1: 1, + field2: 2_i64, + }) + .into(), + ); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut( + (Transmuted, Transmuted), + (Transmuted, Wrapped)), + )] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut( + (Transmuted, Transmuted), + (Transmuted, Wrapped)), + )] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (&mut Transmuted(1), &mut Transmuted(2_i64)), + (&mut Tuple(Wrapped(1), Wrapped(2_i64))).into(), + ); + assert_eq!( + (&mut Transmuted(1), &mut Wrapped(2_i64)), + (&mut Tuple(Wrapped(1), Wrapped(2_i64))).into(), + ); + assert_eq!( + (&mut Transmuted(1), &mut Transmuted(2_i64)), + (&mut Struct { + field1: Wrapped(1), + field2: Wrapped(2_i64), + }) + .into(), + ); + assert_eq!( + (&mut Transmuted(1), &mut Wrapped(2_i64)), + (&mut Struct { + field1: Wrapped(1), + field2: Wrapped(2_i64), + }) + .into(), + ); + } + } + } + } + + mod generic { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (Wrapped(1), Wrapped(2)), + Tuple(Wrapped(1), Wrapped(2)).into(), + ); + assert_eq!( + (Wrapped(1), Wrapped(2)), + Struct { + field1: Wrapped(1), + field2: Wrapped(2), + } + .into(), + ); + } + + mod skip { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(Wrapped, #[into(skip)] Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + #[into(skip)] + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!(Wrapped(1), Tuple(Wrapped(1), Wrapped(2)).into()); + assert_eq!( + Wrapped(2), + Struct { + field1: Wrapped(1), + field2: Wrapped(2), + } + .into(), + ); + } + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into((Transmuted, Transmuted))] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into((Transmuted, Transmuted))] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (Transmuted(1), Transmuted(2)), + Tuple(Wrapped(1), Wrapped(2)).into(), + ); + assert_eq!( + (Transmuted(1), Transmuted(2)), + Struct { + field1: Wrapped(1), + field2: Wrapped(2), + } + .into(), + ); + } + } + + mod ref_ { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref)] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (&Wrapped(1), &Wrapped(2)), + (&Tuple(Wrapped(1), Wrapped(2))).into(), + ); + assert_eq!( + (&Wrapped(1), &Wrapped(2)), + (&Struct { + field1: Wrapped(1), + field2: Wrapped(2), + }) + .into(), + ); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref((Transmuted, Transmuted)))] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref((Transmuted, Transmuted)))] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (&Transmuted(1), &Transmuted(2)), + (&Tuple(Wrapped(1), Wrapped(2))).into(), + ); + assert_eq!( + (&Transmuted(1), &Transmuted(2)), + (&Struct { + field1: Wrapped(1), + field2: Wrapped(2), + }) + .into(), + ); + } + } + } + + mod ref_mut { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut)] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (&mut Wrapped(1), &mut Wrapped(2)), + (&mut Tuple(Wrapped(1), Wrapped(2))).into(), + ); + assert_eq!( + (&mut Wrapped(1), &mut Wrapped(2)), + (&mut Struct { + field1: Wrapped(1), + field2: Wrapped(2), + }) + .into(), + ); + } + + mod types { + use super::*; + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut((Transmuted, Transmuted)))] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + #[into(ref_mut((Transmuted, Transmuted)))] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (&mut Transmuted(1), &mut Transmuted(2)), + (&mut Tuple(Wrapped(1), Wrapped(2))).into(), + ); + assert_eq!( + (&mut Transmuted(1), &mut Transmuted(2)), + (&mut Struct { + field1: Wrapped(1), + field2: Wrapped(2), + }) + .into(), + ); + } + } + } + + mod indirect { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple( + &'static Wrapped, + &'static Wrapped, + ); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field1: &'static Wrapped, + field2: &'static Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (&Wrapped(1), &Wrapped(2)), + Tuple(&Wrapped(1), &Wrapped(2)).into(), + ); + assert_eq!( + (&Wrapped(1), &Wrapped(2)), + (Struct { + field1: &Wrapped(1), + field2: &Wrapped(2), + }) + .into(), + ); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (Wrapped(1), Wrapped(2)), + Tuple(Wrapped(1), Wrapped(2)).into(), + ); + assert_eq!( + (Wrapped(1), Wrapped(2)), + Struct { + field1: Wrapped(1), + field2: Wrapped(2), + } + .into(), + ); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, Into, PartialEq)] + struct Tuple(Wrapped, Wrapped); + + #[derive(Debug, Into, PartialEq)] + struct Struct { + field1: Wrapped, + field2: Wrapped, + } + + #[test] + fn assert() { + assert_eq!( + (Wrapped(1), Wrapped(2)), + Tuple::<1, _, _>(Wrapped(1), Wrapped(2)).into(), + ); + assert_eq!( + (Wrapped(1), Wrapped(2)), + Struct::<_, 1, _> { + field1: Wrapped(1), + field2: Wrapped(2), + } + .into(), + ); + } + } + } }