Repository: JelteF/derive_more
Pull request #248 (2023-03-16 13:41:42)

Redesign `Into` derive macro
## 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((<tuple>), (<tuple>), ...)]`.




## Checklist

- [x] Documentation is updated (if required)
- [x] Tests are added/updated (if required)
- [x] [CHANGELOG entry][l:1] is added (if required)




[l:1]: /CHANGELOG.md


JelteF: It's a bit hard to review this as it also contains all the changes from #241. Could you create a PR on your own where the base branch is the from-attribute branch? That way the diff doesn't contain so much unrelated stuff and it's easier to comment on the actual changes that are made for this one.
JelteF: @ilslv @tyranron now that #241 is merged, let's rebase this one. That should make it much easier to review.
ilslv: @JelteF I was planning to do it next week 👍

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10ca069..1105d76 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(<types>)]` instead of `#[from(types(<types>))]`
   and ignores field type itself.
+- The `Into` derive now uses `#[into(<types>)]` instead of `#[into(types(<types>))]`
+  and ignores field type itself.
 
 ### Added
 
diff --git a/Cargo.toml b/Cargo.toml
index deede6e..32999fb 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 16d0c2b..0ca676d 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<MyInt> 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(<types>)]`.
 
 ```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"), <Cow<_>>::from(Str("Cow".into())));
 
-```rust
-# struct MyInts(i32, i32);
-impl ::core::convert::From<MyInts> 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<Point1D> 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<Unit> {
+    value: i32,
+    #[into(skip)]
+    _unit: PhantomData<Unit>,
 }
 
-```
-
-Code like this will be generated:
-
-```rust
-# struct Point2D {
-#     x: i32,
-#     y: i32,
+assert_eq!(5, Mass::<Gram>::new(5).into());
+assert_eq!(5_i64, Mass::<Gram>::new(5).into());
+assert_eq!(5_i128, Mass::<Gram>::new(5).into());
+#
+# impl<Unit> Mass<Unit> {
+#     fn new(value: i32) -> Self {
+#         Self {
+#             value,
+#             _unit: PhantomData,
+#         }
+#     }
 # }
-impl ::core::convert::From<Point2D> 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 4397f94..cefab9b 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<TokenStream> {
@@ -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<TokenStream> {
+        use crate::utils::FieldsExt as _;
+
         let ident = self.ident;
         let field_tys = self.fields.iter().map(|f| &f.ty).collect::<Vec<_>>();
         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<impl Iterator<Item = &'t TokenStream>> {
-        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::<Vec<_>>()
-                                    .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::<Vec<_>>()
-                                    .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::<Vec<_>>()
-                                .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<L, R> {
-    /// Left variant.
-    Left(L),
-
-    /// Right variant.
-    Right(R),
-}
-
-impl<L, R, T> Iterator for Either<L, R>
-where
-    L: Iterator<Item = T>,
-    R: Iterator<Item = T>,
-{
-    type Item = T;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        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 20e897e..91e23bf 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<TokenStream> {
-    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<TokenStream> {
+    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::<syn::Result<Vec<_>>>()?;
+    let (fields_tys, fields_idents): (Vec<_>, Vec<_>) = fields.into_iter().unzip();
+    let (fields_tys, fields_idents) = (&fields_tys, &fields_idents);
+
+    let expand = |tys: Option<Punctuated<_, _>>, 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::<Vec<_>>();
+                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(<types>)]
+/// #[into(owned(<types>), ref(<types>), ref_mut(<types>))]
+/// ```
+#[derive(Debug, Default)]
+struct StructAttribute {
+    /// [`Type`]s wrapped into `owned(...)` or simply `#[into(...)]`.
+    owned: Option<Punctuated<Type, token::Comma>>,
+
+    /// [`Type`]s wrapped into `ref(...)`.
+    r#ref: Option<Punctuated<Type, token::Comma>>,
+
+    /// [`Type`]s wrapped into `ref_mut(...)`.
+    ref_mut: Option<Punctuated<Type, token::Comma>>,
+}
+
+impl StructAttribute {
+    /// Parses a [`StructAttribute`] from the provided [`syn::Attribute`]s.
+    fn parse_attrs(
+        attrs: impl AsRef<[syn::Attribute]>,
+        fields: &syn::Fields,
+    ) -> syn::Result<Option<Self>> {
+        fn infer<T>(v: T) -> T
+        where
+            T: for<'a> FnOnce(ParseStream<'a>) -> syn::Result<StructAttribute>,
+        {
+            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::<Punctuated<_, _>>(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<Self> {
+        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::<token::Comma>()?;
+                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::<syn::Path>()
+            };
+            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::<Type>()?;
+                    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::<token::Comma>()?)
                     }
                 }
