Repository: JelteF/derive_more
Pull request #286 (2023-08-09 12:56:41)

Refactor `AsRef` and `AsMut` derives
## Synopsis

This PR is a part of replacing all attributes having `syn::Meta` syntax to custom parsing, similarly to #241, #248 

Paves the way for #285, and possibly other enhancements such as an opt-in #123


## Solution

Implement custom attribute parsing without `State`


## 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


MegaBluejay: >  Only doing refactoring to allow future features.

Yes, that's correct, this PR is refactoring only
MegaBluejay: Since they're essentially identical, I think it makes sense to use the same deriving function for `AsRef` and `AsMut`.

I can't think of any hypothetical features that should apply to only one of them, and this would facilitate keeping them in sync.
tyranron: @JelteF the interesting question:

Since `AsRef` and `AsMut`  are effectively the same regarding implementation, should we unite them under a single `as_ref` Cargo feature as we do for `Display`-like derives?

I think it's quite reasonable. May be done as a separate PR, though, after merging this one.
JelteF: @tyranron feel free to sign off on after you are happy. I took a quick look and it looks reasonable to me.

Patch:
diff --git a/Cargo.toml b/Cargo.toml
index bc73639..6362cf1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -229,7 +229,7 @@ required-features = ["try_unwrap"]
 [[test]]
 name = "compile_fail"
 path = "tests/compile_fail/mod.rs"
-required-features = ["debug", "display", "from", "into"]
+required-features = ["as_ref", "as_mut", "debug", "display", "from", "into"]
 
 [[test]]
 name = "no_std"
diff --git a/impl/doc/as_mut.md b/impl/doc/as_mut.md
index a1e6127..a7f8057 100644
--- a/impl/doc/as_mut.md
+++ b/impl/doc/as_mut.md
@@ -50,13 +50,13 @@ This generates:
 
 ```rust
 # struct SingleFieldForward(Vec<i32>);
-impl<__AsMutT: ?::core::marker::Sized> ::core::convert::AsMut<__AsMutT> for SingleFieldForward
+impl<__AsT: ?::core::marker::Sized> ::core::convert::AsMut<__AsT> for SingleFieldForward
 where
-    Vec<i32>: ::core::convert::AsMut<__AsMutT>,
+    Vec<i32>: ::core::convert::AsMut<__AsT>,
 {
     #[inline]
-    fn as_mut(&mut self) -> &mut __AsMutT {
-        <Vec<i32> as ::core::convert::AsMut<__AsMutT>>::as_mut(&mut self.0)
+    fn as_mut(&mut self) -> &mut __AsT {
+        <Vec<i32> as ::core::convert::AsMut<__AsT>>::as_mut(&mut self.0)
     }
 }
 ```
@@ -69,7 +69,7 @@ where
 When `AsMut` is derived for a struct with more than one field (including tuple
 structs), you must also mark one or more fields with the `#[as_mut]` attribute.
 An implementation will be generated for each indicated field.
-You can also exclude a specific field by using `#[as_mut(ignore)]`.
+You can also exclude a specific field by using `#[as_mut(ignore)]` (or `#[as_mut(ignore)]`).
 
 ```rust
 # use derive_more::AsMut;
@@ -121,8 +121,23 @@ struct MyWrapper {
 }
 ```
 
+Similarly, if some field is annotated with `#[as_mut(forward)]`, no other
+field can be marked.
 
-
+```rust,compile_fail
+# use derive_more::AsMut;
+#
+// Error! Conflicting implementations of `AsMut<i32>`
+// note: upstream crates may add a new impl of trait `AsMut<i32>`
+// for type `String` in future versions
+#[derive(AsMut)]
+struct ForwardWithOther {
+    #[as_mut(forward)]
+    str: String,
+    #[as_mut]
+    number: i32,
+}
+```
 
 ## Enums
 
diff --git a/impl/doc/as_ref.md b/impl/doc/as_ref.md
index 112ba15..549e915 100644
--- a/impl/doc/as_ref.md
+++ b/impl/doc/as_ref.md
@@ -50,13 +50,13 @@ This generates:
 
 ```rust
 # struct SingleFieldForward(Vec<i32>);
-impl<__AsRefT: ?::core::marker::Sized> ::core::convert::AsRef<__AsRefT> for SingleFieldForward
+impl<__AsT: ?::core::marker::Sized> ::core::convert::AsRef<__AsT> for SingleFieldForward
 where
-    Vec<i32>: ::core::convert::AsRef<__AsRefT>,
+    Vec<i32>: ::core::convert::AsRef<__AsT>,
 {
     #[inline]
-    fn as_ref(&self) -> &__AsRefT {
-        <Vec<i32> as ::core::convert::AsRef<__AsRefT>>::as_ref(&self.0)
+    fn as_ref(&self) -> &__AsT {
+        <Vec<i32> as ::core::convert::AsRef<__AsT>>::as_ref(&self.0)
     }
 }
 ```
@@ -69,7 +69,7 @@ where
 When `AsRef` is derived for a struct with more than one field (including tuple
 structs), you must also mark one or more fields with the `#[as_ref]` attribute.
 An implementation will be generated for each indicated field.
-You can also exclude a specific field by using `#[as_ref(ignore)]`.
+You can also exclude a specific field by using `#[as_ref(skip)]` (or `#[as_ref(ignore)]`).
 
 ```rust
 # use derive_more::AsRef;
@@ -122,8 +122,23 @@ struct MyWrapper {
 }
 ```
 
+Similarly, if some field is annotated with `#[as_ref(forward)]`, no other
+field can be marked.
 
-
+```rust,compile_fail
+# use derive_more::AsRef;
+#
+// Error! Conflicting implementations of `AsRef<i32>`
+// note: upstream crates may add a new impl of trait `AsRef<i32>`
+// for type `String` in future versions
+#[derive(AsRef)]
+struct ForwardWithOther {
+    #[as_ref(forward)]
+    str: String,
+    #[as_ref]
+    number: i32,
+}
+```
 
 ## Enums
 