-            }).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<Option<Self>> {
+        Ok(attrs
+            .as_ref()
+            .iter()
+            .filter(|attr| attr.path().is_ident("into"))
+            .try_fold(None, |mut attrs, attr| {
+                let field_attr = attr.parse_args::<SkipFieldAttribute>()?;
+                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<Self> {
+        match content.parse::<syn::Path>()? {
+            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::<Vec<_>>()
+                    .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::<Vec<_>>()
+                .join(", ")
+        )),
+    };
+
+    let Ok(metas) = tokens.parse_terminated(polyfill::Meta::parse, token::Comma) else {
+        return Ok(());
+    };
+
+    let parse_list = |list: polyfill::MetaList, attrs: &mut Option<Vec<_>>| {
+        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<Vec<_>>, 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::<Vec<_>>()
+                    .join(", "),
+            )),
+            None => None,
+        };
+        let format = [
+            format(owned, "owned"),
+            format(ref_, "ref"),
+            format(ref_mut, "ref_mut"),
+        ]
+        .into_iter()
+        .flatten()
+        .collect::<Vec<_>>()
+        .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 751fba5..8c028fe 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 af571ed..42b6637 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<T: ToTokens>(items: impl IntoIterator<Item = T>) -> 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<Self> {
         input.step(|c| {
diff --git a/impl/src/utils.rs b/impl/src/utils.rs
index f192205..1728911 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<I: ?Sized>(&self, ident: &I) -> bool
+        pub(crate) fn is_ident<I: ?Sized>(&self, ident: &I) -> bool
         where
             syn::Ident: PartialEq<I>,
         {
@@ -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<syn::Type> {
-        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<L, R> {
+        /// Left variant.
+        Left(L),
+
+        /// Right variant.
+        Right(R),
+    }
+
+    impl<L, R, T> Iterator for Either<L, R>
+    where
+        L: Iterator<Item = T>,
+        R: Iterator<Item = T>,
+    {
+        type Item = T;
+
+        fn next(&mut self) -> Option<Self::Item> {
+            match self {
+                Either::Left(left) => left.next(),
+                Either::Right(right) => right.next(),
+            }
+        }
+    }
+
+    impl<L, R> ToTokens for Either<L, R>
+    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<T> Len for Vec<T> {
+        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<punctuated::Iter<'t, TokenStream>, 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::<Vec<_>>()
+                                        .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::<Vec<_>>()
+                                        .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::<Vec<_>>()
+                                    .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<T: Len + ?Sized> FieldsExt for T {}
+}


Test patch:
diff --git a/tests/compile_fail/into/enum.rs b/tests/compile_fail/into/enum.rs
new file mode 100644
index 0000000..33edf28
--- /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 0000000..74f15e7
--- /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 0000000..f58e772
--- /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 0000000..3671224
--- /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 0000000..5b8a1ba
--- /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 0000000..bfad035
--- /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 0000000..43f81fb
--- /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 0000000..4c23957
--- /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 0000000..7491e38
--- /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 0000000..9be31ca
--- /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 0000000..cef5593
--- /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 0000000..57033a5
--- /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 0000000..89ee924
--- /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 0000000..0e68545
--- /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 0000000..623989c
--- /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 0000000..f8db542
--- /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 7328d3a..95799d2 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, To>(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>(T);
 
-#[derive(Into)]
-#[into(owned, ref, ref_mut)]
-struct Point1D {
-    x: i32,
-}
+#[derive(Debug, PartialEq)]
+#[repr(transparent)]
+struct Transmuted<T>(T);
 
-#[derive(Debug, Eq, PartialEq)]
-#[derive(Into)]
-#[into(owned, ref, ref_mut)]
-struct Point2D {
-    x: i32,
-    y: i32,
+impl<T> From<Wrapped<T>> for Transmuted<T> {
+    fn from(from: Wrapped<T>) -> 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<T> From<&Wrapped<T>> for &Transmuted<T> {
+    fn from(from: &Wrapped<T>) -> 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<T> From<&mut Wrapped<T>> for &mut Transmuted<T> {
+    fn from(from: &mut Wrapped<T>) -> 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<const N: usize>;
+
+        #[derive(Debug, Into, PartialEq)]
+        struct Tuple<const N: usize>();
+
+        #[derive(Debug, Into, PartialEq)]
+        struct Struct<const N: usize> {}
 
-#[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<str>, Cow<'_, str>)]
+        struct Struct {
+            field: String,
+        }
+
+        #[test]
+        fn assert() {
+            assert_not_impl_any!(Tuple: Into<i32>);
+            assert_not_impl_any!(Struct: Into<String>);
+
+            assert_eq!(42_i64, Tuple(42).into());
+            assert_eq!(42_i128, Tuple(42).into());
+            assert_eq!(
+                Box::<str>::from("42".to_owned()),
+                Struct {
+                    field: "42".to_string(),
+                }
+                .into(),
+            );
+            assert_eq!(
+                Cow::Borrowed("42"),
+                Cow::<str>::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<T>(Wrapped<T>);
+
+        #[derive(Debug, Into, PartialEq)]
+        struct Struct<T> {
+            field: Wrapped<T>,
+        }
+
+        #[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<T>(#[into(skip)] Wrapped<T>);
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<T> {
+                #[into(skip)]
+                field: Wrapped<T>,
+            }
+
+            #[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<T>)]
+            struct Tuple<T>(Wrapped<T>);
+
+            #[derive(Debug, Into, PartialEq)]
+            #[into(Transmuted<T>)]
+            struct Struct<T> {
+                field: Wrapped<T>,
+            }
+
+            #[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<T>(Wrapped<T>);
+
+            #[derive(Debug, Into, PartialEq)]
+            #[into(ref)]
+            struct Struct<T> {
+                field: Wrapped<T>,
+            }
+
+            #[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<T>))]
+                struct Tuple<T>(Wrapped<T>);
+
+                #[derive(Debug, Into, PartialEq)]
+                #[into(ref(Transmuted<T>))]
+                struct Struct<T> {
+                    field: Wrapped<T>,
+                }
+
+                #[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<T>(Wrapped<T>);
+
+            #[derive(Debug, Into, PartialEq)]
+            #[into(ref_mut)]
+            struct Struct<T> {
+                field: Wrapped<T>,
+            }
+
+            #[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<T>))]
+                struct Tuple<T>(Wrapped<T>);
+
+                #[derive(Debug, Into, PartialEq)]
+                #[into(ref_mut(Transmuted<T>))]
+                struct Struct<T> {
+                    field: Wrapped<T>,
+                }
+
+                #[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<T: 'static>(&'static Wrapped<T>);
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<T: 'static> {
+                field: &'static Wrapped<T>,
+            }
+
+            #[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<T: Clone>(Wrapped<T>);
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<T: Clone> {
+                field: Wrapped<T>,
+            }
+
+            #[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<const N: usize, T>(Wrapped<T>);
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<T, const N: usize> {
+                field: Wrapped<T>,
+            }
+
+            #[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<str>, 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::<str>::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<i32>, Transmuted<i64>),
+                    (Transmuted<i32>, Wrapped<i64>)),
+                )]
+                struct Tuple(Wrapped<i32>, Wrapped<i64>);
+
+                #[derive(Debug, Into, PartialEq)]
+                #[into(ref(
+                    (Transmuted<i32>, Transmuted<i64>),
+                    (Transmuted<i32>, Wrapped<i64>)),
+                )]
+                struct Struct {
+                    field1: Wrapped<i32>,
+                    field2: Wrapped<i64>,
+                }
+
+                #[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<i32>, Transmuted<i64>),
+                    (Transmuted<i32>, Wrapped<i64>)),
+                )]
+                struct Tuple(Wrapped<i32>, Wrapped<i64>);
+
+                #[derive(Debug, Into, PartialEq)]
+                #[into(ref_mut(
+                    (Transmuted<i32>, Transmuted<i64>),
+                    (Transmuted<i32>, Wrapped<i64>)),
+                )]
+                struct Struct {
+                    field1: Wrapped<i32>,
+                    field2: Wrapped<i64>,
+                }
+
+                #[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<A, B>(Wrapped<A>, Wrapped<B>);
+
+        #[derive(Debug, Into, PartialEq)]
+        struct Struct<A, B> {
+            field1: Wrapped<A>,
+            field2: Wrapped<B>,
+        }
+
+        #[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<A, B>(Wrapped<A>, #[into(skip)] Wrapped<B>);
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<A, B> {
+                #[into(skip)]
+                field1: Wrapped<A>,
+                field2: Wrapped<B>,
+            }
+
+            #[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<A>, Transmuted<B>))]
+            struct Tuple<A, B>(Wrapped<A>, Wrapped<B>);
+
+            #[derive(Debug, Into, PartialEq)]
+            #[into((Transmuted<A>, Transmuted<B>))]
+            struct Struct<A, B> {
+                field1: Wrapped<A>,
+                field2: Wrapped<B>,
+            }
+
+            #[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<A, B>(Wrapped<A>, Wrapped<B>);
+
+            #[derive(Debug, Into, PartialEq)]
+            #[into(ref)]
+            struct Struct<A, B> {
+                field1: Wrapped<A>,
+                field2: Wrapped<B>,
+            }
+
+            #[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<A>, Transmuted<B>)))]
+                struct Tuple<A, B>(Wrapped<A>, Wrapped<B>);
+
+                #[derive(Debug, Into, PartialEq)]
+                #[into(ref((Transmuted<A>, Transmuted<B>)))]
+                struct Struct<A, B> {
+                    field1: Wrapped<A>,
+                    field2: Wrapped<B>,
+                }
+
+                #[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<A, B>(Wrapped<A>, Wrapped<B>);
+
+            #[derive(Debug, Into, PartialEq)]
+            #[into(ref_mut)]
+            struct Struct<A, B> {
+                field1: Wrapped<A>,
+                field2: Wrapped<B>,
+            }
+
+            #[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<A>, Transmuted<B>)))]
+                struct Tuple<A, B>(Wrapped<A>, Wrapped<B>);
+
+                #[derive(Debug, Into, PartialEq)]
+                #[into(ref_mut((Transmuted<A>, Transmuted<B>)))]
+                struct Struct<A, B> {
+                    field1: Wrapped<A>,
+                    field2: Wrapped<B>,
+                }
+
+                #[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<A: 'static, B: 'static>(
+                &'static Wrapped<A>,
+                &'static Wrapped<B>,
+            );
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<A: 'static, B: 'static> {
+                field1: &'static Wrapped<A>,
+                field2: &'static Wrapped<B>,
+            }
+
+            #[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<A: Clone, B: Clone>(Wrapped<A>, Wrapped<B>);
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<A: Clone, B: Clone> {
+                field1: Wrapped<A>,
+                field2: Wrapped<B>,
+            }
+
+            #[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<const N: usize, A, B>(Wrapped<A>, Wrapped<B>);
+
+            #[derive(Debug, Into, PartialEq)]
+            struct Struct<A, const N: usize, B> {
+                field1: Wrapped<A>,
+                field2: Wrapped<B>,
+            }
+
+            #[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(),
+                );
+            }
+        }
+    }
 }