diff --git a/impl/src/as/mod.rs b/impl/src/as/mod.rs
new file mode 100644
index 0000000..6b73723
--- /dev/null
+++ b/impl/src/as/mod.rs
@@ -0,0 +1,298 @@
+//! Implementations of [`AsRef`]/[`AsMut`] derive macros.
+
+#[cfg(feature = "as_mut")]
+pub(crate) mod r#mut;
+#[cfg(feature = "as_ref")]
+pub(crate) mod r#ref;
+
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+use syn::{
+    parse::{discouraged::Speculative as _, Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned,
+    Token,
+};
+
+use crate::utils::{forward, skip, Either, Spanning};
+
+/// Expands an [`AsRef`]/[`AsMut`] derive macro.
+pub fn expand(
+    input: &syn::DeriveInput,
+    trait_info: ExpansionCtx<'_>,
+) -> syn::Result<TokenStream> {
+    let (trait_ident, attr_ident, _) = trait_info;
+
+    let data = match &input.data {
+        syn::Data::Struct(data) => Ok(data),
+        syn::Data::Enum(e) => Err(syn::Error::new(
+            e.enum_token.span(),
+            format!("`{trait_ident}` cannot be derived for enums"),
+        )),
+        syn::Data::Union(u) => Err(syn::Error::new(
+            u.union_token.span(),
+            format!("`{trait_ident}` cannot be derived for unions"),
+        )),
+    }?;
+
+    let expansions = if StructAttribute::parse_attrs(&input.attrs, attr_ident)?
+        .is_some()
+    {
+        if data.fields.len() != 1 {
+            return Err(syn::Error::new(
+                if data.fields.is_empty() {
+                    data.struct_token.span
+                } else {
+                    data.fields.span()
+                },
+                format!(
+                    "`#[{attr_ident}(...)]` attribute can only be placed on structs with exactly \
+                     one field",
+                ),
+            ));
+        }
+
+        let field = data.fields.iter().next().unwrap();
+        if FieldAttribute::parse_attrs(&field.attrs, attr_ident)?.is_some() {
+            return Err(syn::Error::new(
+                field.span(),
+                format!("`#[{attr_ident}(...)]` cannot be placed on both struct and its field"),
+            ));
+        }
+
+        vec![Expansion {
+            trait_info,
+            ident: &input.ident,
+            generics: &input.generics,
+            field,
+            field_index: 0,
+            forward: true,
+        }]
+    } else {
+        let attrs = data
+            .fields
+            .iter()
+            .map(|field| FieldAttribute::parse_attrs(&field.attrs, attr_ident))
+            .collect::<syn::Result<Vec<_>>>()?;
+
+        let present_attrs = attrs.iter().filter_map(Option::as_ref).collect::<Vec<_>>();
+
+        let all = present_attrs
+            .iter()
+            .all(|attr| matches!(attr.item, FieldAttribute::Skip(_)));
+
+        if !all {
+            if let Some(skip_attr) = present_attrs.iter().find_map(|attr| {
+                if let FieldAttribute::Skip(skip) = &attr.item {
+                    Some(attr.as_ref().map(|_| skip))
+                } else {
+                    None
+                }
+            }) {
+                return Err(syn::Error::new(
+                    skip_attr.span(),
+                    format!(
+                        "`#[{attr_ident}({})]` cannot be used in the same struct with other \
+                         `#[{attr_ident}(...)]` attributes",
+                        skip_attr.name(),
+                    ),
+                ));
+            }
+        }
+
+        if all {
+            data.fields
+                .iter()
+                .enumerate()
+                .zip(attrs)
+                .filter_map(|((i, field), attr)| {
+                    attr.is_none().then_some(Expansion {
+                        trait_info,
+                        ident: &input.ident,
+                        generics: &input.generics,
+                        field,
+                        field_index: i,
+                        forward: false,
+                    })
+                })
+                .collect()
+        } else {
+            data.fields
+                .iter()
+                .enumerate()
+                .zip(attrs)
+                .filter_map(|((i, field), attr)| match attr.map(Spanning::into_inner) {
+                    attr @ Some(FieldAttribute::Empty | FieldAttribute::Forward(_)) => {
+                        Some(Expansion {
+                            trait_info,
+                            ident: &input.ident,
+                            generics: &input.generics,
+                            field,
+                            field_index: i,
+                            forward: matches!(attr, Some(FieldAttribute::Forward(_))),
+                        })
+                    }
+                    Some(FieldAttribute::Skip(_)) => unreachable!(),
+                    None => None,
+                })
+                .collect()
+        }
+    };
+    Ok(expansions
+        .into_iter()
+        .map(ToTokens::into_token_stream)
+        .collect())
+}
+
+/// Type alias for an expansion context:
+/// - [`syn::Ident`] of the derived trait.
+/// - [`syn::Ident`] of the derived trait method.
+/// - Optional `mut` token indicating [`AsMut`] expansion.
+type ExpansionCtx<'a> = (&'a syn::Ident, &'a syn::Ident, Option<&'a Token![mut]>);
+
+/// Expansion of a macro for generating [`AsRef`]/[`AsMut`] implementation for a single field of a
+/// struct.
+struct Expansion<'a> {
+    /// [`ExpansionCtx] of the derived trait.
+    trait_info: ExpansionCtx<'a>,
+
+    /// [`syn::Ident`] of the struct.
+    ident: &'a syn::Ident,
+
+    /// [`syn::Generics`] of the struct.
+    generics: &'a syn::Generics,
+
+    /// [`syn::Field`] of the struct.
+    field: &'a syn::Field,
+
+    /// Index of the [`syn::Field`].
+    field_index: usize,
+
+    /// Indicator whether `forward` implementation should be expanded.
+    forward: bool,
+}
+
+impl<'a> ToTokens for Expansion<'a> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let field_ty = &self.field.ty;
+        let field_ident = self.field.ident.as_ref().map_or_else(
+            || Either::Right(syn::Index::from(self.field_index)),
+            Either::Left,
+        );
+
+        let return_ty = if self.forward {
+            quote! { __AsT }
+        } else {
+            quote! { #field_ty }
+        };
+
+        let (trait_ident, method_ident, mut_) = &self.trait_info;
+        let trait_ty = quote! { ::core::convert::#trait_ident <#return_ty> };
+
+        let ty_ident = &self.ident;
+        let mut generics = self.generics.clone();
+        if self.forward {
+            generics.params.push(parse_quote! { #return_ty });
+            generics
+                .make_where_clause()
+                .predicates
+                .extend::<[syn::WherePredicate; 2]>([
+                    parse_quote! { #return_ty: ?::core::marker::Sized },
+                    parse_quote! { #field_ty: #trait_ty },
+                ]);
+        }
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+        let (_, ty_gens, _) = self.generics.split_for_impl();
+
+        let mut body = quote! { & #mut_ self.#field_ident };
+        if self.forward {
+            body = quote! {
+                <#field_ty as #trait_ty>::#method_ident(#body)
+            };
+        }
+
+        quote! {
+            #[automatically_derived]
+            impl #impl_gens #trait_ty for #ty_ident #ty_gens #where_clause {
+                #[inline]
+                fn #method_ident(& #mut_ self) -> & #mut_ #return_ty {
+                    #body
+                }
+            }
+        }
+        .to_tokens(tokens)
+    }
+}
+
+/// Representation of an [`AsRef`]/[`AsMut`] derive macro struct container attribute.
+///
+/// ```rust,ignore
+/// #[as_ref(forward)]
+/// ```
+type StructAttribute = forward::Attribute;
+
+/// Representation of an [`AsRef`]/[`AsMut`] derive macro field attribute.
+///
+/// ```rust,ignore
+/// #[as_ref]
+/// #[as_ref(forward)]
+/// #[as_ref(skip)] #[as_ref(ignore)]
+/// ```
+enum FieldAttribute {
+    Empty,
+    Forward(forward::Attribute),
+    Skip(skip::Attribute),
+}
+
+impl Parse for FieldAttribute {
+    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
+        if input.is_empty() {
+            return Ok(Self::Empty);
+        }
+
+        let ahead = input.fork();
+        if let Ok(attr) = ahead.parse::<forward::Attribute>() {
+            input.advance_to(&ahead);
+            return Ok(Self::Forward(attr));
+        }
+
+        let ahead = input.fork();
+        if let Ok(attr) = ahead.parse::<skip::Attribute>() {
+            input.advance_to(&ahead);
+            return Ok(Self::Skip(attr));
+        }
+
+        Err(syn::Error::new(
+            input.span(),
+            "only `forward` or `skip`/`ignore` allowed here",
+        ))
+    }
+}
+
+impl FieldAttribute {
+    /// Parses a [`FieldAttribute`] from the provided [`syn::Attribute`]s, preserving its [`Span`].
+    ///
+    /// [`Span`]: proc_macro2::Span
+    fn parse_attrs(
+        attrs: &[syn::Attribute],
+        attr_ident: &syn::Ident,
+    ) -> syn::Result<Option<Spanning<Self>>> {
+        attrs.iter()
+            .filter(|attr| attr.path().is_ident(attr_ident))
+            .try_fold(None, |mut attrs, attr| {
+                let parsed = Spanning::new(if matches!(attr.meta, syn::Meta::Path(_)) {
+                    Self::Empty
+                } else {
+                    attr.parse_args()?
+                }, attr.span());
+                if attrs.replace(parsed).is_some() {
+                    Err(syn::Error::new(
+                        attr.span(),
+                        format!("only single `#[{attr_ident}(...)]` attribute is allowed here"),
+                    ))
+                } else {
+                    Ok(attrs)
+                }
+            })
+    }
+}
diff --git a/impl/src/as/mut.rs b/impl/src/as/mut.rs
new file mode 100644
index 0000000..b24d0e1
--- /dev/null
+++ b/impl/src/as/mut.rs
@@ -0,0 +1,17 @@
+//! Implementation of an [`AsMut`] derive macro.
+
+use proc_macro2::TokenStream;
+use quote::format_ident;
+use syn::Token;
+
+/// Expands an [`AsMut`] derive macro.
+pub(crate) fn expand(
+    input: &syn::DeriveInput,
+    trait_name: &'static str,
+) -> syn::Result<TokenStream> {
+    let trait_ident = format_ident!("{trait_name}");
+    let method_ident = format_ident!("as_mut");
+    let mutability = <Token![mut]>::default();
+
+    super::expand(input, (&trait_ident, &method_ident, Some(&mutability)))
+}
diff --git a/impl/src/as/ref.rs b/impl/src/as/ref.rs
new file mode 100644
index 0000000..941fcf2
--- /dev/null
+++ b/impl/src/as/ref.rs
@@ -0,0 +1,15 @@
+//! Implementation of an [`AsRef`] derive macro.
+
+use proc_macro2::TokenStream;
+use quote::format_ident;
+
+/// Expands an [`AsRef`] derive macro.
+pub(crate) fn expand(
+    input: &syn::DeriveInput,
+    trait_name: &'static str,
+) -> syn::Result<TokenStream> {
+    let trait_ident = format_ident!("{trait_name}");
+    let method_ident = format_ident!("as_ref");
+
+    super::expand(input, (&trait_ident, &method_ident, None))
+}
diff --git a/impl/src/as_mut.rs b/impl/src/as_mut.rs
deleted file mode 100644
index 1d05adc..0000000
--- a/impl/src/as_mut.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use crate::utils::{
-    add_where_clauses_for_new_ident, AttrParams, MultiFieldData, State,
-};
-use proc_macro2::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse::Result, DeriveInput};
-
-pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
-    let as_mut_type = format_ident!("__AsMutT");
-    let state = State::with_type_bound(
-        input,
-        trait_name,
-        "as_mut".into(),
-        AttrParams::ignore_and_forward(),
-        false,
-    )?;
-    let MultiFieldData {
-        fields,
-        input_type,
-        members,
-        infos,
-        trait_path,
-        impl_generics,
-        ty_generics,
-        where_clause,
-        ..
-    } = state.enabled_fields_data();
-    let sub_items: Vec<_> = infos
-        .iter()
-        .zip(members.iter())
-        .zip(fields)
-        .map(|((info, member), field)| {
-            let field_type = &field.ty;
-            if info.forward {
-                let trait_path = quote! { #trait_path<#as_mut_type> };
-                let type_where_clauses = quote! {
-                    where #field_type: #trait_path
-                };
-                let new_generics = add_where_clauses_for_new_ident(
-                    &input.generics,
-                    &[field],
-                    &as_mut_type,
-                    type_where_clauses,
-                    false,
-                );
-                let (impl_generics, _, where_clause) = new_generics.split_for_impl();
-                let casted_trait = quote! { <#field_type as #trait_path> };
-                (
-                    quote! { #casted_trait::as_mut(&mut #member) },
-                    quote! { #impl_generics },
-                    quote! { #where_clause },
-                    quote! { #trait_path },
-                    quote! { #as_mut_type },
-                )
-            } else {
-                (
-                    quote! { &mut #member },
-                    quote! { #impl_generics },
-                    quote! { #where_clause },
-                    quote! { #trait_path<#field_type> },
-                    quote! { #field_type },
-                )
-            }
-        })
-        .collect();
-    let bodies = sub_items.iter().map(|i| &i.0);
-    let impl_genericses = sub_items.iter().map(|i| &i.1);
-    let where_clauses = sub_items.iter().map(|i| &i.2);
-    let trait_paths = sub_items.iter().map(|i| &i.3);
-    let return_types = sub_items.iter().map(|i| &i.4);
-
-    Ok(quote! {#(
-        #[automatically_derived]
-        impl #impl_genericses #trait_paths for #input_type #ty_generics #where_clauses {
-            #[inline]
-            fn as_mut(&mut self) -> &mut #return_types {
-                #bodies
-            }
-        }
-    )*})
-}
diff --git a/impl/src/as_ref.rs b/impl/src/as_ref.rs
deleted file mode 100644
index 71f1259..0000000
--- a/impl/src/as_ref.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use crate::utils::{
-    add_where_clauses_for_new_ident, AttrParams, MultiFieldData, State,
-};
-use proc_macro2::TokenStream;
-use quote::{format_ident, quote};
-use syn::{parse::Result, DeriveInput};
-
-pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result<TokenStream> {
-    let as_ref_type = format_ident!("__AsRefT");
-    let state = State::with_type_bound(
-        input,
-        trait_name,
-        "as_ref".into(),
-        AttrParams::ignore_and_forward(),
-        false,
-    )?;
-    let MultiFieldData {
-        fields,
-        input_type,
-        members,
-        infos,
-        trait_path,
-        impl_generics,
-        ty_generics,
-        where_clause,
-        ..
-    } = state.enabled_fields_data();
-    let sub_items: Vec<_> = infos
-        .iter()
-        .zip(members.iter())
-        .zip(fields)
-        .map(|((info, member), field)| {
-            let field_type = &field.ty;
-            if info.forward {
-                let trait_path = quote! { #trait_path<#as_ref_type> };
-                let type_where_clauses = quote! {
-                    where #field_type: #trait_path
-                };
-                let new_generics = add_where_clauses_for_new_ident(
-                    &input.generics,
-                    &[field],
-                    &as_ref_type,
-                    type_where_clauses,
-                    false,
-                );
-                let (impl_generics, _, where_clause) = new_generics.split_for_impl();
-                let casted_trait = quote! { <#field_type as #trait_path> };
-                (
-                    quote! { #casted_trait::as_ref(&#member) },
-                    quote! { #impl_generics },
-                    quote! { #where_clause },
-                    quote! { #trait_path },
-                    quote! { #as_ref_type },
-                )
-            } else {
-                (
-                    quote! { &#member },
-                    quote! { #impl_generics },
-                    quote! { #where_clause },
-                    quote! { #trait_path<#field_type> },
-                    quote! { #field_type },
-                )
-            }
-        })
-        .collect();
-    let bodies = sub_items.iter().map(|i| &i.0);
-    let impl_generics = sub_items.iter().map(|i| &i.1);
-    let where_clauses = sub_items.iter().map(|i| &i.2);
-    let trait_paths = sub_items.iter().map(|i| &i.3);
-    let return_types = sub_items.iter().map(|i| &i.4);
-
-    Ok(quote! {#(
-        #[automatically_derived]
-        impl #impl_generics #trait_paths for #input_type #ty_generics #where_clauses {
-            #[inline]
-            fn as_ref(&self) -> &#return_types {
-                #bodies
-            }
-        }
-    )*})
-}
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index 8cb86b1..ea1c801 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -21,10 +21,8 @@ mod add_assign_like;
 mod add_helpers;
 #[cfg(any(feature = "add", feature = "mul"))]
 mod add_like;
-#[cfg(feature = "as_mut")]
-mod as_mut;
-#[cfg(feature = "as_ref")]
-mod as_ref;
+#[cfg(any(feature = "as_ref", feature = "as_mut"))]
+mod r#as;
 #[cfg(feature = "constructor")]
 mod constructor;
 #[cfg(feature = "deref")]
@@ -135,9 +133,9 @@ create_derive!(
     bit_xor_assign_derive,
 );
 
-create_derive!("as_mut", as_mut, AsMut, as_mut_derive, as_mut);
+create_derive!("as_mut", r#as::r#mut, AsMut, as_mut_derive, as_mut);
 
-create_derive!("as_ref", as_ref, AsRef, as_ref_derive, as_ref);
+create_derive!("as_ref", r#as::r#ref, AsRef, as_ref_derive, as_ref);
 
 create_derive!("constructor", constructor, Constructor, constructor_derive);
 
diff --git a/impl/src/utils.rs b/impl/src/utils.rs
index cd970e2..43e5071 100644
--- a/impl/src/utils.rs
+++ b/impl/src/utils.rs
@@ -14,14 +14,19 @@ use syn::{
 };
 
 #[cfg(any(
+    feature = "as_ref",
+    feature = "as_mut",
     feature = "debug",
     feature = "display",
     feature = "from",
     feature = "into",
 ))]
 pub(crate) use self::either::Either;
+
 #[cfg(any(feature = "from", feature = "into"))]
 pub(crate) use self::fields_ext::FieldsExt;
+#[cfg(any(feature = "as_ref", feature = "as_mut"))]
+pub(crate) use self::spanning::Spanning;
 
 #[derive(Clone, Copy, Default)]
 pub struct DeterministicState;
@@ -289,10 +294,6 @@ impl AttrParams {
             field: vec![],
         }
     }
-
-    pub fn ignore_and_forward() -> AttrParams {
-        AttrParams::new(vec!["ignore", "forward"])
-    }
 }
 
 impl<'input> State<'input> {
@@ -355,22 +356,6 @@ impl<'input> State<'input> {
         State::new_impl(input, trait_name, trait_attr, allowed_attr_params, true)
     }
 
-    pub fn with_type_bound<'arg_input>(
-        input: &'arg_input DeriveInput,
-        trait_name: &'static str,
-        trait_attr: String,
-        allowed_attr_params: AttrParams,
-        add_type_bound: bool,
-    ) -> Result<State<'arg_input>> {
-        Self::new_impl(
-            input,
-            trait_name,
-            trait_attr,
-            allowed_attr_params,
-            add_type_bound,
-        )
-    }
-
     fn new_impl<'arg_input>(
         input: &'arg_input DeriveInput,
         trait_name: &'static str,
@@ -1333,7 +1318,99 @@ pub fn is_type_parameter_used_in_type(
     }
 }
 
+#[cfg(any(feature = "as_ref", feature = "as_mut"))]
+pub(crate) mod forward {
+    use syn::{
+        parse::{Parse, ParseStream},
+        spanned::Spanned as _,
+    };
+
+    use super::Spanning;
+
+    /// Representation of a `forward` attribute.
+    ///
+    /// ```rust,ignore
+    /// #[<attribute>(forward)]
+    /// ```
+    pub(crate) struct Attribute;
+
+    impl Parse for Attribute {
+        fn parse(content: ParseStream<'_>) -> syn::Result<Self> {
+            match content.parse::<syn::Path>()? {
+                p if p.is_ident("forward") => Ok(Self),
+                p => Err(syn::Error::new(p.span(), "only `forward` allowed here")),
+            }
+        }
+    }
+
+    impl Attribute {
+        /// Parses an [`Attribute`] from the provided [`syn::Attribute`]s, preserving its [`Span`].
+        ///
+        /// [`Span`]: proc_macro2::Span
+        pub(crate) fn parse_attrs(
+            attrs: impl AsRef<[syn::Attribute]>,
+            attr_ident: &syn::Ident,
+        ) -> syn::Result<Option<Spanning<Self>>> {
+            attrs
+                .as_ref()
+                .iter()
+                .filter(|attr| attr.path().is_ident(attr_ident))
+                .try_fold(None, |mut attrs, attr| {
+                    let parsed = Spanning::new(attr.parse_args::<Self>()?, attr.span());
+                    if attrs.replace(parsed).is_some() {
+                        Err(syn::Error::new(
+                            attr.span(),
+                            format!(
+                                "only single `#[{attr_ident}(forward)]` attribute is allowed here",
+                            ),
+                        ))
+                    } else {
+                        Ok(attrs)
+                    }
+                })
+        }
+    }
+}
+
+#[cfg(any(feature = "as_ref", feature = "as_mut"))]
+pub(crate) mod skip {
+    use syn::{
+        parse::{Parse, ParseStream},
+        spanned::Spanned as _,
+    };
+
+    /// Representation of a `skip`/`ignore` attribute.
+    ///
+    /// ```rust,ignore
+    /// #[<attribute>(skip)]
+    /// #[<attribute>(ignore)]
+    /// ```
+    pub(crate) struct Attribute(&'static str);
+
+    impl Parse for Attribute {
+        fn parse(content: ParseStream<'_>) -> syn::Result<Self> {
+            match content.parse::<syn::Path>()? {
+                p if p.is_ident("skip") => Ok(Self("skip")),
+                p if p.is_ident("ignore") => Ok(Self("ignore")),
+                p => Err(syn::Error::new(
+                    p.span(),
+                    "only `skip`/`ignore` allowed here",
+                )),
+            }
+        }
+    }
+
+    impl Attribute {
+        /// Returns the concrete name of this attribute (`skip` or `ignore`).
+        pub(crate) const fn name(&self) -> &'static str {
+            self.0
+        }
+    }
+}
+
 #[cfg(any(
+    feature = "as_ref",
+    feature = "as_mut",
     feature = "debug",
     feature = "display",
     feature = "from",
@@ -1348,6 +1425,7 @@ mod either {
     ///
     /// [`Left`]: Either::Left
     /// [`Right`]: Either::Right
+    #[derive(Clone, Copy, Debug)]
     pub(crate) enum Either<L, R> {
         /// Left variant.
         Left(L),
@@ -1399,6 +1477,81 @@ mod either {
     }
 }
 
+#[cfg(any(feature = "as_ref", feature = "as_mut"))]
+mod spanning {
+    use std::ops::{Deref, DerefMut};
+
+    use proc_macro2::Span;
+
+    /// Wrapper for non-[`Spanned`] types to hold their [`Span`].
+    ///
+    /// [`Spanned`]: syn::spanned::Spanned
+    #[derive(Clone, Copy, Debug)]
+    pub(crate) struct Spanning<T: ?Sized> {
+        /// [`Span`] of the `item`.
+        pub(crate) span: Span,
+
+        /// Item the [`Span`] is held for.
+        pub(crate) item: T,
+    }
+
+    impl<T: ?Sized> Spanning<T> {
+        /// Creates a new [`Spanning`] `item`, attaching the provided [`Span`] to it.
+        pub(crate) const fn new(item: T, span: Span) -> Self
+        where
+            T: Sized,
+        {
+            Self { span, item }
+        }
+
+        /// Destructures this [`Spanning`] wrapper returning the underlying `item`.
+        pub fn into_inner(self) -> T
+        where
+            T: Sized,
+        {
+            self.item
+        }
+
+        /// Returns the [`Span`] contained in this [`Spanning`] wrapper.
+        pub(crate) const fn span(&self) -> Span {
+            self.span
+        }
+
+        /// Converts this `&`[`Spanning`]`<T>` into [`Spanning`]`<&T>` (moves the reference inside).
+        pub(crate) const fn as_ref(&self) -> Spanning<&T> {
+            Spanning {
+                span: self.span,
+                item: &self.item,
+            }
+        }
+
+        /// Maps the wrapped `item` with the provided `f`unction, preserving the current [`Span`].
+        pub(crate) fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanning<U>
+        where
+            T: Sized,
+        {
+            Spanning {
+                span: self.span,
+                item: f(self.item),
+            }
+        }
+    }
+
+    impl<T: ?Sized> Deref for Spanning<T> {
+        type Target = T;
+
+        fn deref(&self) -> &Self::Target {
+            &self.item
+        }
+    }
+
+    impl<T: ?Sized> DerefMut for Spanning<T> {
+        fn deref_mut(&mut self) -> &mut Self::Target {
+            &mut self.item
+        }
+    }
+}
+
 #[cfg(any(feature = "from", feature = "into"))]
 mod fields_ext {
     use std::{cmp, iter};
@@ -1497,8 +1650,7 @@ mod fields_ext {
                         return Err(syn::Error::new(
                             other.span(),
                             format!(
-                                "expected tuple: `({}, {})`",
-                                other,
+                                "expected tuple: `({other}, {})`",
                                 (0..(self.len() - 1))
                                     .map(|_| "_")
                                     .collect::<Vec<_>>()


Test patch:
diff --git a/tests/as_mut.rs b/tests/as_mut.rs
index 2a973d4..846ab11 100644
--- a/tests/as_mut.rs
+++ b/tests/as_mut.rs
@@ -1,117 +1,535 @@
 #![cfg_attr(not(feature = "std"), no_std)]
-#![allow(dead_code)]
+#![allow(dead_code, clippy::unnecessary_mut_passed)]
 
 #[cfg(not(feature = "std"))]
 extern crate alloc;
 
 #[cfg(not(feature = "std"))]
-use alloc::{string::String, vec, vec::Vec};
+use alloc::{borrow::ToOwned, collections::VecDeque, string::String, vec, vec::Vec};
+
+#[cfg(feature = "std")]
+use std::collections::VecDeque;
+
 use core::ptr;
 
 use derive_more::AsMut;
 
-#[derive(AsMut)]
-struct SingleFieldTuple(String);
+mod single_field {
+    use super::*;
 
-#[test]
-fn single_field_tuple() {
-    let mut item = SingleFieldTuple("test".into());
+    mod tuple {
+        use super::*;
 
-    assert!(ptr::eq(&mut item.0, item.as_mut()));
-}
+        #[derive(AsMut)]
+        struct Nothing(String);
 
-#[derive(AsMut)]
-#[as_mut(forward)]
-struct SingleFieldForward(Vec<i32>);
+        #[test]
+        fn nothing() {
+            let mut item = Nothing("test".to_owned());
 
-#[test]
-fn single_field_forward() {
-    let mut item = SingleFieldForward(vec![]);
-    let _: &mut [i32] = (&mut item).as_mut();
-}
+            assert!(ptr::eq(item.as_mut(), &mut item.0));
+        }
 
-#[derive(AsMut)]
-struct SingleFieldStruct {
-    first: String,
-}
+        #[derive(AsMut)]
+        #[as_mut(forward)]
+        struct Forward(String);
 
-#[test]
-fn single_field_struct() {
-    let mut item = SingleFieldStruct {
-        first: "test".into(),
-    };
+        #[test]
+        fn forward() {
+            let mut item = Forward("test".to_owned());
 
-    assert!(ptr::eq(&mut item.first, item.as_mut()));
-}
+            let rf: &mut str = item.as_mut();
+            assert!(ptr::eq(rf, item.0.as_mut()));
+        }
 
-#[cfg(feature = "std")]
-mod pathbuf {
-    use std::path::PathBuf;
+        #[derive(AsMut)]
+        struct Field(#[as_mut] String);
 
-    use super::*;
+        #[test]
+        fn field() {
+            let mut item = Field("test".to_owned());
 
-    #[derive(AsMut)]
-    struct MultiFieldTuple(#[as_mut] String, #[as_mut] PathBuf, Vec<usize>);
+            assert!(ptr::eq(item.as_mut(), &mut item.0));
+        }
 
-    #[test]
-    fn multi_field_tuple() {
-        let mut item = MultiFieldTuple("test".into(), PathBuf::new(), vec![]);
+        #[derive(AsMut)]
+        struct FieldForward(#[as_mut(forward)] String);
 
-        assert!(ptr::eq(&mut item.0, item.as_mut()));
-        assert!(ptr::eq(&mut item.1, item.as_mut()));
-    }
+        #[test]
+        fn field_forward() {
+            let mut item = FieldForward("test".to_owned());
 
-    #[derive(AsMut)]
-    struct MultiFieldStruct {
-        #[as_mut]
-        first: String,
-        #[as_mut]
-        second: PathBuf,
-        third: Vec<usize>,
-    }
+            let rf: &mut str = item.as_mut();
+            assert!(ptr::eq(rf, item.0.as_mut()));
+        }
 
-    #[test]
-    fn multi_field_struct() {
-        let mut item = MultiFieldStruct {
-            first: "test".into(),
-            second: PathBuf::new(),
-            third: vec![],
-        };
+        mod generic {
+            use super::*;
 
-        assert!(ptr::eq(&mut item.first, item.as_mut()));
-        assert!(ptr::eq(&mut item.second, item.as_mut()));
-    }
+            #[derive(AsMut)]
+            struct Nothing<T>(T);
+
+            #[test]
+            fn nothing() {
+                let mut item = Nothing("test".to_owned());
 
-    #[derive(AsMut)]
-    struct MultiFieldGenericStruct<T> {
-        #[as_mut]
-        first: Vec<T>,
-        #[as_mut]
-        second: PathBuf,
-        third: Vec<usize>,
+                assert!(ptr::eq(item.as_mut(), &mut item.0));
+            }
+
+            #[derive(AsMut)]
+            #[as_mut(forward)]
+            struct Forward<T>(T);
+
+            #[test]
+            fn forward() {
+                let mut item = Forward("test".to_owned());
+
+                let rf: &mut str = item.as_mut();
+                assert!(ptr::eq(rf, item.0.as_mut()));
+            }
+
+            #[derive(AsMut)]
+            struct Field<T>(#[as_mut] T);
+
+            #[test]
+            fn field() {
+                let mut item = Field("test".to_owned());
+
+                assert!(ptr::eq(item.as_mut(), &mut item.0));
+            }
+
+            #[derive(AsMut)]
+            struct FieldForward<T>(#[as_mut(forward)] T);
+
+            #[test]
+            fn field_forward() {
+                let mut item = FieldForward("test".to_owned());
+
+                let rf: &mut str = item.as_mut();
+                assert!(ptr::eq(rf, item.0.as_mut()));
+            }
+        }
     }
 
-    #[test]
-    fn multi_field_generic_struct() {
-        let mut item = MultiFieldGenericStruct {
-            first: b"test".to_vec(),
-            second: PathBuf::new(),
-            third: vec![],
-        };
+    mod named {
+        use super::*;
+
+        #[derive(AsMut)]
+        struct Nothing {
+            first: String,
+        }
+
+        #[test]
+        fn nothing() {
+            let mut item = Nothing {
+                first: "test".to_owned(),
+            };
+
+            assert!(ptr::eq(item.as_mut(), &mut item.first));
+        }
+
+        #[derive(AsMut)]
+        #[as_mut(forward)]
+        struct Forward {
+            first: String,
+        }
 
-        assert!(ptr::eq(&mut item.first, item.as_mut()));
-        assert!(ptr::eq(&mut item.second, item.as_mut()));
+        #[test]
+        fn forward() {
+            let mut item = Forward {
+                first: "test".to_owned(),
+            };
+
+            let rf: &mut str = item.as_mut();
+            assert!(ptr::eq(rf, item.first.as_mut()));
+        }
+
+        #[derive(AsMut)]
+        struct Field {
+            #[as_mut]
+            first: String,
+        }
+
+        #[test]
+        fn field() {
+            let mut item = Field {
+                first: "test".to_owned(),
+            };
+
+            assert!(ptr::eq(item.as_mut(), &mut item.first));
+        }
+
+        #[derive(AsMut)]
+        struct FieldForward {
+            #[as_mut(forward)]
+            first: String,
+        }
+
+        #[test]
+        fn field_forward() {
+            let mut item = FieldForward {
+                first: "test".to_owned(),
+            };
+
+            let rf: &mut str = item.as_mut();
+            assert!(ptr::eq(rf, item.first.as_mut()));
+        }
+
+        mod generic {
+            use super::*;
+
+            #[derive(AsMut)]
+            struct Nothing<T> {
+                first: T,
+            }
+
+            #[test]
+            fn nothing() {
+                let mut item = Nothing {
+                    first: "test".to_owned(),
+                };
+
+                assert!(ptr::eq(item.as_mut(), &mut item.first));
+            }
+
+            #[derive(AsMut)]
+            #[as_mut(forward)]
+            struct Forward<T> {
+                first: T,
+            }
+
+            #[test]
+            fn struct_forward() {
+                let mut item = Forward {
+                    first: "test".to_owned(),
+                };
+
+                let rf: &mut str = item.as_mut();
+                assert!(ptr::eq(rf, item.first.as_mut()));
+            }
+
+            #[derive(AsMut)]
+            struct Field<T> {
+                #[as_mut]
+                first: T,
+            }
+
+            #[test]
+            fn field() {
+                let mut item = Field {
+                    first: "test".to_owned(),
+                };
+
+                assert!(ptr::eq(item.as_mut(), &mut item.first));
+            }
+
+            #[derive(AsMut)]
+            struct FieldForward<T> {
+                #[as_mut(forward)]
+                first: T,
+            }
+
+            #[test]
+            fn field_forward() {
+                let mut item = FieldForward {
+                    first: "test".to_owned(),
+                };
+
+                let rf: &mut str = item.as_mut();
+                assert!(ptr::eq(rf, item.first.as_mut()));
+            }
+        }
     }
 }
 
-#[derive(AsMut)]
-struct SingleFieldGenericStruct<T> {
-    first: T,
-}
+mod multi_field {
+    use super::*;
+
+    mod tuple {
+        use super::*;
+
+        #[derive(AsMut)]
+        struct Nothing(String, i32);
+
+        #[test]
+        fn nothing() {
+            let mut item = Nothing("test".to_owned(), 0);
+
+            assert!(ptr::eq(item.as_mut(), &mut item.0));
+            assert!(ptr::eq(item.as_mut(), &mut item.1));
+        }
 
-#[test]
-fn single_field_generic_struct() {
-    let mut item = SingleFieldGenericStruct { first: "test" };
+        #[derive(AsMut)]
+        struct Skip(String, i32, #[as_mut(skip)] f64);
 
-    assert!(ptr::eq(&mut item.first, item.as_mut()));
+        // Asserts that the macro expansion doesn't generate `AsMut` impl for the skipped field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsMut<f64> for Skip {
+            fn as_mut(&mut self) -> &mut f64 {
+                &mut self.2
+            }
+        }
+
+        #[test]
+        fn skip() {
+            let mut item = Skip("test".to_owned(), 0, 0.0);
+
+            assert!(ptr::eq(item.as_mut(), &mut item.0));
+            assert!(ptr::eq(item.as_mut(), &mut item.1));
+        }
+
+        #[derive(AsMut)]
+        struct Field(#[as_mut] String, #[as_mut] i32, f64);
+
+        // Asserts that the macro expansion doesn't generate `AsMut` impl for the third field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsMut<f64> for Field {
+            fn as_mut(&mut self) -> &mut f64 {
+                &mut self.2
+            }
+        }
+
+        #[test]
+        fn field() {
+            let mut item = Field("test".to_owned(), 0, 0.0);
+
+            assert!(ptr::eq(item.as_mut(), &mut item.0));
+            assert!(ptr::eq(item.as_mut(), &mut item.1));
+        }
+
+        #[derive(AsMut)]
+        struct FieldForward(#[as_mut(forward)] String, i32);
+
+        #[test]
+        fn field_forward() {
+            let mut item = FieldForward("test".to_owned(), 0);
+
+            let rf: &mut str = item.as_mut();
+            assert!(ptr::eq(rf, item.0.as_mut()));
+        }
+
+        mod generic {
+            use super::*;
+
+            #[derive(AsMut)]
+            struct Nothing<T, U>(Vec<T>, VecDeque<U>);
+
+            #[test]
+            fn nothing() {
+                let mut item = Nothing(vec![1], VecDeque::from([2]));
+
+                assert!(ptr::eq(item.as_mut(), &mut item.0));
+                assert!(ptr::eq(item.as_mut(), &mut item.1));
+            }
+
+            #[derive(AsMut)]
+            struct Skip<T, U, V>(Vec<T>, VecDeque<U>, #[as_mut(skip)] V);
+
+            #[test]
+            fn skip() {
+                let mut item = Skip(vec![1], VecDeque::from([2]), 0);
+
+                assert!(ptr::eq(item.as_mut(), &mut item.0));
+                assert!(ptr::eq(item.as_mut(), &mut item.1));
+            }
+
+            #[derive(AsMut)]
+            struct Field<T, U, V>(#[as_mut] Vec<T>, #[as_mut] VecDeque<U>, V);
+
+            #[test]
+            fn field() {
+                let mut item = Field(vec![1], VecDeque::from([2]), 0);
+
+                assert!(ptr::eq(item.as_mut(), &mut item.0));
+                assert!(ptr::eq(item.as_mut(), &mut item.1));
+            }
+
+            #[derive(AsMut)]
+            struct FieldForward<T, U>(#[as_mut(forward)] T, U);
+
+            #[test]
+            fn field_forward() {
+                let mut item = FieldForward("test".to_owned(), 0);
+
+                let rf: &mut str = item.as_mut();
+                assert!(ptr::eq(rf, item.0.as_mut()));
+            }
+        }
+    }
+
+    mod named {
+        use super::*;
+
+        #[derive(AsMut)]
+        struct Nothing {
+            first: String,
+            second: i32,
+        }
+
+        #[test]
+        fn nothing() {
+            let mut item = Nothing {
+                first: "test".to_owned(),
+                second: 0,
+            };
+
+            assert!(ptr::eq(item.as_mut(), &mut item.first));
+            assert!(ptr::eq(item.as_mut(), &mut item.second));
+        }
+
+        #[derive(AsMut)]
+        struct Skip {
+            first: String,
+            second: i32,
+            #[as_mut(skip)]
+            third: f64,
+        }
+
+        // Asserts that the macro expansion doesn't generate `AsMut` impl for the skipped field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsMut<f64> for Skip {
+            fn as_mut(&mut self) -> &mut f64 {
+                &mut self.third
+            }
+        }
+
+        #[test]
+        fn skip() {
+            let mut item = Skip {
+                first: "test".to_owned(),
+                second: 0,
+                third: 0.0,
+            };
+
+            assert!(ptr::eq(item.as_mut(), &mut item.first));
+            assert!(ptr::eq(item.as_mut(), &mut item.second));
+        }
+
+        #[derive(AsMut)]
+        struct Field {
+            #[as_mut]
+            first: String,
+            #[as_mut]
+            second: i32,
+            third: f64,
+        }
+
+        // Asserts that the macro expansion doesn't generate `AsMut` impl for the `third` field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsMut<f64> for Field {
+            fn as_mut(&mut self) -> &mut f64 {
+                &mut self.third
+            }
+        }
+
+        #[test]
+        fn field() {
+            let mut item = Field {
+                first: "test".to_owned(),
+                second: 0,
+                third: 0.0,
+            };
+
+            assert!(ptr::eq(item.as_mut(), &mut item.first));
+            assert!(ptr::eq(item.as_mut(), &mut item.second));
+        }
+
+        #[derive(AsMut)]
+        struct FieldForward {
+            #[as_mut(forward)]
+            first: String,
+            second: i32,
+        }
+
+        #[test]
+        fn field_forward() {
+            let mut item = FieldForward {
+                first: "test".to_owned(),
+                second: 0,
+            };
+
+            let rf: &mut str = item.as_mut();
+            assert!(ptr::eq(rf, item.first.as_mut()));
+        }
+
+        mod generic {
+            use super::*;
+
+            #[derive(AsMut)]
+            struct Nothing<T, U> {
+                first: Vec<T>,
+                second: VecDeque<U>,
+            }
+
+            #[test]
+            fn nothing() {
+                let mut item = Nothing {
+                    first: vec![1],
+                    second: VecDeque::from([2]),
+                };
+
+                assert!(ptr::eq(item.as_mut(), &mut item.first));
+                assert!(ptr::eq(item.as_mut(), &mut item.second));
+            }
+
+            #[derive(AsMut)]
+            struct Skip<T, U, V> {
+                first: Vec<T>,
+                second: VecDeque<U>,
+                #[as_mut(skip)]
+                third: V,
+            }
+
+            #[test]
+            fn skip() {
+                let mut item = Skip {
+                    first: vec![1],
+                    second: VecDeque::from([2]),
+                    third: 0,
+                };
+
+                assert!(ptr::eq(item.as_mut(), &mut item.first));
+                assert!(ptr::eq(item.as_mut(), &mut item.second));
+            }
+
+            #[derive(AsMut)]
+            struct Field<T, U, V> {
+                #[as_mut]
+                first: Vec<T>,
+                #[as_mut]
+                second: VecDeque<U>,
+                third: V,
+            }
+
+            #[test]
+            fn field() {
+                let mut item = Field {
+                    first: vec![1],
+                    second: VecDeque::from([2]),
+                    third: 0,
+                };
+
+                assert!(ptr::eq(item.as_mut(), &mut item.first));
+                assert!(ptr::eq(item.as_mut(), &mut item.second));
+            }
+
+            #[derive(AsMut)]
+            struct FieldForward<T, U> {
+                #[as_mut(forward)]
+                first: T,
+                second: U,
+            }
+
+            #[test]
+            fn field_forward() {
+                let mut item = FieldForward {
+                    first: "test".to_owned(),
+                    second: 0,
+                };
+
+                let rf: &mut str = item.as_mut();
+                assert!(ptr::eq(rf, item.first.as_mut()));
+            }
+        }
+    }
 }
diff --git a/tests/as_ref.rs b/tests/as_ref.rs
index d28f934..3551245 100644
--- a/tests/as_ref.rs
+++ b/tests/as_ref.rs
@@ -5,113 +5,531 @@
 extern crate alloc;
 
 #[cfg(not(feature = "std"))]
-use alloc::{string::String, vec, vec::Vec};
+use alloc::{borrow::ToOwned, collections::VecDeque, string::String, vec, vec::Vec};
+
+#[cfg(feature = "std")]
+use std::collections::VecDeque;
+
 use core::ptr;
 
 use derive_more::AsRef;
 
-#[derive(AsRef)]
-struct SingleFieldTuple(String);
+mod single_field {
+    use super::*;
 
-#[test]
-fn single_field_tuple() {
-    let item = SingleFieldTuple("test".into());
+    mod tuple {
+        use super::*;
 
-    assert!(ptr::eq(&item.0, item.as_ref()));
-}
+        #[derive(AsRef)]
+        struct Nothing(String);
 
-#[derive(AsRef)]
-#[as_ref(forward)]
-struct SingleFieldForward(Vec<i32>);
+        #[test]
+        fn nothing() {
+            let item = Nothing("test".to_owned());
 
-#[test]
-fn single_field_forward() {
-    let item = SingleFieldForward(vec![]);
-    let _: &[i32] = (&item).as_ref();
-}
+            assert!(ptr::eq(item.as_ref(), &item.0));
+        }
 
-#[derive(AsRef)]
-struct SingleFieldStruct {
-    first: String,
-}
+        #[derive(AsRef)]
+        #[as_ref(forward)]
+        struct Forward(String);
 
-#[test]
-fn single_field_struct() {
-    let item = SingleFieldStruct {
-        first: "test".into(),
-    };
+        #[test]
+        fn forward() {
+            let item = Forward("test".to_owned());
 
-    assert!(ptr::eq(&item.first, item.as_ref()));
-}
+            let rf: &str = item.as_ref();
+            assert!(ptr::eq(rf, item.0.as_ref()));
+        }
 
-#[cfg(feature = "std")]
-mod pathbuf {
-    use std::path::PathBuf;
+        #[derive(AsRef)]
+        struct Field(#[as_ref] String);
 
-    use super::*;
+        #[test]
+        fn field() {
+            let item = Field("test".to_owned());
 
-    #[derive(AsRef)]
-    struct MultiFieldTuple(#[as_ref] String, #[as_ref] PathBuf, Vec<usize>);
+            assert!(ptr::eq(item.as_ref(), &item.0));
+        }
 
-    #[test]
-    fn multi_field_tuple() {
-        let item = MultiFieldTuple("test".into(), PathBuf::new(), vec![]);
+        #[derive(AsRef)]
+        struct FieldForward(#[as_ref(forward)] String);
 
-        assert!(ptr::eq(&item.0, item.as_ref()));
-        assert!(ptr::eq(&item.1, item.as_ref()));
-    }
+        #[test]
+        fn field_forward() {
+            let item = FieldForward("test".to_owned());
+
+            let rf: &str = item.as_ref();
+            assert!(ptr::eq(rf, item.0.as_ref()));
+        }
+
+        mod generic {
+            use super::*;
+
+            #[derive(AsRef)]
+            struct Nothing<T>(T);
 
-    #[derive(AsRef)]
-    struct MultiFieldStruct {
-        #[as_ref]
-        first: String,
-        #[as_ref]
-        second: PathBuf,
-        third: Vec<usize>,
+            #[test]
+            fn nothing() {
+                let item = Nothing("test".to_owned());
+
+                assert!(ptr::eq(item.as_ref(), &item.0));
+            }
+
+            #[derive(AsRef)]
+            #[as_ref(forward)]
+            struct Forward<T>(T);
+
+            #[test]
+            fn forward() {
+                let item = Forward("test".to_owned());
+
+                let rf: &str = item.as_ref();
+                assert!(ptr::eq(rf, item.0.as_ref()));
+            }
+
+            #[derive(AsRef)]
+            struct Field<T>(#[as_ref] T);
+
+            #[test]
+            fn field() {
+                let item = Field("test".to_owned());
+
+                assert!(ptr::eq(item.as_ref(), &item.0));
+            }
+
+            #[derive(AsRef)]
+            struct FieldForward<T>(#[as_ref(forward)] T);
+
+            #[test]
+            fn field_forward() {
+                let item = FieldForward("test".to_owned());
+
+                let rf: &str = item.as_ref();
+                assert!(ptr::eq(rf, item.0.as_ref()));
+            }
+        }
     }
 
-    #[test]
-    fn multi_field_struct() {
-        let item = MultiFieldStruct {
-            first: "test".into(),
-            second: PathBuf::new(),
-            third: vec![],
-        };
+    mod named {
+        use super::*;
+
+        #[derive(AsRef)]
+        struct Nothing {
+            first: String,
+        }
+
+        #[test]
+        fn nothing() {
+            let item = Nothing {
+                first: "test".to_owned(),
+            };
+
+            assert!(ptr::eq(item.as_ref(), &item.first));
+        }
 
-        assert!(ptr::eq(&item.first, item.as_ref()));
-        assert!(ptr::eq(&item.second, item.as_ref()));
+        #[derive(AsRef)]
+        #[as_ref(forward)]
+        struct Forward {
+            first: String,
+        }
+
+        #[test]
+        fn forward() {
+            let item = Forward {
+                first: "test".to_owned(),
+            };
+
+            let rf: &str = item.as_ref();
+            assert!(ptr::eq(rf, item.first.as_ref()));
+        }
+
+        #[derive(AsRef)]
+        struct Field {
+            #[as_ref]
+            first: String,
+        }
+
+        #[test]
+        fn field() {
+            let item = Field {
+                first: "test".to_owned(),
+            };
+
+            assert!(ptr::eq(item.as_ref(), &item.first));
+        }
+
+        #[derive(AsRef)]
+        struct FieldForward {
+            #[as_ref(forward)]
+            first: String,
+        }
+
+        #[test]
+        fn field_forward() {
+            let item = FieldForward {
+                first: "test".to_owned(),
+            };
+
+            let rf: &str = item.as_ref();
+            assert!(ptr::eq(rf, item.first.as_ref()));
+        }
+
+        mod generic {
+            use super::*;
+
+            #[derive(AsRef)]
+            struct Nothing<T> {
+                first: T,
+            }
+
+            #[test]
+            fn nothing() {
+                let item = Nothing {
+                    first: "test".to_owned(),
+                };
+
+                assert!(ptr::eq(item.as_ref(), &item.first));
+            }
+
+            #[derive(AsRef)]
+            #[as_ref(forward)]
+            struct Forward<T> {
+                first: T,
+            }
+
+            #[test]
+            fn forward() {
+                let item = Forward {
+                    first: "test".to_owned(),
+                };
+
+                let rf: &str = item.as_ref();
+                assert!(ptr::eq(rf, item.first.as_ref()));
+            }
+
+            #[derive(AsRef)]
+            struct Field<T> {
+                #[as_ref]
+                first: T,
+            }
+
+            #[test]
+            fn field() {
+                let item = Field {
+                    first: "test".to_owned(),
+                };
+
+                assert!(ptr::eq(item.as_ref(), &item.first));
+            }
+
+            #[derive(AsRef)]
+            struct FieldForward<T> {
+                #[as_ref(forward)]
+                first: T,
+            }
+
+            #[test]
+            fn field_forward() {
+                let item = FieldForward {
+                    first: "test".to_owned(),
+                };
+
+                let rf: &str = item.as_ref();
+                assert!(ptr::eq(rf, item.first.as_ref()));
+            }
+        }
     }
 }
 
-#[derive(AsRef)]
-struct SingleFieldGenericStruct<T> {
-    first: T,
-}
+mod multi_field {
+    use super::*;
 
-#[test]
-fn single_field_generic_struct() {
-    let item = SingleFieldGenericStruct { first: "test" };
+    mod tuple {
+        use super::*;
 
-    assert!(ptr::eq(&item.first, item.as_ref()));
-}
+        #[derive(AsRef)]
+        struct Nothing(String, i32);
 
-#[derive(AsRef)]
-struct MultiFieldGenericStruct<T, U> {
-    #[as_ref]
-    first: Vec<T>,
-    #[as_ref]
-    second: [U; 2],
-    third: Vec<usize>,
-}
+        #[test]
+        fn nothing() {
+            let item = Nothing("test".to_owned(), 0);
+
+            assert!(ptr::eq(item.as_ref(), &item.0));
+            assert!(ptr::eq(item.as_ref(), &item.1));
+        }
 
-#[test]
-fn multi_field_generic_struct() {
-    let item = MultiFieldGenericStruct {
-        first: b"test".to_vec(),
-        second: [0i32, 1i32],
-        third: vec![],
-    };
+        #[derive(AsRef)]
+        struct Skip(String, i32, #[as_ref(skip)] f64);
 
-    assert!(ptr::eq(&item.first, item.as_ref()));
-    assert!(ptr::eq(&item.second, item.as_ref()));
+        // Asserts that the macro expansion doesn't generate `AsRef` impl for the skipped field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsRef<f64> for Skip {
+            fn as_ref(&self) -> &f64 {
+                &self.2
+            }
+        }
+
+        #[test]
+        fn skip() {
+            let item = Skip("test".to_owned(), 0, 0.0);
+
+            assert!(ptr::eq(item.as_ref(), &item.0));
+            assert!(ptr::eq(item.as_ref(), &item.1));
+        }
+
+        #[derive(AsRef)]
+        struct Field(#[as_ref] String, #[as_ref] i32, f64);
+
+        // Asserts that the macro expansion doesn't generate `AsRef` impl for the third field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsRef<f64> for Field {
+            fn as_ref(&self) -> &f64 {
+                &self.2
+            }
+        }
+
+        #[test]
+        fn field() {
+            let item = Field("test".to_owned(), 0, 0.0);
+
+            assert!(ptr::eq(item.as_ref(), &item.0));
+            assert!(ptr::eq(item.as_ref(), &item.1));
+        }
+
+        #[derive(AsRef)]
+        struct FieldForward(#[as_ref(forward)] String, i32);
+
+        #[test]
+        fn field_forward() {
+            let item = FieldForward("test".to_owned(), 0);
+
+            let rf: &str = item.as_ref();
+            assert!(ptr::eq(rf, item.0.as_ref()));
+        }
+
+        mod generic {
+            use super::*;
+
+            #[derive(AsRef)]
+            struct Nothing<T, U>(Vec<T>, VecDeque<U>);
+
+            #[test]
+            fn nothing() {
+                let item = Nothing(vec![1], VecDeque::from([2]));
+
+                assert!(ptr::eq(item.as_ref(), &item.0));
+                assert!(ptr::eq(item.as_ref(), &item.1));
+            }
+
+            #[derive(AsRef)]
+            struct Skip<T, U, V>(Vec<T>, VecDeque<U>, #[as_ref(skip)] V);
+
+            #[test]
+            fn skip() {
+                let item = Skip(vec![1], VecDeque::from([2]), 0);
+
+                assert!(ptr::eq(item.as_ref(), &item.0));
+                assert!(ptr::eq(item.as_ref(), &item.1));
+            }
+
+            #[derive(AsRef)]
+            struct Field<T, U, V>(#[as_ref] Vec<T>, #[as_ref] VecDeque<U>, V);
+
+            #[test]
+            fn field() {
+                let item = Field(vec![1], VecDeque::from([2]), 0);
+
+                assert!(ptr::eq(item.as_ref(), &item.0));
+                assert!(ptr::eq(item.as_ref(), &item.1));
+            }
+
+            #[derive(AsRef)]
+            struct FieldForward<T, U>(#[as_ref(forward)] T, U);
+
+            #[test]
+            fn field_forward() {
+                let item = FieldForward("test".to_owned(), 0);
+
+                let rf: &str = item.as_ref();
+                assert!(ptr::eq(rf, item.0.as_ref()));
+            }
+        }
+    }
+
+    mod named {
+        use super::*;
+
+        #[derive(AsRef)]
+        struct Nothing {
+            first: String,
+            second: i32,
+        }
+
+        #[test]
+        fn nothing() {
+            let item = Nothing {
+                first: "test".to_owned(),
+                second: 0,
+            };
+
+            assert!(ptr::eq(item.as_ref(), &item.first));
+            assert!(ptr::eq(item.as_ref(), &item.second));
+        }
+
+        #[derive(AsRef)]
+        struct Skip {
+            first: String,
+            second: i32,
+            #[as_ref(skip)]
+            third: f64,
+        }
+
+        // Asserts that the macro expansion doesn't generate `AsRef` impl for the skipped field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsRef<f64> for Skip {
+            fn as_ref(&self) -> &f64 {
+                &self.third
+            }
+        }
+
+        #[test]
+        fn skip() {
+            let item = Skip {
+                first: "test".to_owned(),
+                second: 0,
+                third: 0.0,
+            };
+
+            assert!(ptr::eq(item.as_ref(), &item.first));
+            assert!(ptr::eq(item.as_ref(), &item.second));
+        }
+
+        #[derive(AsRef)]
+        struct Field {
+            #[as_ref]
+            first: String,
+            #[as_ref]
+            second: i32,
+            third: f64,
+        }
+
+        // Asserts that the macro expansion doesn't generate `AsRef` impl for the `third` field, by
+        // producing trait implementations conflict error during compilation, if it does.
+        impl AsRef<f64> for Field {
+            fn as_ref(&self) -> &f64 {
+                &self.third
+            }
+        }
+
+        #[test]
+        fn field() {
+            let item = Field {
+                first: "test".to_owned(),
+                second: 0,
+                third: 0.0,
+            };
+
+            assert!(ptr::eq(item.as_ref(), &item.first));
+            assert!(ptr::eq(item.as_ref(), &item.second));
+        }
+
+        #[derive(AsRef)]
+        struct FieldForward {
+            #[as_ref(forward)]
+            first: String,
+            second: i32,
+        }
+
+        #[test]
+        fn field_forward() {
+            let item = FieldForward {
+                first: "test".to_owned(),
+                second: 0,
+            };
+
+            let rf: &str = item.as_ref();
+            assert!(ptr::eq(rf, item.first.as_ref()));
+        }
+
+        mod generic {
+            use super::*;
+
+            #[derive(AsRef)]
+            struct Nothing<T, U> {
+                first: Vec<T>,
+                second: VecDeque<U>,
+            }
+
+            #[test]
+            fn nothing() {
+                let item = Nothing {
+                    first: vec![1],
+                    second: VecDeque::from([2]),
+                };
+
+                assert!(ptr::eq(item.as_ref(), &item.first));
+                assert!(ptr::eq(item.as_ref(), &item.second));
+            }
+
+            #[derive(AsRef)]
+            struct Skip<T, U, V> {
+                first: Vec<T>,
+                second: VecDeque<U>,
+                #[as_ref(skip)]
+                third: V,
+            }
+
+            #[test]
+            fn skip() {
+                let item = Skip {
+                    first: vec![1],
+                    second: VecDeque::from([2]),
+                    third: 0,
+                };
+
+                assert!(ptr::eq(item.as_ref(), &item.first));
+                assert!(ptr::eq(item.as_ref(), &item.second));
+            }
+
+            #[derive(AsRef)]
+            struct Field<T, U, V> {
+                #[as_ref]
+                first: Vec<T>,
+                #[as_ref]
+                second: VecDeque<U>,
+                third: V,
+            }
+
+            #[test]
+            fn field() {
+                let item = Field {
+                    first: vec![1],
+                    second: VecDeque::from([2]),
+                    third: 0,
+                };
+
+                assert!(ptr::eq(item.as_ref(), &item.first));
+                assert!(ptr::eq(item.as_ref(), &item.second));
+            }
+
+            #[derive(AsRef)]
+            struct FieldForward<T, U> {
+                #[as_ref(forward)]
+                first: T,
+                second: U,
+            }
+
+            #[test]
+            fn field_forward() {
+                let item = FieldForward {
+                    first: "test".to_owned(),
+                    second: 0,
+                };
+
+                let rf: &str = item.as_ref();
+                assert!(ptr::eq(rf, item.first.as_ref()));
+            }
+        }
+    }
 }
diff --git a/tests/compile_fail/as_mut/enum.rs b/tests/compile_fail/as_mut/enum.rs
new file mode 100644
index 0000000..c4fc02b
--- /dev/null
+++ b/tests/compile_fail/as_mut/enum.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsMut)]
+enum Foo {
+    Bar(i32),
+    Baz(i32),
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/enum.stderr b/tests/compile_fail/as_mut/enum.stderr
new file mode 100644
index 0000000..59df352
--- /dev/null
+++ b/tests/compile_fail/as_mut/enum.stderr
@@ -0,0 +1,5 @@
+error: `AsMut` cannot be derived for enums
+ --> tests/compile_fail/as_mut/enum.rs:2:1
+  |
+2 | enum Foo {
+  | ^^^^
diff --git a/tests/compile_fail/as_mut/multiple_field_attrs.rs b/tests/compile_fail/as_mut/multiple_field_attrs.rs
new file mode 100644
index 0000000..d4abe47
--- /dev/null
+++ b/tests/compile_fail/as_mut/multiple_field_attrs.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsMut)]
+struct Foo {
+    #[as_mut]
+    #[as_mut(forward)]
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/multiple_field_attrs.stderr b/tests/compile_fail/as_mut/multiple_field_attrs.stderr
new file mode 100644
index 0000000..6e07576
--- /dev/null
+++ b/tests/compile_fail/as_mut/multiple_field_attrs.stderr
@@ -0,0 +1,5 @@
+error: only single `#[as_mut(...)]` attribute is allowed here
+ --> tests/compile_fail/as_mut/multiple_field_attrs.rs:4:5
+  |
+4 |     #[as_mut(forward)]
+  |     ^
diff --git a/tests/compile_fail/as_mut/multiple_struct_attrs.rs b/tests/compile_fail/as_mut/multiple_struct_attrs.rs
new file mode 100644
index 0000000..cf948e4
--- /dev/null
+++ b/tests/compile_fail/as_mut/multiple_struct_attrs.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsMut)]
+#[as_mut(forward)]
+#[as_mut(forward)]
+struct Foo {
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/multiple_struct_attrs.stderr b/tests/compile_fail/as_mut/multiple_struct_attrs.stderr
new file mode 100644
index 0000000..cec25f8
--- /dev/null
+++ b/tests/compile_fail/as_mut/multiple_struct_attrs.stderr
@@ -0,0 +1,5 @@
+error: only single `#[as_mut(forward)]` attribute is allowed here
+ --> tests/compile_fail/as_mut/multiple_struct_attrs.rs:3:1
+  |
+3 | #[as_mut(forward)]
+  | ^
diff --git a/tests/compile_fail/as_mut/skip_and_others.rs b/tests/compile_fail/as_mut/skip_and_others.rs
new file mode 100644
index 0000000..1ceaf35
--- /dev/null
+++ b/tests/compile_fail/as_mut/skip_and_others.rs
@@ -0,0 +1,9 @@
+#[derive(derive_more::AsMut)]
+struct Foo {
+    #[as_mut]
+    bar: i32,
+    #[as_mut(ignore)]
+    baz: f32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/skip_and_others.stderr b/tests/compile_fail/as_mut/skip_and_others.stderr
new file mode 100644
index 0000000..26c1b79
--- /dev/null
+++ b/tests/compile_fail/as_mut/skip_and_others.stderr
@@ -0,0 +1,5 @@
+error: `#[as_mut(ignore)]` cannot be used in the same struct with other `#[as_mut(...)]` attributes
+ --> tests/compile_fail/as_mut/skip_and_others.rs:5:5
+  |
+5 |     #[as_mut(ignore)]
+  |     ^
diff --git a/tests/compile_fail/as_mut/struct_and_field.rs b/tests/compile_fail/as_mut/struct_and_field.rs
new file mode 100644
index 0000000..0593161
--- /dev/null
+++ b/tests/compile_fail/as_mut/struct_and_field.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsMut)]
+#[as_mut(forward)]
+struct Foo {
+    #[as_mut]
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/struct_and_field.stderr b/tests/compile_fail/as_mut/struct_and_field.stderr
new file mode 100644
index 0000000..85f857f
--- /dev/null
+++ b/tests/compile_fail/as_mut/struct_and_field.stderr
@@ -0,0 +1,5 @@
+error: `#[as_mut(...)]` cannot be placed on both struct and its field
+ --> tests/compile_fail/as_mut/struct_and_field.rs:4:5
+  |
+4 |     #[as_mut]
+  |     ^
diff --git a/tests/compile_fail/as_mut/struct_attr_empty.rs b/tests/compile_fail/as_mut/struct_attr_empty.rs
new file mode 100644
index 0000000..e7d2596
--- /dev/null
+++ b/tests/compile_fail/as_mut/struct_attr_empty.rs
@@ -0,0 +1,5 @@
+#[derive(derive_more::AsMut)]
+#[as_mut(forward)]
+struct Foo;
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/struct_attr_empty.stderr b/tests/compile_fail/as_mut/struct_attr_empty.stderr
new file mode 100644
index 0000000..d8a4f1d
--- /dev/null
+++ b/tests/compile_fail/as_mut/struct_attr_empty.stderr
@@ -0,0 +1,5 @@
+error: `#[as_mut(...)]` attribute can only be placed on structs with exactly one field
+ --> tests/compile_fail/as_mut/struct_attr_empty.rs:3:1
+  |
+3 | struct Foo;
+  | ^^^^^^
diff --git a/tests/compile_fail/as_mut/struct_attr_multiple_fields.rs b/tests/compile_fail/as_mut/struct_attr_multiple_fields.rs
new file mode 100644
index 0000000..c033d99
--- /dev/null
+++ b/tests/compile_fail/as_mut/struct_attr_multiple_fields.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsMut)]
+#[as_mut(forward)]
+struct Foo {
+    bar: i32,
+    baz: f32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/struct_attr_multiple_fields.stderr b/tests/compile_fail/as_mut/struct_attr_multiple_fields.stderr
new file mode 100644
index 0000000..d2721c3
--- /dev/null
+++ b/tests/compile_fail/as_mut/struct_attr_multiple_fields.stderr
@@ -0,0 +1,9 @@
+error: `#[as_mut(...)]` attribute can only be placed on structs with exactly one field
+ --> tests/compile_fail/as_mut/struct_attr_multiple_fields.rs:3:12
+  |
+3 |   struct Foo {
+  |  ____________^
+4 | |     bar: i32,
+5 | |     baz: f32,
+6 | | }
+  | |_^
diff --git a/tests/compile_fail/as_mut/union.rs b/tests/compile_fail/as_mut/union.rs
new file mode 100644
index 0000000..cef35db
--- /dev/null
+++ b/tests/compile_fail/as_mut/union.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsMut)]
+union Foo {
+    f1: u32,
+    f2: f32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/union.stderr b/tests/compile_fail/as_mut/union.stderr
new file mode 100644
index 0000000..1f8cfb1
--- /dev/null
+++ b/tests/compile_fail/as_mut/union.stderr
@@ -0,0 +1,5 @@
+error: `AsMut` cannot be derived for unions
+ --> tests/compile_fail/as_mut/union.rs:2:1
+  |
+2 | union Foo {
+  | ^^^^^
diff --git a/tests/compile_fail/as_mut/unknown_field_attr_arg.rs b/tests/compile_fail/as_mut/unknown_field_attr_arg.rs
new file mode 100644
index 0000000..928ffe3
--- /dev/null
+++ b/tests/compile_fail/as_mut/unknown_field_attr_arg.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsMut)]
+struct Foo {
+    #[as_mut(baz)]
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/unknown_field_attr_arg.stderr b/tests/compile_fail/as_mut/unknown_field_attr_arg.stderr
new file mode 100644
index 0000000..b191e1a
--- /dev/null
+++ b/tests/compile_fail/as_mut/unknown_field_attr_arg.stderr
@@ -0,0 +1,5 @@
+error: only `forward` or `skip`/`ignore` allowed here
+ --> tests/compile_fail/as_mut/unknown_field_attr_arg.rs:3:14
+  |
+3 |     #[as_mut(baz)]
+  |              ^^^
diff --git a/tests/compile_fail/as_mut/unknown_struct_attr_arg.rs b/tests/compile_fail/as_mut/unknown_struct_attr_arg.rs
new file mode 100644
index 0000000..4695803
--- /dev/null
+++ b/tests/compile_fail/as_mut/unknown_struct_attr_arg.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsMut)]
+#[as_mut(baz)]
+struct Foo {
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_mut/unknown_struct_attr_arg.stderr b/tests/compile_fail/as_mut/unknown_struct_attr_arg.stderr
new file mode 100644
index 0000000..3699acf
--- /dev/null
+++ b/tests/compile_fail/as_mut/unknown_struct_attr_arg.stderr
@@ -0,0 +1,5 @@
+error: only `forward` allowed here
+ --> tests/compile_fail/as_mut/unknown_struct_attr_arg.rs:2:10
+  |
+2 | #[as_mut(baz)]
+  |          ^^^
diff --git a/tests/compile_fail/as_ref/enum.rs b/tests/compile_fail/as_ref/enum.rs
new file mode 100644
index 0000000..b0ede63
--- /dev/null
+++ b/tests/compile_fail/as_ref/enum.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsRef)]
+enum Foo {
+    Bar(i32),
+    Baz(i32),
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/enum.stderr b/tests/compile_fail/as_ref/enum.stderr
new file mode 100644
index 0000000..28e593e
--- /dev/null
+++ b/tests/compile_fail/as_ref/enum.stderr
@@ -0,0 +1,5 @@
+error: `AsRef` cannot be derived for enums
+ --> tests/compile_fail/as_ref/enum.rs:2:1
+  |
+2 | enum Foo {
+  | ^^^^
diff --git a/tests/compile_fail/as_ref/multiple_field_attrs.rs b/tests/compile_fail/as_ref/multiple_field_attrs.rs
new file mode 100644
index 0000000..e9c314a
--- /dev/null
+++ b/tests/compile_fail/as_ref/multiple_field_attrs.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsRef)]
+struct Foo {
+    #[as_ref]
+    #[as_ref(forward)]
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/multiple_field_attrs.stderr b/tests/compile_fail/as_ref/multiple_field_attrs.stderr
new file mode 100644
index 0000000..e019ef3
--- /dev/null
+++ b/tests/compile_fail/as_ref/multiple_field_attrs.stderr
@@ -0,0 +1,5 @@
+error: only single `#[as_ref(...)]` attribute is allowed here
+ --> tests/compile_fail/as_ref/multiple_field_attrs.rs:4:5
+  |
+4 |     #[as_ref(forward)]
+  |     ^
diff --git a/tests/compile_fail/as_ref/multiple_struct_attrs.rs b/tests/compile_fail/as_ref/multiple_struct_attrs.rs
new file mode 100644
index 0000000..bbfcb38
--- /dev/null
+++ b/tests/compile_fail/as_ref/multiple_struct_attrs.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsRef)]
+#[as_ref(forward)]
+#[as_ref(forward)]
+struct Foo {
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/multiple_struct_attrs.stderr b/tests/compile_fail/as_ref/multiple_struct_attrs.stderr
new file mode 100644
index 0000000..9baec8b
--- /dev/null
+++ b/tests/compile_fail/as_ref/multiple_struct_attrs.stderr
@@ -0,0 +1,5 @@
+error: only single `#[as_ref(forward)]` attribute is allowed here
+ --> tests/compile_fail/as_ref/multiple_struct_attrs.rs:3:1
+  |
+3 | #[as_ref(forward)]
+  | ^
diff --git a/tests/compile_fail/as_ref/skip_and_others.rs b/tests/compile_fail/as_ref/skip_and_others.rs
new file mode 100644
index 0000000..7ed71a1
--- /dev/null
+++ b/tests/compile_fail/as_ref/skip_and_others.rs
@@ -0,0 +1,9 @@
+#[derive(derive_more::AsRef)]
+struct Foo {
+    #[as_ref]
+    bar: i32,
+    #[as_ref(skip)]
+    baz: f32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/skip_and_others.stderr b/tests/compile_fail/as_ref/skip_and_others.stderr
new file mode 100644
index 0000000..e7f0636
--- /dev/null
+++ b/tests/compile_fail/as_ref/skip_and_others.stderr
@@ -0,0 +1,5 @@
+error: `#[as_ref(skip)]` cannot be used in the same struct with other `#[as_ref(...)]` attributes
+ --> tests/compile_fail/as_ref/skip_and_others.rs:5:5
+  |
+5 |     #[as_ref(skip)]
+  |     ^
diff --git a/tests/compile_fail/as_ref/struct_and_field.rs b/tests/compile_fail/as_ref/struct_and_field.rs
new file mode 100644
index 0000000..766f4d4
--- /dev/null
+++ b/tests/compile_fail/as_ref/struct_and_field.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsRef)]
+#[as_ref(forward)]
+struct Foo {
+    #[as_ref]
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/struct_and_field.stderr b/tests/compile_fail/as_ref/struct_and_field.stderr
new file mode 100644
index 0000000..e094c5c
--- /dev/null
+++ b/tests/compile_fail/as_ref/struct_and_field.stderr
@@ -0,0 +1,5 @@
+error: `#[as_ref(...)]` cannot be placed on both struct and its field
+ --> tests/compile_fail/as_ref/struct_and_field.rs:4:5
+  |
+4 |     #[as_ref]
+  |     ^
diff --git a/tests/compile_fail/as_ref/struct_attr_empty.rs b/tests/compile_fail/as_ref/struct_attr_empty.rs
new file mode 100644
index 0000000..b698100
--- /dev/null
+++ b/tests/compile_fail/as_ref/struct_attr_empty.rs
@@ -0,0 +1,5 @@
+#[derive(derive_more::AsRef)]
+#[as_ref(forward)]
+struct Foo;
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/struct_attr_empty.stderr b/tests/compile_fail/as_ref/struct_attr_empty.stderr
new file mode 100644
index 0000000..9a81c5e
--- /dev/null
+++ b/tests/compile_fail/as_ref/struct_attr_empty.stderr
@@ -0,0 +1,5 @@
+error: `#[as_ref(...)]` attribute can only be placed on structs with exactly one field
+ --> tests/compile_fail/as_ref/struct_attr_empty.rs:3:1
+  |
+3 | struct Foo;
+  | ^^^^^^
diff --git a/tests/compile_fail/as_ref/struct_attr_multiple_fields.rs b/tests/compile_fail/as_ref/struct_attr_multiple_fields.rs
new file mode 100644
index 0000000..a528eea
--- /dev/null
+++ b/tests/compile_fail/as_ref/struct_attr_multiple_fields.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::AsRef)]
+#[as_ref(forward)]
+struct Foo {
+    bar: i32,
+    baz: f32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/struct_attr_multiple_fields.stderr b/tests/compile_fail/as_ref/struct_attr_multiple_fields.stderr
new file mode 100644
index 0000000..46a8137
--- /dev/null
+++ b/tests/compile_fail/as_ref/struct_attr_multiple_fields.stderr
@@ -0,0 +1,9 @@
+error: `#[as_ref(...)]` attribute can only be placed on structs with exactly one field
+ --> tests/compile_fail/as_ref/struct_attr_multiple_fields.rs:3:12
+  |
+3 |   struct Foo {
+  |  ____________^
+4 | |     bar: i32,
+5 | |     baz: f32,
+6 | | }
+  | |_^
diff --git a/tests/compile_fail/as_ref/union.rs b/tests/compile_fail/as_ref/union.rs
new file mode 100644
index 0000000..e5ffbb7
--- /dev/null
+++ b/tests/compile_fail/as_ref/union.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsRef)]
+union Foo {
+    f1: u32,
+    f2: f32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/union.stderr b/tests/compile_fail/as_ref/union.stderr
new file mode 100644
index 0000000..b57605e
--- /dev/null
+++ b/tests/compile_fail/as_ref/union.stderr
@@ -0,0 +1,5 @@
+error: `AsRef` cannot be derived for unions
+ --> tests/compile_fail/as_ref/union.rs:2:1
+  |
+2 | union Foo {
+  | ^^^^^
diff --git a/tests/compile_fail/as_ref/unknown_field_attr_arg.rs b/tests/compile_fail/as_ref/unknown_field_attr_arg.rs
new file mode 100644
index 0000000..7dc59b7
--- /dev/null
+++ b/tests/compile_fail/as_ref/unknown_field_attr_arg.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsRef)]
+struct Foo {
+    #[as_ref(baz)]
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/unknown_field_attr_arg.stderr b/tests/compile_fail/as_ref/unknown_field_attr_arg.stderr
new file mode 100644
index 0000000..90f6751
--- /dev/null
+++ b/tests/compile_fail/as_ref/unknown_field_attr_arg.stderr
@@ -0,0 +1,5 @@
+error: only `forward` or `skip`/`ignore` allowed here
+ --> tests/compile_fail/as_ref/unknown_field_attr_arg.rs:3:14
+  |
+3 |     #[as_ref(baz)]
+  |              ^^^
diff --git a/tests/compile_fail/as_ref/unknown_struct_attr_arg.rs b/tests/compile_fail/as_ref/unknown_struct_attr_arg.rs
new file mode 100644
index 0000000..45fcc24
--- /dev/null
+++ b/tests/compile_fail/as_ref/unknown_struct_attr_arg.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::AsRef)]
+#[as_ref(baz)]
+struct Foo {
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/as_ref/unknown_struct_attr_arg.stderr b/tests/compile_fail/as_ref/unknown_struct_attr_arg.stderr
new file mode 100644
index 0000000..760b982
--- /dev/null
+++ b/tests/compile_fail/as_ref/unknown_struct_attr_arg.stderr
@@ -0,0 +1,5 @@
+error: only `forward` allowed here
+ --> tests/compile_fail/as_ref/unknown_struct_attr_arg.rs:2:10
+  |
+2 | #[as_ref(baz)]
+  |          ^^^