Repository: JelteF/derive_more
Pull request #240 (2023-02-09 14:24:32)

Rename and refactor `derive_more::Debug` (#223)
Resolves https://github.com/JelteF/derive_more/issues/223




## Synopsis

#223 




## Solution

Rename `DebugCustom` to `Debug` and change it's logic to be a superset of standard library variant.




## 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


ilslv: @tyranron this PR is ready for review. 
ilslv: @tyranron this PR is ready for review 
ilslv: @tyranron pipeline failed for an unrelated reason and can't restart, but this PR is ready for a review
JelteF: Had a look at the test cases and they test all the special cases I could think of. Very nice work!

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46d298c..57fa602 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
 and this project adheres to [Semantic Versioning](http://semver.org/).
 
 
-## 1.0.0 - 2022-??-??
+## 1.0.0 - 2023-??-??
 
 ### Breaking changes
 
@@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 - Disable all Cargo features by default (except `std`) supporting and add the
   `full` feature which can be used to get the old behaviour of supporting all
   possible derives.
+- The `DebugCustom` derive is renamed to just `Debug` (gated now under a separate
+  `debug` feature), and its semantics were changed to be a superset of `std` variant
+  of `Debug`.
 
 ### New features
 
diff --git a/Cargo.toml b/Cargo.toml
index 2ef2be6..1438c14 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,6 +46,7 @@ add = ["derive_more-impl/add"]
 as_mut = ["derive_more-impl/as_mut"]
 as_ref = ["derive_more-impl/as_ref"]
 constructor = ["derive_more-impl/constructor"]
+debug = ["derive_more-impl/debug"]
 deref = ["derive_more-impl/deref"]
 deref_mut = ["derive_more-impl/deref_mut"]
 display = ["derive_more-impl/display"]
@@ -72,6 +73,7 @@ full = [
     "as_mut",
     "as_ref",
     "constructor",
+    "debug",
     "deref",
     "deref_mut",
     "display",
@@ -124,6 +126,11 @@ name = "constructor"
 path = "tests/constructor.rs"
 required-features = ["constructor"]
 
+[[test]]
+name = "debug"
+path = "tests/debug.rs"
+required-features = ["debug"]
+
 [[test]]
 name = "deref"
 path = "tests/deref.rs"
@@ -212,7 +219,7 @@ required-features = ["unwrap"]
 [[test]]
 name = "compile_fail"
 path = "tests/compile_fail/mod.rs"
-required-features = ["display"]
+required-features = ["debug", "display"]
 
 [[test]]
 name = "no_std"
diff --git a/README.md b/README.md
index be2702c..1c9d43d 100644
--- a/README.md
+++ b/README.md
@@ -95,8 +95,9 @@ These are traits that are used to convert automatically between types.
 
 These traits are used for converting a struct to a string in different ways.
 
-1. [`Display`-like], contains `Display`, `DebugCustom`, `Binary`, `Octal`,
-   `LowerHex`, `UpperHex`, `LowerExp`, `UpperExp`, `Pointer`
+1. [`Debug`]
+2. [`Display`-like], contains `Display`, `Binary`, `Octal`, `LowerHex`,
+   `UpperHex`, `LowerExp`, `UpperExp`, `Pointer`
 
 
 ### Error-handling traits
@@ -185,6 +186,7 @@ extern crate derive_more;
 [`AsRef`]: https://jeltef.github.io/derive_more/derive_more/as_ref.html
 [`AsMut`]: https://jeltef.github.io/derive_more/derive_more/as_mut.html
 
+[`Debug`]: https://github.com/JelteF/derive_more/blob/master/impl/doc/debug.md
 [`Display`-like]: https://jeltef.github.io/derive_more/derive_more/display.html
 
 [`Error`]: https://jeltef.github.io/derive_more/derive_more/error.html
diff --git a/impl/Cargo.toml b/impl/Cargo.toml
index 29e7cf4..38dcf97 100644
--- a/impl/Cargo.toml
+++ b/impl/Cargo.toml
@@ -32,7 +32,7 @@ unicode-xid = { version = "0.2.2", optional = true }
 rustc_version = { version = "0.4", optional = true }
 
 [dev-dependencies]
-derive_more = { path = "..", features = ["add", "from_str", "not", "try_into"] }
+derive_more = { path = "..", features = ["add", "debug", "from_str", "not", "try_into"] }
 itertools = "0.10.5"
 
 [badges]
@@ -49,6 +49,7 @@ add = []
 as_mut = []
 as_ref = []
 constructor = []
+debug = ["syn/extra-traits", "dep:unicode-xid"]
 deref = []
 deref_mut = []
 display = ["syn/extra-traits", "dep:unicode-xid"]
diff --git a/impl/doc/debug.md b/impl/doc/debug.md
new file mode 100644
index 0000000..935c52d
--- /dev/null
+++ b/impl/doc/debug.md
@@ -0,0 +1,126 @@
+# What `#[derive(Debug)]` generates
+
+This derive macro is a clever superset of `Debug` from standard library. Additional features include:
+- not imposing redudant trait bounds;
+- `#[debug(skip)]` attribute to skip formatting struct field or enum variant;
+- `#[debug("...", args...)]` to specify custom formatting for a particular struct or enum variant field;
+- `#[debug(bounds(...))]` to impose additional custom trait bounds.
+
+
+
+
+## The format of the format
+
+You supply a format by placing an attribute on particular struct or enum variant field (not enum variant itself):
+`#[debug("...", args...)]`. The format is exactly like in [`format!()`] or any other [`format_args!()`]-based macros.
+
+The variables available in the arguments is `self` and each member of the variant, with members of tuple structs being
+named with a leading underscore and their index, i.e. `_0`, `_1`, `_2`, etc.
+
+
+
+
+### Generic data types
+
+When deriving `Debug` for a generic struct/enum, all generic type arguments _used_ during formatting
+are bound by respective formatting trait.
+
+E.g., for a structure `Foo` defined like this:
+```rust
+use derive_more::Debug;
+
+#[derive(Debug)]
+struct Foo<'a, T1, T2: Trait, T3, T4> {
+    #[debug("{a}")]
+    a: T1,
+    #[debug("{b}")]
+    b: <T2 as Trait>::Type,
+    #[debug("{c:?}")]
+    c: Vec<T3>,
+    #[debug("{d:p}")]
+    d: &'a T1,
+    #[debug(skip)]
+    e: T4,
+}
+
+trait Trait { type Type; }
+```
+
+The following where clauses would be generated:
+* `T1: Display + Pointer`
+* `<T2 as Trait>::Type: Debug`
+* `Bar<T3>: Display`
+
+
+
+
+### Custom trait bounds
+
+Sometimes you may want to specify additional trait bounds on your generic type parameters, so that they could be used
+during formatting. This can be done with a `#[debug(bound(...))]` attribute.
+
+`#[debug(bound(...))]` accepts code tokens in a format similar to the format used in angle bracket list (or `where`
+clause predicates): `T: MyTrait, U: Trait1 + Trait2`.
+
+Using `#[debug("...", ...)]` formatting we'll try our best to infer trait bounds, but in more advanced cases this isn't
+possible. Our aim is to avoid imposing additional bounds, as they can be added with `#[debug(bound(...))]`.
+In the example below, we can infer only that `V: Display`, other bounds have to be supplied by the user:
+
+```rust
+use std::fmt::Display;
+use derive_more::Debug;
+
+#[derive(Debug)]
+#[debug(bound(T: MyTrait, U: Display))]
+struct MyStruct<T, U, V, F> {
+    #[debug("{}", a.my_function())]
+    a: T,
+    #[debug("{}", b.to_string().len())]
+    b: U,
+    #[debug("{c}")]
+    c: V,
+    #[debug(skip)]
+    d: F,
+}
+
+trait MyTrait { fn my_function(&self) -> i32; }
+```
+
+
+
+
+## Example usage
+
+```rust
+use std::path::PathBuf;
+use derive_more::Debug;
+
+#[derive(Debug)]
+struct MyInt(i32);
+
+#[derive(Debug)]
+struct MyIntHex(#[debug("{_0:x}")] i32);
+
+#[derive(Debug)]
+enum E {
+    Skipped {
+        x: u32,
+        #[debug(skip)]
+        y: u32,
+    },
+    Binary {
+        #[debug("{i:b}")]
+        i: i8,
+    },
+    Path(#[debug("{}", _0.display())] PathBuf),
+}
+
+assert_eq!(format!("{:?}", MyInt(-2)), "MyInt(-2)");
+assert_eq!(format!("{:?}", MyIntHex(-255)), "MyIntHex(ffffff01)");
+assert_eq!(format!("{:?}", E::Skipped { x: 10, y: 20 }), "Skipped { x: 10, .. }");
+assert_eq!(format!("{:?}", E::Binary { i: -2 }), "Binary { i: 11111110 }");
+assert_eq!(format!("{:?}", E::Path("abc".into())), "Path(abc)");
+```
+
+[`format!()`]: https://doc.rust-lang.org/stable/std/macro.format.html
+[`format_args!()`]: https://doc.rust-lang.org/stable/std/macro.format_args.html
diff --git a/impl/doc/display.md b/impl/doc/display.md
index 1f14915..6478212 100644
--- a/impl/doc/display.md
+++ b/impl/doc/display.md
@@ -35,9 +35,8 @@ i.e. `_0`, `_1`, `_2`, etc.
 The syntax does not change, but the name of the attribute is the snake case version of the trait.
 E.g. `Octal` -> `octal`, `Pointer` -> `pointer`, `UpperHex` -> `upper_hex`.
 
-A special case is the `DebugCustom` trait, which derives a `Debug` implementation instead of a `Display` implementation.
-The attribute for formatting is named `#[debug(..)]` when using `DebugCustom`.
-The arguments are the same as for `#[display(..)]`.
+Note, that `Debug` has a slightly different API and semantics, described in its docs, and so,
+requires a separate `debug` feature.
 
 
 ### Generic data types
@@ -111,15 +110,11 @@ struct MyStruct<T, U, V> {
 ```rust
 # use std::path::PathBuf;
 #
-# use derive_more::{DebugCustom, Display, Octal, UpperHex};
+# use derive_more::{Display, Octal, UpperHex};
 #
 #[derive(Display)]
 struct MyInt(i32);
 
-#[derive(DebugCustom)]
-#[debug("MyIntDbg(as hex: {_0:x}, as dec: {_0})")]
-struct MyIntDbg(i32);
-
 #[derive(Display)]
 #[display("({x}, {y})")]
 struct Point2D {
@@ -186,8 +181,4 @@ assert_eq!(Unit.to_string(), "Unit");
 assert_eq!(UnitStruct {}.to_string(), "UnitStruct");
 assert_eq!(PositiveOrNegative { x: 1 }.to_string(), "Positive");
 assert_eq!(PositiveOrNegative { x: -1 }.to_string(), "Negative");
-assert_eq!(
-    format!("{:?}", MyIntDbg(-255)),
-    "MyIntDbg(as hex: ffffff01, as dec: -255)",
-);
 ```
diff --git a/impl/src/fmt/debug.rs b/impl/src/fmt/debug.rs
new file mode 100644
index 0000000..ecb4ed1
--- /dev/null
+++ b/impl/src/fmt/debug.rs
@@ -0,0 +1,384 @@
+//! Implementation of a [`fmt::Debug`] derive macro.
+//!
+//! [`fmt::Debug`]: std::fmt::Debug
+
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+use syn::{
+    parse::{Error, Parse, ParseStream, Result},
+    parse_quote,
+    spanned::Spanned as _,
+    Ident,
+};
+
+use super::{BoundsAttribute, FmtAttribute};
+
+/// Expands a [`fmt::Debug`] derive macro.
+///
+/// [`fmt::Debug`]: std::fmt::Debug
+pub fn expand(input: &syn::DeriveInput, _: &str) -> Result<TokenStream> {
+    let attrs = ContainerAttributes::parse_attrs(&input.attrs)?;
+    let ident = &input.ident;
+
+    let (bounds, body) = match &input.data {
+        syn::Data::Struct(s) => expand_struct(attrs, ident, s),
+        syn::Data::Enum(e) => expand_enum(attrs, e),
+        syn::Data::Union(_) => {
+            return Err(Error::new(
+                input.span(),
+                "`Debug` cannot be derived for unions",
+            ));
+        }
+    }?;
+
+    let (impl_gens, ty_gens, where_clause) = {
+        let (impl_gens, ty_gens, where_clause) = input.generics.split_for_impl();
+        let mut where_clause = where_clause
+            .cloned()
+            .unwrap_or_else(|| parse_quote! { where });
+        where_clause.predicates.extend(bounds);
+        (impl_gens, ty_gens, where_clause)
+    };
+
+    Ok(quote! {
+        #[automatically_derived]
+        impl #impl_gens ::core::fmt::Debug for #ident #ty_gens
+             #where_clause
+        {
+            fn fmt(
+                &self, __derive_more_f: &mut ::core::fmt::Formatter<'_>
+            ) -> ::core::fmt::Result {
+                #body
+            }
+        }
+    })
+}
+
+/// Expands a [`fmt::Debug`] derive macro for the provided struct.
+///
+/// [`fmt::Debug`]: std::fmt::Debug
+fn expand_struct(
+    attrs: ContainerAttributes,
+    ident: &Ident,
+    s: &syn::DataStruct,
+) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
+    let s = Expansion {
+        attr: &attrs,
+        fields: &s.fields,
+        ident,
+    };
+    let bounds = s.generate_bounds()?;
+    let body = s.generate_body()?;
+
+    let vars = s.fields.iter().enumerate().map(|(i, f)| {
+        let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"));
+        let member = f
+            .ident
+            .clone()
+            .map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named);
+        quote! { let #var = &self.#member; }
+    });
+
+    let body = quote! {
+        #( #vars )*
+        #body
+    };
+
+    Ok((bounds, body))
+}
+
+/// Expands a [`fmt::Debug`] derive macro for the provided enum.
+///
+/// [`fmt::Debug`]: std::fmt::Debug
+fn expand_enum(
+    attrs: ContainerAttributes,
+    e: &syn::DataEnum,
+) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
+    let (bounds, match_arms) = e.variants.iter().try_fold(
+        (Vec::new(), TokenStream::new()),
+        |(mut bounds, mut arms), variant| {
+            let ident = &variant.ident;
+
+            let v = Expansion {
+                attr: &attrs,
+                fields: &variant.fields,
+                ident,
+            };
+            let arm_body = v.generate_body()?;
+            bounds.extend(v.generate_bounds()?);
+
+            let fields_idents =
+                variant.fields.iter().enumerate().map(|(i, f)| {
+                    f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"))
+                });
+            let matcher = match variant.fields {
+                syn::Fields::Named(_) => {
+                    quote! { Self::#ident { #( #fields_idents ),* } }
+                }
+                syn::Fields::Unnamed(_) => {
+                    quote! { Self::#ident ( #( #fields_idents ),* ) }
+                }
+                syn::Fields::Unit => quote! { Self::#ident },
+            };
+
+            arms.extend([quote! { #matcher => { #arm_body }, }]);
+
+            Ok::<_, Error>((bounds, arms))
+        },
+    )?;
+
+    let body = match_arms
+        .is_empty()
+        .then(|| quote! { match *self {} })
+        .unwrap_or_else(|| quote! { match self { #match_arms } });
+
+    Ok((bounds, body))
+}
+
+/// Representation of a [`fmt::Debug`] derive macro container attribute.
+///
+/// ```rust,ignore
+/// #[debug(bound(<bounds>))]
+/// ```
+///
+/// [`fmt::Debug`]: std::fmt::Debug
+#[derive(Debug, Default)]
+struct ContainerAttributes {
+    /// Additional trait bounds.
+    bounds: BoundsAttribute,
+}
+
+impl ContainerAttributes {
+    /// Parses [`ContainerAttributes`] from the provided [`syn::Attribute`]s.
+    fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> Result<Self> {
+        attrs
+            .as_ref()
+            .iter()
+            .filter(|attr| attr.path.is_ident("debug"))
+            .try_fold(ContainerAttributes::default(), |mut attrs, attr| {
+                let attr = syn::parse2::<ContainerAttributes>(attr.tokens.clone())?;
+                attrs.bounds.0.extend(attr.bounds.0);
+                Ok(attrs)
+            })
+    }
+}
+
+impl Parse for ContainerAttributes {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let error_span = input.span();
+
+        let content;
+        syn::parenthesized!(content in input);
+
+        BoundsAttribute::check_legacy_fmt(&content, error_span)?;
+
+        content.parse().map(|bounds| ContainerAttributes { bounds })
+    }
+}
+
+/// Representation of a [`fmt::Debug`] derive macro field attribute.
+///
+/// ```rust,ignore
+/// #[debug("<fmt_literal>", <fmt_args>)]
+/// #[debug(skip)]
+/// ```
+///
+/// [`fmt::Debug`]: std::fmt::Debug
+enum FieldAttribute {
+    /// [`fmt`] attribute.
+    ///
+    /// [`fmt`]: std::fmt
+    Fmt(FmtAttribute),
+
+    /// Attribute for skipping field.
+    Skip,
+}
+
+impl FieldAttribute {
+    /// Parses [`ContainerAttributes`] from the provided [`syn::Attribute`]s.
+    fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> Result<Option<Self>> {
+        Ok(attrs
+            .as_ref()
+            .iter()
+            .filter(|attr| attr.path.is_ident("debug"))
+            .try_fold(None, |mut attrs, attr| {
+                let field_attr = syn::parse2::<FieldAttribute>(attr.tokens.clone())?;
+                if let Some((path, _)) = attrs.replace((&attr.path, field_attr)) {
+                    Err(Error::new(
+                        path.span(),
+                        "only single `#[debug(...)]` attribute is allowed here",
+                    ))
+                } else {
+                    Ok(attrs)
+                }
+            })?
+            .map(|(_, attr)| attr))
+    }
+}
+
+impl Parse for FieldAttribute {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let error_span = input.span();
+
+        let content;
+        syn::parenthesized!(content in input);
+
+        FmtAttribute::check_legacy_fmt(&content, error_span)?;
+
+        if content.peek(syn::LitStr) {
+            content.parse().map(Self::Fmt)
+        } else {
+            let _ = content.parse::<syn::Path>().and_then(|p| {
+                if ["skip", "ignore"].into_iter().any(|i| p.is_ident(i)) {
+                    Ok(p)
+                } else {
+                    Err(Error::new(
+                        p.span(),
+                        "unknown attribute, expected `skip` or `ignore`",
+                    ))
+                }
+            })?;
+
+            Ok(Self::Skip)
+        }
+    }
+}
+
+/// Helper struct to generate [`Debug::fmt()`] implementation body and trait
+/// bounds for a struct or an enum variant.
+///
+/// [`Debug::fmt()`]: std::fmt::Debug::fmt()
+#[derive(Debug)]
+struct Expansion<'a> {
+    attr: &'a ContainerAttributes,
+
+    /// Struct or enum [`Ident`](struct@Ident).
+    ident: &'a Ident,
+
+    /// Struct or enum [`syn::Fields`].
+    fields: &'a syn::Fields,
+}
+
+impl<'a> Expansion<'a> {
+    /// Generates [`Debug::fmt()`] implementation for a struct or an enum variant.
+    ///
+    /// [`Debug::fmt()`]: std::fmt::Debug::fmt()
+    fn generate_body(&self) -> Result<TokenStream> {
+        match self.fields {
+            syn::Fields::Unit => {
+                let ident = self.ident.to_string();
+                Ok(quote! {
+                    ::core::fmt::Formatter::write_str(
+                        __derive_more_f,
+                        #ident,
+                    )
+                })
+            }
+            syn::Fields::Unnamed(unnamed) => {
+                let mut exhaustive = true;
+                let ident_str = self.ident.to_string();
+
+                let out = quote! {
+                    &mut ::derive_more::fmt::debug_tuple(
+                        __derive_more_f,
+                        #ident_str,
+                    )
+                };
+                let out = unnamed.unnamed.iter().enumerate().try_fold(
+                    out,
+                    |out, (i, field)| match FieldAttribute::parse_attrs(&field.attrs)? {
+                        Some(FieldAttribute::Skip) => {
+                            exhaustive = false;
+                            Ok::<_, Error>(out)
+                        }
+                        Some(FieldAttribute::Fmt(fmt)) => Ok(quote! {
+                            ::derive_more::fmt::DebugTuple::field(
+                                #out,
+                                &::core::format_args!(#fmt),
+                            )
+                        }),
+                        None => {
+                            let ident = format_ident!("_{i}");
+                            Ok(quote! {
+                                ::derive_more::fmt::DebugTuple::field(#out, #ident)
+                            })
+                        }
+                    },
+                )?;
+                Ok(if exhaustive {
+                    quote! { ::derive_more::fmt::DebugTuple::finish(#out) }
+                } else {
+                    quote! { ::derive_more::fmt::DebugTuple::finish_non_exhaustive(#out) }
+                })
+            }
+            syn::Fields::Named(named) => {
+                let mut exhaustive = true;
+                let ident = self.ident.to_string();
+
+                let out = quote! {
+                    &mut ::core::fmt::Formatter::debug_struct(
+                        __derive_more_f,
+                        #ident,
+                    )
+                };
+                let out = named.named.iter().try_fold(out, |out, field| {
+                        let field_ident = field.ident.as_ref().unwrap_or_else(|| {
+                            unreachable!("`syn::Fields::Named`");
+                        });
+                        let field_str = field_ident.to_string();
+                        match FieldAttribute::parse_attrs(&field.attrs)? {
+                            Some(FieldAttribute::Skip) => {
+                                exhaustive = false;
+                                Ok::<_, Error>(out)
+                            }
+                            Some(FieldAttribute::Fmt(fmt)) => Ok(quote! {
+                                ::core::fmt::DebugStruct::field(
+                                    #out,
+                                    #field_str,
+                                    &::core::format_args!(#fmt),
+                                )
+                            }),
+                            None => Ok(quote! {
+                                ::core::fmt::DebugStruct::field(#out, #field_str, #field_ident)
+                            }),
+                        }
+                    })?;
+                Ok(if exhaustive {
+                    quote! { ::core::fmt::DebugStruct::finish(#out) }
+                } else {
+                    quote! { ::core::fmt::DebugStruct::finish_non_exhaustive(#out) }
+                })
+            }
+        }
+    }
+
+    /// Generates trait bounds for a struct or an enum variant.
+    fn generate_bounds(&self) -> Result<Vec<syn::WherePredicate>> {
+        self.fields.iter().try_fold(
+            self.attr.bounds.0.clone().into_iter().collect::<Vec<_>>(),
+            |mut out, field| {
+                let fmt_attr =
+                    FieldAttribute::parse_attrs(&field.attrs)?.and_then(|attr| {
+                        match attr {
+                            FieldAttribute::Fmt(fmt) => Some(fmt),
+                            FieldAttribute::Skip => None,
+                        }
+                    });
+                let ty = &field.ty;
+
+                if let Some(attr) = fmt_attr {
+                    out.extend(attr.bounded_types(self.fields).map(
+                        |(ty, trait_name)| {
+                            let trait_name = format_ident!("{trait_name}");
+                            parse_quote! { #ty: ::core::fmt::#trait_name }
+                        },
+                    ));
+                } else {
+                    out.extend([parse_quote! { #ty: ::core::fmt::Debug }]);
+                }
+
+                Ok(out)
+            },
+        )
+    }
+}
diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs
new file mode 100644
index 0000000..48d3a6a
--- /dev/null
+++ b/impl/src/fmt/display.rs
@@ -0,0 +1,368 @@
+//! Implementation of [`fmt::Display`]-like derive macros.
+
+#[cfg(doc)]
+use std::fmt;
+
+use proc_macro2::{Ident, TokenStream};
+use quote::{format_ident, quote};
+use syn::{
+    parse::{Parse, ParseStream},
+    parse_quote,
+    spanned::Spanned as _,
+    Error, Result,
+};
+
+use super::{BoundsAttribute, FmtAttribute};
+
+/// Expands a [`fmt::Display`]-like derive macro.
+///
+/// Available macros:
+/// - [`Binary`](fmt::Binary)
+/// - [`Display`](fmt::Display)
+/// - [`LowerExp`](fmt::LowerExp)
+/// - [`LowerHex`](fmt::LowerHex)
+/// - [`Octal`](fmt::Octal)
+/// - [`Pointer`](fmt::Pointer)
+/// - [`UpperExp`](fmt::UpperExp)
+/// - [`UpperHex`](fmt::UpperHex)
+pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result<TokenStream> {
+    let trait_name = normalize_trait_name(trait_name);
+
+    let attrs = Attributes::parse_attrs(&input.attrs, trait_name)?;
+    let trait_ident = format_ident!("{trait_name}");
+    let ident = &input.ident;
+
+    let ctx = (&attrs, ident, &trait_ident, trait_name);
+    let (bounds, body) = match &input.data {
+        syn::Data::Struct(s) => expand_struct(s, ctx),
+        syn::Data::Enum(e) => expand_enum(e, ctx),
+        syn::Data::Union(u) => expand_union(u, ctx),
+    }?;
+
+    let (impl_gens, ty_gens, where_clause) = {
+        let (impl_gens, ty_gens, where_clause) = input.generics.split_for_impl();
+        let mut where_clause = where_clause
+            .cloned()
+            .unwrap_or_else(|| parse_quote! { where });
+        where_clause.predicates.extend(bounds);
+        (impl_gens, ty_gens, where_clause)
+    };
+
+    Ok(quote! {
+        #[automatically_derived]
+        impl #impl_gens ::core::fmt::#trait_ident for #ident #ty_gens
+             #where_clause
+        {
+            fn fmt(
+                &self, __derive_more_f: &mut ::core::fmt::Formatter<'_>
+            ) -> ::core::fmt::Result {
+                #body
+            }
+        }
+    })
+}
+
+/// Type alias for an expansion context:
+/// - [`Attributes`].
+/// - Struct/enum/union [`Ident`].
+/// - Derived trait [`Ident`].
+/// - Derived trait `&`[`str`].
+type ExpansionCtx<'a> = (&'a Attributes, &'a Ident, &'a Ident, &'a str);
+
+/// Expands a [`fmt::Display`]-like derive macro for the provided struct.
+fn expand_struct(
+    s: &syn::DataStruct,
+    (attrs, ident, trait_ident, _): ExpansionCtx<'_>,
+) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
+    let s = Expansion {
+        attrs,
+        fields: &s.fields,
+        trait_ident,
+        ident,
+    };
+    let bounds = s.generate_bounds();
+    let body = s.generate_body();
+
+    let vars = s.fields.iter().enumerate().map(|(i, f)| {
+        let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"));
+        let member = f
+            .ident
+            .clone()
+            .map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named);
+        quote! {
+            let #var = &self.#member;
+        }
+    });
+
+    let body = quote! {
+        #( #vars )*
+        #body
+    };
+
+    Ok((bounds, body))
+}
+
+/// Expands a [`fmt`]-like derive macro for the provided enum.
+fn expand_enum(
+    e: &syn::DataEnum,
+    (attrs, _, trait_ident, trait_name): ExpansionCtx<'_>,
+) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
+    if attrs.fmt.is_some() {
+        todo!("https://github.com/JelteF/derive_more/issues/142");
+    }
+
+    let (bounds, match_arms) = e.variants.iter().try_fold(
+        (Vec::new(), TokenStream::new()),
+        |(mut bounds, mut arms), variant| {
+            let attrs = Attributes::parse_attrs(&variant.attrs, trait_name)?;
+            let ident = &variant.ident;
+
+            if attrs.fmt.is_none()
+                && variant.fields.is_empty()
+                && trait_name != "Display"
+            {
+                return Err(Error::new(
+                    e.variants.span(),
+                    format!(
+                        "implicit formatting of unit enum variant is supported \
+                         only for `Display` macro, use `#[{}(\"...\")]` to \
+                         explicitly specify the formatting",
+                        trait_name_to_attribute_name(trait_name),
+                    ),
+                ));
+            }
+
+            let v = Expansion {
+                attrs: &attrs,
+                fields: &variant.fields,
+                trait_ident,
+                ident,
+            };
+            let arm_body = v.generate_body();
+            bounds.extend(v.generate_bounds());
+
+            let fields_idents =
+                variant.fields.iter().enumerate().map(|(i, f)| {
+                    f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"))
+                });
+            let matcher = match variant.fields {
+                syn::Fields::Named(_) => {
+                    quote! { Self::#ident { #( #fields_idents ),* } }
+                }
+                syn::Fields::Unnamed(_) => {
+                    quote! { Self::#ident ( #( #fields_idents ),* ) }
+                }
+                syn::Fields::Unit => quote! { Self::#ident },
+            };
+
+            arms.extend([quote! { #matcher => { #arm_body }, }]);
+
+            Ok::<_, Error>((bounds, arms))
+        },
+    )?;
+
+    let body = match_arms
+        .is_empty()
+        .then(|| quote! { match *self {} })
+        .unwrap_or_else(|| quote! { match self { #match_arms } });
+
+    Ok((bounds, body))
+}
+
+/// Expands a [`fmt::Display`]-like derive macro for the provided union.
+fn expand_union(
+    u: &syn::DataUnion,
+    (attrs, _, _, trait_name): ExpansionCtx<'_>,
+) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
+    let fmt = &attrs.fmt.as_ref().ok_or_else(|| {
+        Error::new(
+            u.fields.span(),
+            format!(
+                "unions must have `#[{}(\"...\", ...)]` attribute",
+                trait_name_to_attribute_name(trait_name),
+            ),
+        )
+    })?;
+    let (lit, args) = (&fmt.lit, &fmt.args);
+
+    let body = quote! {
+        ::core::write!(__derive_more_f, #lit, #( #args ),*)
+    };
+
+    Ok((attrs.bounds.0.clone().into_iter().collect(), body))
+}
+
+/// Representation of a [`fmt::Display`]-like derive macro attribute.
+///
+/// ```rust,ignore
+/// #[<fmt_trait>("<fmt_literal>", <fmt_args>)]
+/// #[bound(<bounds>)]
+/// ```
+///
+/// `#[<fmt_trait>(...)]` can be specified only once, while multiple
+/// `#[<fmt_trait>(bound(...))]` are allowed.
+#[derive(Debug, Default)]
+struct Attributes {
+    /// Interpolation [`FmtAttribute`].
+    fmt: Option<FmtAttribute>,
+
+    /// Addition trait bounds.
+    bounds: BoundsAttribute,
+}
+
+impl Attributes {
+    /// Parses [`Attributes`] from the provided [`syn::Attribute`]s.
+    fn parse_attrs(
+        attrs: impl AsRef<[syn::Attribute]>,
+        trait_name: &str,
+    ) -> Result<Self> {
+        attrs
+            .as_ref()
+            .iter()
+            .filter(|attr| attr.path.is_ident(trait_name_to_attribute_name(trait_name)))
+            .try_fold(Attributes::default(), |mut attrs, attr| {
+                let attr = syn::parse2::<Attribute>(attr.tokens.clone())?;
+               match attr {
+                   Attribute::Bounds(more) => {
+                       attrs.bounds.0.extend(more.0);
+                   }
+                   Attribute::Fmt(fmt) => {
+                       attrs.fmt.replace(fmt).map_or(Ok(()), |dup| Err(Error::new(
+                           dup.span(),
+                           format!(
+                               "Multiple `#[{}(\"...\", ...)]` attributes aren't allowed",
+                               trait_name_to_attribute_name(trait_name),
+                           ))))?;
+                   }
+               };
+                Ok(attrs)
+            })
+    }
+}
+
+/// Representation of a single [`fmt::Display`]-like derive macro attribute.
+#[derive(Debug)]
+enum Attribute {
+    /// [`fmt`] attribute.
+    Fmt(FmtAttribute),
+
+    /// Addition trait bounds.
+    Bounds(BoundsAttribute),
+}
+
+impl Parse for Attribute {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let error_span = input.span();
+
+        let content;
+        syn::parenthesized!(content in input);
+
+        BoundsAttribute::check_legacy_fmt(&content, error_span)?;
+        FmtAttribute::check_legacy_fmt(&content, error_span)?;
+
+        if content.peek(syn::LitStr) {
+            content.parse().map(Attribute::Fmt)
+        } else {
+            content.parse().map(Attribute::Bounds)
+        }
+    }
+}
+
+/// Helper struct to generate [`Display::fmt()`] implementation body and trait
+/// bounds for a struct or an enum variant.
+///
+/// [`Display::fmt()`]: fmt::Display::fmt()
+#[derive(Debug)]
+struct Expansion<'a> {
+    /// Derive macro [`Attributes`].
+    attrs: &'a Attributes,
+
+    /// Struct or enum [`Ident`].
+    ident: &'a Ident,
+
+    /// Struct or enum [`syn::Fields`].
+    fields: &'a syn::Fields,
+
+    /// [`fmt`] trait [`Ident`].
+    trait_ident: &'a Ident,
+}
+
+impl<'a> Expansion<'a> {
+    /// Generates [`Display::fmt()`] implementation for a struct or an enum variant.
+    ///
+    /// [`Display::fmt()`]: fmt::Display::fmt()
+    fn generate_body(&self) -> TokenStream {
+        if let Some(fmt) = &self.attrs.fmt {
+            let (lit, args) = (&fmt.lit, &fmt.args);
+            quote! {
+                ::core::write!(__derive_more_f, #lit, #( #args ),*)
+            }
+        } else if self.fields.iter().count() == 1 {
+            let field = self
+                .fields
+                .iter()
+                .next()
+                .unwrap_or_else(|| unreachable!("count() == 1"));
+            let ident = field.ident.clone().unwrap_or_else(|| format_ident!("_0"));
+            let trait_ident = self.trait_ident;
+            quote! {
+                ::core::fmt::#trait_ident::fmt(#ident, __derive_more_f)
+            }
+        } else {
+            let ident_str = self.ident.to_string();
+            quote! {
+                ::core::write!(__derive_more_f, #ident_str)
+            }
+        }
+    }
+
+    /// Generates trait bounds for a struct or an enum variant.
+    fn generate_bounds(&self) -> Vec<syn::WherePredicate> {
+        let Some(fmt) = &self.attrs.fmt else {
+            return self.fields.iter().next().map(|f| {
+                let ty = &f.ty;
+                let trait_ident = &self.trait_ident;
+                vec![parse_quote! { #ty: ::core::fmt::#trait_ident }]
+            })
+            .unwrap_or_default();
+        };
+
+        fmt.bounded_types(self.fields)
+            .map(|(ty, trait_name)| {
+                let tr = format_ident!("{}", trait_name);
+                parse_quote! { #ty: ::core::fmt::#tr }
+            })
+            .chain(self.attrs.bounds.0.clone())
+            .collect()
+    }
+}
+
+/// Matches the provided `trait_name` to appropriate [`Attribute::Fmt`] argument name.
+fn trait_name_to_attribute_name(trait_name: &str) -> &'static str {
+    match trait_name {
+        "Binary" => "binary",
+        "Display" => "display",
+        "LowerExp" => "lower_exp",
+        "LowerHex" => "lower_hex",
+        "Octal" => "octal",
+        "Pointer" => "pointer",
+        "UpperExp" => "upper_exp",
+        "UpperHex" => "upper_hex",
+        _ => unimplemented!(),
+    }
+}
+
+/// Matches the provided derive macro `name` to appropriate actual trait name.
+fn normalize_trait_name(name: &str) -> &'static str {
+    match name {
+        "Binary" => "Binary",
+        "Display" => "Display",
+        "LowerExp" => "LowerExp",
+        "LowerHex" => "LowerHex",
+        "Octal" => "Octal",
+        "Pointer" => "Pointer",
+        "UpperExp" => "UpperExp",
+        "UpperHex" => "UpperHex",
+        _ => unimplemented!(),
+    }
+}
diff --git a/impl/src/display.rs b/impl/src/fmt/mod.rs
similarity index 50%
rename from impl/src/display.rs
rename to impl/src/fmt/mod.rs
index 12b0811..4b42f86 100644
--- a/impl/src/display.rs
+++ b/impl/src/fmt/mod.rs
@@ -1,543 +1,135 @@
-//! Implementation of [`fmt`]-like derive macros.
+//! Implementations of [`fmt`]-like derive macros.
 //!
 //! [`fmt`]: std::fmt
 
+#[cfg(feature = "debug")]
+pub(crate) mod debug;
+#[cfg(feature = "display")]
+pub(crate) mod display;
+mod parsing;
+
 use std::{iter, mem};
 
 use proc_macro2::{Ident, Span, TokenStream, TokenTree};
-use quote::{format_ident, quote, ToTokens};
+use quote::ToTokens;
 use syn::{
     buffer::Cursor,
     parse::{Parse, ParseBuffer, ParseStream},
-    parse_quote,
     punctuated::Punctuated,
     spanned::Spanned as _,
     Error, Result,
 };
 
-use crate::parsing;
-
-/// Expands [`fmt`]-like derive macro.
-///
-/// Available macros:
-/// - [`Binary`]
-/// - [`Debug`]
-/// - [`Display`]
-/// - [`LowerExp`]
-/// - [`LowerHex`]
-/// - [`Octal`]
-/// - [`Pointer`]
-/// - [`UpperExp`]
-/// - [`UpperHex`]
-///
-/// [`fmt`]: std::fmt
-/// [`Binary`]: std::fmt::Binary
-/// [`Debug`]: std::fmt::Debug
-/// [`Display`]: std::fmt::Display
-/// [`LowerExp`]: std::fmt::LowerExp
-/// [`LowerHex`]: std::fmt::LowerHex
-/// [`Octal`]: std::fmt::Octal
-/// [`Pointer`]: std::fmt::Pointer
-/// [`UpperExp`]: std::fmt::UpperExp
-/// [`UpperHex`]: std::fmt::UpperHex
-pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result<TokenStream> {
-    let trait_name = normalize_trait_name(trait_name);
-
-    let attrs = Attributes::parse_attrs(&input.attrs, trait_name)?;
-    let trait_ident = format_ident!("{trait_name}");
-    let ident = &input.ident;
-
-    let ctx = (&attrs, ident, &trait_ident, trait_name);
-    let (bounds, body) = match &input.data {
-        syn::Data::Struct(s) => expand_struct(s, ctx),
-        syn::Data::Enum(e) => expand_enum(e, ctx),
-        syn::Data::Union(u) => expand_union(u, ctx),
-    }?;
-
-    let (impl_gens, ty_gens, where_clause) = {
-        let (impl_gens, ty_gens, where_clause) = input.generics.split_for_impl();
-        let mut where_clause = where_clause
-            .cloned()
-            .unwrap_or_else(|| parse_quote! { where });
-        where_clause.predicates.extend(bounds);
-        (impl_gens, ty_gens, where_clause)
-    };
-
-    Ok(quote! {
-        #[automatically_derived]
-        impl #impl_gens ::core::fmt::#trait_ident for #ident #ty_gens
-             #where_clause
-        {
-            fn fmt(
-                &self, __derive_more_f: &mut ::core::fmt::Formatter<'_>
-            ) -> ::core::fmt::Result {
-                #body
-            }
-        }
-    })
-}
-
-/// Type alias for an expansion context:
-/// - [`Attributes`].
-/// - Struct/enum/union [`Ident`].
-/// - Derived trait [`Ident`].
-/// - Derived trait `&`[`str`].
-type ExpansionCtx<'a> = (&'a Attributes, &'a Ident, &'a Ident, &'a str);
-
-/// Expands a [`fmt`]-like derive macro for the provided struct.
-///
-/// [`fmt`]: std::fmt
-fn expand_struct(
-    s: &syn::DataStruct,
-    (attrs, ident, trait_ident, _): ExpansionCtx<'_>,
-) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
-    let s = Expansion {
-        attrs,
-        fields: &s.fields,
-        trait_ident,
-        ident,
-    };
-    let bounds = s.generate_bounds();
-    let body = s.generate_body();
-
-    let vars = s.fields.iter().enumerate().map(|(i, f)| {
-        let var = f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"));
-        let member = f
-            .ident
-            .clone()
-            .map_or_else(|| syn::Member::Unnamed(i.into()), syn::Member::Named);
-        quote! {
-            let #var = &self.#member;
-        }
-    });
-
-    let body = quote! {
-        #( #vars )*
-        #body
-    };
-
-    Ok((bounds, body))
-}
-
-/// Expands a [`fmt`]-like derive macro for the provided enum.
-///
-/// [`fmt`]: std::fmt
-fn expand_enum(
-    e: &syn::DataEnum,
-    (attrs, _, trait_ident, trait_name): ExpansionCtx<'_>,
-) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
-    if attrs.fmt_lit.is_some() {
-        todo!("https://github.com/JelteF/derive_more/issues/142");
-    }
-
-    let (bounds, match_arms) = e.variants.iter().try_fold(
-        (Vec::new(), TokenStream::new()),
-        |(mut bounds, mut arms), variant| {
-            let attrs = Attributes::parse_attrs(&variant.attrs, trait_name)?;
-            let ident = &variant.ident;
-
-            if attrs.fmt_lit.is_none()
-                && variant.fields.is_empty()
-                && trait_name != "Display"
-            {
-                return Err(Error::new(
-                    e.variants.span(),
-                    format!(
-                        "Implicit formatting of unit enum variant is supported \
-                         only for `Display` macro. Use `#[{}(\"...\")]` to \
-                         explicitly specify the formatting.",
-                        trait_name_to_attribute_name(trait_name),
-                    ),
-                ));
-            }
-
-            let v = Expansion {
-                attrs: &attrs,
-                fields: &variant.fields,
-                trait_ident,
-                ident,
-            };
-            let arm_body = v.generate_body();
-            bounds.extend(v.generate_bounds());
-
-            let fields_idents =
-                variant.fields.iter().enumerate().map(|(i, f)| {
-                    f.ident.clone().unwrap_or_else(|| format_ident!("_{i}"))
-                });
-            let matcher = match variant.fields {
-                syn::Fields::Named(_) => {
-                    quote! { Self::#ident { #( #fields_idents ),* } }
-                }
-                syn::Fields::Unnamed(_) => {
-                    quote! { Self::#ident ( #( #fields_idents ),* ) }
-                }
-                syn::Fields::Unit => quote! { Self::#ident },
-            };
-
-            arms.extend([quote! { #matcher => { #arm_body }, }]);
-
-            Ok::<_, Error>((bounds, arms))
-        },
-    )?;
-
-    let body = match_arms
-        .is_empty()
-        .then(|| quote! { match *self {} })
-        .unwrap_or_else(|| quote! { match self { #match_arms } });
-
-    Ok((bounds, body))
-}
-
-/// Expands a [`fmt`]-like derive macro for the provided union.
-///
-/// [`fmt`]: std::fmt
-fn expand_union(
-    u: &syn::DataUnion,
-    (attrs, _, _, trait_name): ExpansionCtx<'_>,
-) -> Result<(Vec<syn::WherePredicate>, TokenStream)> {
-    let fmt_lit = attrs.fmt_lit.as_ref().ok_or_else(|| {
-        Error::new(
-            u.fields.span(),
-            format!(
-                "Unions must have `#[{}(\"...\", ...)]` attribute",
-                trait_name_to_attribute_name(trait_name),
-            ),
-        )
-    })?;
-    let fmt_args = &attrs.fmt_args;
-
-    let body = quote! {
-        ::core::write!(__derive_more_f, #fmt_lit, #( #fmt_args ),*)
-    };
-
-    Ok((attrs.bounds.clone().into_iter().collect(), body))
-}
-
-/// Representation of a [`fmt`]-like derive macro attribute.
-///
-/// ```rust,ignore
-/// #[<fmt_trait>("<fmt_literal>", <fmt_args>)]
-/// #[bound(<bounds>)]
-/// ```
-///
-/// `#[<fmt_trait>(...)]` can be specified only once, while multiple
-/// `#[bound(...)]` are allowed.
-///
-/// [`fmt`]: std::fmt
+/// Representation of a macro attribute expressing additional trait bounds.
 #[derive(Debug, Default)]
-struct Attributes {
-    /// Interpolation [`syn::LitStr`].
-    fmt_lit: Option<syn::LitStr>,
+struct BoundsAttribute(Punctuated<syn::WherePredicate, syn::token::Comma>);
 
-    /// Interpolation arguments.
-    fmt_args: Vec<FmtArgument>,
+impl BoundsAttribute {
+    /// Errors in case legacy syntax is encountered: `bound = "..."`.
+    fn check_legacy_fmt(input: &ParseBuffer<'_>, error_span: Span) -> Result<()> {
+        let fork = input.fork();
 
-    /// Addition trait bounds.
-    bounds: Punctuated<syn::WherePredicate, syn::token::Comma>,
-}
+        let path = fork
+            .parse::<syn::Path>()
+            .and_then(|path| fork.parse::<syn::token::Eq>().map(|_| path));
 
-impl Attributes {
-    /// Parses [`Attributes`] from the provided [`syn::Attribute`]s.
-    fn parse_attrs(
-        attrs: impl AsRef<[syn::Attribute]>,
-        trait_name: &str,
-    ) -> Result<Self> {
-        attrs
-            .as_ref()
-            .iter()
-            .filter(|attr| attr.path.is_ident(trait_name_to_attribute_name(trait_name)))
-            .try_fold(Attributes::default(), |mut attrs, attr| {
-                let attr = syn::parse2::<Attribute>(attr.tokens.clone())?;
-               match attr {
-                   Attribute::Bounds(more) => {
-                       attrs.bounds.extend(more);
-                   }
-                   Attribute::Fmt { fmt_lit, fmt_args } => {
-                       attrs.fmt_lit.replace(fmt_lit).map_or(Ok(()), |dup| Err(Error::new(
-                           dup.span(),
-                           format!(
-                               "Multiple `#[{}(\"...\", ...)]` attributes aren't allowed",
-                               trait_name_to_attribute_name(trait_name),
-                           ))))?;
-                       attrs.fmt_args.extend(fmt_args)
-                   }
-               };
-                Ok(attrs)
-            })
+        match path {
+            Ok(path) if path.is_ident("bound") => fork
+                .parse::<syn::Lit>()
+                .ok()
+                .and_then(|lit| match lit {
+                    syn::Lit::Str(s) => Some(s.value()),
+                    _ => None,
+                })
+                .map_or(Ok(()), |bound| {
+                    Err(Error::new(
+                        error_span,
+                        format!("legacy syntax, use `bound({bound})` instead"),
+                    ))
+                }),
+            Ok(_) | Err(_) => Ok(()),
+        }
     }
 }
 
-/// Helper struct to generate [`Display::fmt()`] implementation body and trait
-/// bounds for a struct or an enum variant.
-///
-/// [`Display::fmt()`]: std::fmt::Display::fmt()
-#[derive(Debug)]
-struct Expansion<'a> {
-    /// Derive macro [`Attributes`].
-    attrs: &'a Attributes,
-
-    /// Struct or enum [`Ident`].
-    ident: &'a Ident,
-
-    /// Struct or enum [`syn::Fields`].
-    fields: &'a syn::Fields,
-
-    /// [`fmt`] trait [`Ident`].
-    ///
-    /// [`fmt`]: std::fmt
-    trait_ident: &'a Ident,
-}
-
-impl<'a> Expansion<'a> {
-    /// Generates [`Display::fmt()`] implementation for a struct or an enum variant.
-    ///
-    /// [`Display::fmt()`]: std::fmt::Display::fmt()
-    fn generate_body(&self) -> TokenStream {
-        if let Some(lit) = &self.attrs.fmt_lit {
-            let args = &self.attrs.fmt_args;
-            quote! {
-                ::core::write!(__derive_more_f, #lit, #( #args ),*)
-            }
-        } else if self.fields.iter().count() == 1 {
-            let field = self
-                .fields
-                .iter()
-                .next()
-                .unwrap_or_else(|| unreachable!("count() == 1"));
-            let ident = field.ident.clone().unwrap_or_else(|| format_ident!("_0"));
-            let trait_ident = self.trait_ident;
-            quote! {
-                ::core::fmt::#trait_ident::fmt(#ident, __derive_more_f)
-            }
-        } else {
-            let ident_str = self.ident.to_string();
-            quote! {
-                ::core::write!(__derive_more_f, #ident_str)
+impl Parse for BoundsAttribute {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let _ = input.parse::<syn::Path>().and_then(|p| {
+            if ["bound", "bounds", "where"]
+                .into_iter()
+                .any(|i| p.is_ident(i))
+            {
+                Ok(p)
+            } else {
+                Err(Error::new(
+                    p.span(),
+                    "unknown attribute, expected `bound(...)`",
+                ))
             }
-        }
-    }
-
-    /// Generates trait bounds for a struct or an enum variant.
-    fn generate_bounds(&self) -> Vec<syn::WherePredicate> {
-        let Some(display_literal) = &self.attrs.fmt_lit else {
-            return self.fields.iter().next().map(|f| {
-                let ty = &f.ty;
-                let trait_ident = &self.trait_ident;
-                vec![parse_quote! { #ty: ::core::fmt::#trait_ident }]
-            })
-            .unwrap_or_default();
-        };
-
-        let placeholders = Placeholder::parse_fmt_string(&display_literal.value());
+        })?;
 
-        // We ignore unknown fields, as compiler will produce better error messages.
-        placeholders
-            .into_iter()
-            .filter_map(|placeholder| {
-                let name = match placeholder.arg {
-                    Parameter::Named(name) => self
-                        .attrs
-                        .fmt_args
-                        .iter()
-                        .find_map(|a| (a.alias.as_ref()? == &name).then_some(&a.expr))
-                        .map_or(Some(name), |expr| {
-                            expr.ident().map(ToString::to_string)
-                        })?,
-                    Parameter::Positional(i) => self
-                        .attrs
-                        .fmt_args
-                        .get(i)
-                        .and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))?
-                        .to_string(),
-                };
-
-                let unnamed = name.strip_prefix('_').and_then(|s| s.parse().ok());
-                let ty = match (&self.fields, unnamed) {
-                    (syn::Fields::Unnamed(f), Some(i)) => {
-                        f.unnamed.iter().nth(i).map(|f| &f.ty)
-                    }
-                    (syn::Fields::Named(f), None) => f.named.iter().find_map(|f| {
-                        f.ident.as_ref().filter(|s| **s == name).map(|_| &f.ty)
-                    }),
-                    _ => None,
-                }?;
+        let content;
+        syn::parenthesized!(content in input);
 
-                let tr = format_ident!("{}", placeholder.trait_name);
-                Some(parse_quote! {
-                    #ty: ::core::fmt::#tr
-                })
-            })
-            .chain(self.attrs.bounds.clone())
-            .collect()
+        content
+            .parse_terminated(syn::WherePredicate::parse)
+            .map(Self)
     }
 }
 
-/// Representation of a single [`fmt`]-like display attribute.
+/// Representation of a [`fmt`]-like attribute.
 ///
 /// [`fmt`]: std::fmt
 #[derive(Debug)]
-enum Attribute {
-    /// [`fmt`] attribute.
+struct FmtAttribute {
+    /// Interpolation [`syn::LitStr`].
     ///
-    /// [`fmt`]: std::fmt
-    Fmt {
-        /// Interpolation [`syn::LitStr`].
-        fmt_lit: syn::LitStr,
+    /// [`syn::LitStr`]: struct@syn::LitStr
+    lit: syn::LitStr,
 
-        /// Interpolation arguments.
-        fmt_args: Vec<FmtArgument>,
-    },
-
-    /// Addition trait bounds.
-    Bounds(Punctuated<syn::WherePredicate, syn::token::Comma>),
-}
-
-/// [Named parameter][1]: `identifier '=' expression`.
-///
-/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters
-#[derive(Debug, Default)]
-struct FmtArgument {
-    /// `identifier` [`Ident`].
-    alias: Option<Ident>,
-
-    /// `expression` [`FmtExpr`].
-    expr: FmtExpr,
-}
-
-impl ToTokens for FmtArgument {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        if let Some(alias) = &self.alias {
-            alias.to_tokens(tokens);
-            syn::token::Eq::default().to_tokens(tokens);
-        }
-        self.expr.to_tokens(tokens);
-    }
-}
-
-/// Expression of a [`FmtArgument`].
-///
-/// This type is used instead of a [`syn::Expr`] to avoid using [`syn`]'s
-/// `full` feature increasing compilation times.
-#[derive(Debug)]
-enum FmtExpr {
-    /// [`Ident`].
-    Ident(Ident),
-
-    /// Plain [`TokenStream`].
-    TokenStream(TokenStream),
-}
-
-impl FmtExpr {
-    /// Returns an [`Ident`] in case this [`FmtExpr`] contains only it, or [`None`]
-    /// otherwise.
-    fn ident(&self) -> Option<&Ident> {
-        match self {
-            Self::Ident(i) => Some(i),
-            Self::TokenStream(_) => None,
-        }
-    }
-
-    /// Checks whether this [`FmtExpr`] is empty.
-    fn is_empty(&self) -> bool {
-        match self {
-            Self::Ident(_) => false,
-            Self::TokenStream(stream) => stream.is_empty(),
-        }
-    }
+    /// Interpolation arguments.
+    args: Vec<FmtArgument>,
 }
 
-impl Default for FmtExpr {
-    fn default() -> Self {
-        Self::TokenStream(TokenStream::new())
-    }
-}
+impl FmtAttribute {
+    /// Returns an [`Iterator`] over bounded [`syn::Type`]s and trait names.
+    fn bounded_types<'a>(
+        &'a self,
+        fields: &'a syn::Fields,
+    ) -> impl Iterator<Item = (&'a syn::Type, &'static str)> {
+        let placeholders = Placeholder::parse_fmt_string(&self.lit.value());
 
-impl ToTokens for FmtExpr {
-    fn to_tokens(&self, tokens: &mut TokenStream) {
-        match self {
-            Self::Ident(ident) => ident.to_tokens(tokens),
-            Self::TokenStream(ts) => ts.to_tokens(tokens),
-        }
-    }
-}
+        // We ignore unknown fields, as compiler will produce better error messages.
+        placeholders.into_iter().filter_map(move |placeholder| {
+            let name = match placeholder.arg {
+                Parameter::Named(name) => self
+                    .args
+                    .iter()
+                    .find_map(|a| (a.alias.as_ref()? == &name).then_some(&a.expr))
+                    .map_or(Some(name), |expr| expr.ident().map(ToString::to_string))?,
+                Parameter::Positional(i) => self
+                    .args
+                    .get(i)
+                    .and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))?
+                    .to_string(),
+            };
 
-impl Extend<TokenTree> for FmtExpr {
-    fn extend<T: IntoIterator<Item = TokenTree>>(&mut self, iter: T) {
-        *self = iter
-            .into_iter()
-            .fold(mem::take(self), |this, tt| match (this, tt) {
-                (Self::TokenStream(stream), TokenTree::Ident(ident))
-                    if stream.is_empty() =>
-                {
-                    Self::Ident(ident)
+            let unnamed = name.strip_prefix('_').and_then(|s| s.parse().ok());
+            let ty = match (&fields, unnamed) {
+                (syn::Fields::Unnamed(f), Some(i)) => {
+                    f.unnamed.iter().nth(i).map(|f| &f.ty)
                 }
-                (Self::TokenStream(mut stream), tt) => {
-                    stream.extend([tt]);
-                    Self::TokenStream(stream)
-                }
-                (Self::Ident(ident), tt) => {
-                    let mut stream = ident.into_token_stream();
-                    stream.extend([tt]);
-                    Self::TokenStream(stream)
-                }
-            });
-    }
-}
-
-impl Parse for Attribute {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let error_span = input.span();
-
-        let content;
-        syn::parenthesized!(content in input);
-
-        Self::check_legacy_fmt(&content, error_span)?;
-
-        if content.peek(syn::LitStr) {
-            let fmt_lit = content.parse()?;
-
-            if content.peek(syn::token::Comma) {
-                let _ = content.parse::<syn::token::Comma>()?;
-            }
-
-            let fmt_args = content.step(|cursor| {
-                let mut cursor = *cursor;
-                let arguments = iter::from_fn(|| {
-                    let (arg, c) = FmtArgument::parse(cursor)?;
-                    cursor = c;
-                    Some(arg)
-                })
-                .collect();
-
-                Ok((arguments, cursor))
-            })?;
-
-            return Ok(Attribute::Fmt { fmt_lit, fmt_args });
-        }
-
-        let _ = content.parse::<syn::Path>().and_then(|p| {
-            if ["bound", "bounds"].into_iter().any(|i| p.is_ident(i)) {
-                Ok(p)
-            } else {
-                Err(Error::new(
-                    p.span(),
-                    "Unknown attribute. Expected `\"...\", ...` or `bound(...)`",
-                ))
-            }
-        })?;
-
-        let inner;
-        syn::parenthesized!(inner in content);
+                (syn::Fields::Named(f), None) => f.named.iter().find_map(|f| {
+                    f.ident.as_ref().filter(|s| **s == name).map(|_| &f.ty)
+                }),
+                _ => None,
+            }?;
 
-        inner
-            .parse_terminated(syn::WherePredicate::parse)
-            .map(Attribute::Bounds)
+            Some((ty, placeholder.trait_name))
+        })
     }
-}
 
-impl Attribute {
-    /// Errors in case legacy syntax is encountered: `fmt = "...", (arg),*` or
-    /// `bound = "..."`.
+    /// Errors in case legacy syntax is encountered: `fmt = "...", (arg),*`.
     fn check_legacy_fmt(input: &ParseBuffer<'_>, error_span: Span) -> Result<()> {
         let fork = input.fork();
 
@@ -566,29 +158,66 @@ impl Attribute {
             .map_or(Ok(()), |fmt| {
                 Err(Error::new(
                     error_span,
-                    format!("Legacy syntax, use: `{}`", fmt.join(", ")),
+                    format!("legacy syntax, use `{}` instead", fmt.join(", ")),
                 ))
             }),
-            Ok(path) if path.is_ident("bound") => fork
-                .parse::<syn::Lit>()
-                .ok()
-                .and_then(|lit| match lit {
-                    syn::Lit::Str(s) => Some(s.value()),
-                    _ => None,
-                })
-                .map_or(Ok(()), |bound| {
-                    Err(Error::new(
-                        error_span,
-                        format!("Legacy syntax, use: `bound({bound})`"),
-                    ))
-                }),
             Ok(_) | Err(_) => Ok(()),
         }
     }
 }
 
+impl Parse for FmtAttribute {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let fmt_lit = input.parse()?;
+
+        if input.peek(syn::token::Comma) {
+            let _ = input.parse::<syn::token::Comma>()?;
+        }
+
+        let fmt_args = input.step(|cursor| {
+            let mut cursor = *cursor;
+            let arguments = iter::from_fn(|| {
+                let (arg, c) = FmtArgument::parse(cursor)?;
+                cursor = c;
+                Some(arg)
+            })
+            .collect();
+
+            Ok((arguments, cursor))
+        })?;
+
+        Ok(Self {
+            lit: fmt_lit,
+            args: fmt_args,
+        })
+    }
+}
+
+impl ToTokens for FmtAttribute {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        self.lit.to_tokens(tokens);
+        for arg in &self.args {
+            syn::token::Comma(arg.span()).to_tokens(tokens);
+            arg.to_tokens(tokens);
+        }
+    }
+}
+
+/// Representation of a [named parameter][1] (`identifier '=' expression`) in
+/// in a [`FmtAttribute`].
+///
+/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters
+#[derive(Debug, Default)]
+struct FmtArgument {
+    /// `identifier` [`Ident`].
+    alias: Option<Ident>,
+
+    /// `expression` [`FmtExpr`].
+    expr: FmtExpr,
+}
+
 impl FmtArgument {
-    /// Parses this [`FmtArgument`] and returns a new [`Cursor`], where parsing
+    /// Parses a [`FmtArgument`] and returns a new [`Cursor`], where parsing
     /// can be continued.
     ///
     /// Returns [`None`] in case of eof or trailing comma.
@@ -619,7 +248,7 @@ impl FmtArgument {
         (!arg.expr.is_empty()).then_some((arg, c))
     }
 
-    /// Parses the rest, until the end of this [`FmtArgument`] (comma or eof),
+    /// Parses the rest, until the end of a [`FmtArgument`] (comma or eof),
     /// in case simplest case of `(ident =)? ident(,|eof)` wasn't parsed.
     fn parse_rest(mut cursor: Cursor<'_>) -> (TokenStream, Cursor<'_>) {
         let mut out = TokenStream::new();
@@ -713,7 +342,7 @@ impl FmtArgument {
         })
     }
 
-    /// Parses until balanced amount of `open` and `close` [`TokenTree::Punc`]
+    /// Parses until balanced amount of `open` and `close` [`TokenTree::Punct`]
     /// or eof.
     ///
     /// [`Cursor`] should be pointing **right after** the first `open`ing.
@@ -744,39 +373,87 @@ impl FmtArgument {
     }
 }
 
-/// Matches trait name to [`Attribute::Fmt`] argument name.
-fn trait_name_to_attribute_name(trait_name: &str) -> &'static str {
-    match trait_name {
-        "Binary" => "binary",
-        "Debug" => "debug",
-        "Display" => "display",
-        "LowerExp" => "lower_exp",
-        "LowerHex" => "lower_hex",
-        "Octal" => "octal",
-        "Pointer" => "pointer",
-        "UpperExp" => "upper_exp",
-        "UpperHex" => "upper_hex",
-        _ => unimplemented!(),
+impl ToTokens for FmtArgument {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        if let Some(alias) = &self.alias {
+            alias.to_tokens(tokens);
+            syn::token::Eq::default().to_tokens(tokens);
+        }
+        self.expr.to_tokens(tokens);
     }
 }
 
-/// Matches derive macro name to actual trait name.
-fn normalize_trait_name(name: &str) -> &'static str {
-    match name {
-        "Binary" => "Binary",
-        "Debug" | "DebugCustom" => "Debug",
-        "Display" => "Display",
-        "LowerExp" => "LowerExp",
-        "LowerHex" => "LowerHex",
-        "Octal" => "Octal",
-        "Pointer" => "Pointer",
-        "UpperExp" => "UpperExp",
-        "UpperHex" => "UpperHex",
-        _ => unimplemented!(),
+/// Representation of an expression being a [`FmtArgument`].
+///
+/// **NOTE**: This type is used instead of a [`syn::Expr`] to avoid using
+///           [`syn`]'s `full` feature significantly blowing up compilation times.
+#[derive(Debug)]
+enum FmtExpr {
+    /// [`Ident`].
+    Ident(Ident),
+
+    /// Plain [`TokenStream`].
+    TokenStream(TokenStream),
+}
+
+impl FmtExpr {
+    /// Returns an [`Ident`] in case this [`FmtExpr`] contains only it, or [`None`]
+    /// otherwise.
+    fn ident(&self) -> Option<&Ident> {
+        match self {
+            Self::Ident(i) => Some(i),
+            Self::TokenStream(_) => None,
+        }
+    }
+
+    /// Checks whether this [`FmtExpr`] is empty.
+    fn is_empty(&self) -> bool {
+        match self {
+            Self::Ident(_) => false,
+            Self::TokenStream(stream) => stream.is_empty(),
+        }
+    }
+}
+
+impl Default for FmtExpr {
+    fn default() -> Self {
+        Self::TokenStream(TokenStream::new())
+    }
+}
+
+impl ToTokens for FmtExpr {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        match self {
+            Self::Ident(ident) => ident.to_tokens(tokens),
+            Self::TokenStream(ts) => ts.to_tokens(tokens),
+        }
+    }
+}
+
+impl Extend<TokenTree> for FmtExpr {
+    fn extend<T: IntoIterator<Item = TokenTree>>(&mut self, iter: T) {
+        *self = iter
+            .into_iter()
+            .fold(mem::take(self), |this, tt| match (this, tt) {
+                (Self::TokenStream(stream), TokenTree::Ident(ident))
+                    if stream.is_empty() =>
+                {
+                    Self::Ident(ident)
+                }
+                (Self::TokenStream(mut stream), tt) => {
+                    stream.extend([tt]);
+                    Self::TokenStream(stream)
+                }
+                (Self::Ident(ident), tt) => {
+                    let mut stream = ident.into_token_stream();
+                    stream.extend([tt]);
+                    Self::TokenStream(stream)
+                }
+            });
     }
 }
 
-/// [Parameter][1] used in [`Placeholder`].
+/// Representation of a [parameter][1] used in a [`Placeholder`].
 ///
 /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-parameters
 #[derive(Debug, Eq, PartialEq)]
@@ -795,13 +472,13 @@ enum Parameter {
 impl<'a> From<parsing::Argument<'a>> for Parameter {
     fn from(arg: parsing::Argument<'a>) -> Self {
         match arg {
-            parsing::Argument::Integer(i) => Parameter::Positional(i),
-            parsing::Argument::Identifier(i) => Parameter::Named(i.to_owned()),
+            parsing::Argument::Integer(i) => Self::Positional(i),
+            parsing::Argument::Identifier(i) => Self::Named(i.to_owned()),
         }
     }
 }
 
-/// Representation of formatting placeholder.
+/// Representation of a formatting placeholder.
 #[derive(Debug, PartialEq, Eq)]
 struct Placeholder {
     /// Formatting argument (either named or positional) to be used by this placeholder.
@@ -822,7 +499,7 @@ struct Placeholder {
 }
 
 impl Placeholder {
-    /// Parses [`Placeholder`]s from a given formatting string.
+    /// Parses [`Placeholder`]s from the provided formatting string.
     fn parse_fmt_string(s: &str) -> Vec<Self> {
         let mut n = 0;
         parsing::format_string(s)
@@ -860,87 +537,31 @@ impl Placeholder {
 }
 
 #[cfg(test)]
-mod placeholder_parse_fmt_string_spec {
-    use super::*;
-
-    #[test]
-    fn indicates_position_and_trait_name_for_each_fmt_placeholder() {
-        let fmt_string = "{},{:?},{{}},{{{1:0$}}}-{2:.1$x}{par:#?}{:width$}";
-        assert_eq!(
-            Placeholder::parse_fmt_string(&fmt_string),
-            vec![
-                Placeholder {
-                    arg: Parameter::Positional(0),
-                    width: None,
-                    precision: None,
-                    trait_name: "Display",
-                },
-                Placeholder {
-                    arg: Parameter::Positional(1),
-                    width: None,
-                    precision: None,
-                    trait_name: "Debug",
-                },
-                Placeholder {
-                    arg: Parameter::Positional(1),
-                    width: Some(Parameter::Positional(0)),
-                    precision: None,
-                    trait_name: "Display",
-                },
-                Placeholder {
-                    arg: Parameter::Positional(2),
-                    width: None,
-                    precision: Some(Parameter::Positional(1)),
-                    trait_name: "LowerHex",
-                },
-                Placeholder {
-                    arg: Parameter::Named("par".to_owned()),
-                    width: None,
-                    precision: None,
-                    trait_name: "Debug",
-                },
-                Placeholder {
-                    arg: Parameter::Positional(2),
-                    width: Some(Parameter::Named("width".to_owned())),
-                    precision: None,
-                    trait_name: "Display",
-                },
-            ],
-        );
-    }
-}
-
-#[cfg(test)]
-mod attribute_parse_fmt_args_spec {
+mod fmt_attribute_spec {
     use itertools::Itertools as _;
     use quote::ToTokens;
     use syn;
 
-    use super::Attribute;
+    use super::FmtAttribute;
 
     fn assert<'a>(input: &'a str, parsed: impl AsRef<[&'a str]>) {
         let parsed = parsed.as_ref();
-        match syn::parse_str::<Attribute>(&format!("(\"\", {})", input)).unwrap() {
-            Attribute::Fmt { fmt_args, .. } => {
-                let fmt_args = fmt_args
-                    .into_iter()
-                    .map(|arg| arg.into_token_stream().to_string())
-                    .collect::<Vec<String>>();
-                fmt_args.iter().zip_eq(parsed).enumerate().for_each(
-                    |(i, (found, expected))| {
-                        assert_eq!(
-                            *expected, found,
-                            "Mismatch at index {i}\n\
-                             Expected: {parsed:?}\n\
-                             Found: {fmt_args:?}",
-                        );
-                    },
+        let attr = syn::parse_str::<FmtAttribute>(&format!("\"\", {}", input)).unwrap();
+        let fmt_args = attr
+            .args
+            .into_iter()
+            .map(|arg| arg.into_token_stream().to_string())
+            .collect::<Vec<String>>();
+        fmt_args.iter().zip_eq(parsed).enumerate().for_each(
+            |(i, (found, expected))| {
+                assert_eq!(
+                    *expected, found,
+                    "Mismatch at index {i}\n\
+                     Expected: {parsed:?}\n\
+                     Found: {fmt_args:?}",
                 );
-            }
-            attrs @ Attribute::Bounds(_) => {
-                panic!("Expected `Attribute::Fmt`, found: {attrs:?}");
-            }
-        }
+            },
+        );
     }
 
     #[test]
@@ -989,3 +610,54 @@ mod attribute_parse_fmt_args_spec {
         }
     }
 }
+
+#[cfg(test)]
+mod placeholder_parse_fmt_string_spec {
+    use super::{Parameter, Placeholder};
+
+    #[test]
+    fn indicates_position_and_trait_name_for_each_fmt_placeholder() {
+        let fmt_string = "{},{:?},{{}},{{{1:0$}}}-{2:.1$x}{par:#?}{:width$}";
+        assert_eq!(
+            Placeholder::parse_fmt_string(&fmt_string),
+            vec![
+                Placeholder {
+                    arg: Parameter::Positional(0),
+                    width: None,
+                    precision: None,
+                    trait_name: "Display",
+                },
+                Placeholder {
+                    arg: Parameter::Positional(1),
+                    width: None,
+                    precision: None,
+                    trait_name: "Debug",
+                },
+                Placeholder {
+                    arg: Parameter::Positional(1),
+                    width: Some(Parameter::Positional(0)),
+                    precision: None,
+                    trait_name: "Display",
+                },
+                Placeholder {
+                    arg: Parameter::Positional(2),
+                    width: None,
+                    precision: Some(Parameter::Positional(1)),
+                    trait_name: "LowerHex",
+                },
+                Placeholder {
+                    arg: Parameter::Named("par".to_owned()),
+                    width: None,
+                    precision: None,
+                    trait_name: "Debug",
+                },
+                Placeholder {
+                    arg: Parameter::Positional(2),
+                    width: Some(Parameter::Named("width".to_owned())),
+                    precision: None,
+                    trait_name: "Display",
+                },
+            ],
+        );
+    }
+}
diff --git a/impl/src/parsing.rs b/impl/src/fmt/parsing.rs
similarity index 98%
rename from impl/src/parsing.rs
rename to impl/src/fmt/parsing.rs
index c465a04..e458666 100644
--- a/impl/src/parsing.rs
+++ b/impl/src/fmt/parsing.rs
@@ -1,3 +1,7 @@
+//! Parsing of [Rust `fmt` syntax][0].
+//!
+//! [0]: std::fmt#syntax
+
 use std::{convert::identity, iter};
 
 use unicode_xid::UnicodeXID as XID;
@@ -9,6 +13,8 @@ pub(crate) struct FormatString<'a> {
 }
 
 /// Output of the [`format`] parser.
+///
+/// [`format`]: fn@format
 #[derive(Debug, Clone, Copy, Eq, PartialEq)]
 pub(crate) struct Format<'a> {
     pub(crate) arg: Option<Argument<'a>>,
@@ -156,6 +162,7 @@ pub(crate) fn format_string(input: &str) -> Option<FormatString<'_>> {
 /// {par:-^#.0$?}
 /// ```
 ///
+/// [`format`]: fn@format
 /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax
 fn maybe_format(input: &str) -> Option<(LeftToParse<'_>, MaybeFormat<'_>)> {
     alt(&mut [
@@ -179,6 +186,7 @@ fn maybe_format(input: &str) -> Option<(LeftToParse<'_>, MaybeFormat<'_>)> {
 /// {par:-^#.0$?}
 /// ```
 ///
+/// [`format`]: fn@format
 /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#syntax
 fn format(input: &str) -> Option<(LeftToParse<'_>, Format<'_>)> {
     let input = char('{')(input)?;
@@ -222,7 +230,7 @@ fn argument(input: &str) -> Option<(LeftToParse<'_>, Argument)> {
 ///
 /// # Grammar
 ///
-/// [`format_spec`]` := [[fill]align][sign]['#']['0'][`[`width`]`]`
+/// [`format_spec`]` := [[fill]align][sign]['#']['0'][width]`
 ///                     `['.' `[`precision`]`]`[`type`]
 ///
 /// # Example
@@ -545,6 +553,8 @@ fn take_while1(
 /// Parses with `basic` while `until` returns [`None`]. Returns [`None`] in case
 /// `until` succeeded initially or `basic` never succeeded. Doesn't consume
 /// [`char`]s parsed by `until`.
+///
+/// [`char`]: fn@char
 fn take_until1(
     mut basic: impl FnMut(&str) -> Option<&str>,
     mut until: impl FnMut(&str) -> Option<&str>,
@@ -582,6 +592,8 @@ fn char(c: char) -> impl FnMut(&str) -> Option<LeftToParse<'_>> {
 }
 
 /// Checks whether first [`char`] suits `check`.
+///
+/// [`char`]: fn@char
 fn check_char(
     mut check: impl FnMut(char) -> bool,
 ) -> impl FnMut(&str) -> Option<LeftToParse<'_>> {
@@ -594,11 +606,15 @@ fn check_char(
 }
 
 /// Checks whether first [`char`] of input is present in `chars`.
+///
+/// [`char`]: fn@char
 fn one_of(chars: &str) -> impl FnMut(&str) -> Option<LeftToParse<'_>> + '_ {
     move |input: &str| chars.chars().find_map(|c| char(c)(input))
 }
 
 /// Parses any [`char`].
+///
+/// [`char`]: fn@char
 fn any_char(input: &str) -> Option<LeftToParse<'_>> {
     input.chars().next().map(|c| &input[c.len_utf8()..])
 }
diff --git a/impl/src/lib.rs b/impl/src/lib.rs
index da8f8c6..394eeb4 100644
--- a/impl/src/lib.rs
+++ b/impl/src/lib.rs
@@ -31,10 +31,10 @@ mod constructor;
 mod deref;
 #[cfg(feature = "deref_mut")]
 mod deref_mut;
-#[cfg(feature = "display")]
-mod display;
 #[cfg(feature = "error")]
 mod error;
+#[cfg(any(feature = "debug", feature = "display"))]
+mod fmt;
 #[cfg(feature = "from")]
 mod from;
 #[cfg(feature = "from_str")]
@@ -57,8 +57,6 @@ mod mul_helpers;
 mod mul_like;
 #[cfg(feature = "not")]
 mod not_like;
-#[cfg(feature = "display")]
-mod parsing;
 #[cfg(feature = "sum")]
 mod sum_like;
 #[cfg(feature = "try_into")]
@@ -90,13 +88,13 @@ impl Output for Result<proc_macro2::TokenStream, ParseError> {
 }
 
 macro_rules! create_derive(
-    ($feature:literal, $mod_:ident, $trait_:ident, $fn_name: ident $(,$attribute:ident)* $(,)?) => {
+    ($feature:literal, $mod_:ident $(:: $mod_rest:ident)*, $trait_:ident, $fn_name: ident $(,$attribute:ident)* $(,)?) => {
         #[cfg(feature = $feature)]
         #[proc_macro_derive($trait_, attributes($($attribute),*))]
         #[doc = include_str!(concat!("../doc/", $feature, ".md"))]
         pub fn $fn_name(input: TokenStream) -> TokenStream {
             let ast = syn::parse(input).unwrap();
-            Output::process($mod_::expand(&ast, stringify!($trait_)))
+            Output::process($mod_$(:: $mod_rest)*::expand(&ast, stringify!($trait_)))
         }
     }
 );
@@ -186,15 +184,40 @@ create_derive!("error", error, Error, error_derive, error);
 
 create_derive!("from_str", from_str, FromStr, from_str_derive);
 
-create_derive!("display", display, Display, display_derive, display);
-create_derive!("display", display, Binary, binary_derive, binary);
-create_derive!("display", display, Octal, octal_derive, octal);
-create_derive!("display", display, LowerHex, lower_hex_derive, lower_hex);
-create_derive!("display", display, UpperHex, upper_hex_derive, upper_hex);
-create_derive!("display", display, LowerExp, lower_exp_derive, lower_exp);
-create_derive!("display", display, UpperExp, upper_exp_derive, upper_exp);
-create_derive!("display", display, Pointer, pointer_derive, pointer);
-create_derive!("display", display, DebugCustom, debug_custom_derive, debug);
+create_derive!("debug", fmt::debug, Debug, debug_derive, debug);
+
+create_derive!("display", fmt::display, Display, display_derive, display);
+create_derive!("display", fmt::display, Binary, binary_derive, binary);
+create_derive!("display", fmt::display, Octal, octal_derive, octal);
+create_derive!(
+    "display",
+    fmt::display,
+    LowerHex,
+    lower_hex_derive,
+    lower_hex,
+);
+create_derive!(
+    "display",
+    fmt::display,
+    UpperHex,
+    upper_hex_derive,
+    upper_hex,
+);
+create_derive!(
+    "display",
+    fmt::display,
+    LowerExp,
+    lower_exp_derive,
+    lower_exp,
+);
+create_derive!(
+    "display",
+    fmt::display,
+    UpperExp,
+    upper_exp_derive,
+    upper_exp,
+);
+create_derive!("display", fmt::display, Pointer, pointer_derive, pointer);
 
 create_derive!("index", index, Index, index_derive, index);
 create_derive!(
@@ -232,7 +255,7 @@ create_derive!(
     is_variant,
     IsVariant,
     is_variant_derive,
-    is_variant
+    is_variant,
 );
 
 create_derive!("unwrap", unwrap, Unwrap, unwrap_derive, unwrap);
diff --git a/src/fmt.rs b/src/fmt.rs
new file mode 100644
index 0000000..9808556
--- /dev/null
+++ b/src/fmt.rs
@@ -0,0 +1,184 @@
+//! [`core::fmt::DebugTuple`] reimplementation with
+//! [`DebugTuple::finish_non_exhaustive()`] method.
+
+pub use core::fmt::{Debug, Error, Formatter, Result, Write};
+
+/// Same as [`core::fmt::DebugTuple`], but with
+/// [`DebugTuple::finish_non_exhaustive()`] method.
+#[must_use = "must eventually call `finish()` or `finish_non_exhaustive()` on \
+              Debug builders"]
+pub struct DebugTuple<'a, 'b: 'a> {
+    fmt: &'a mut Formatter<'b>,
+    result: Result,
+    fields: usize,
+    empty_name: bool,
+}
+
+/// Creates a new [`DebugTuple`].
+pub fn debug_tuple<'a, 'b>(
+    fmt: &'a mut Formatter<'b>,
+    name: &str,
+) -> DebugTuple<'a, 'b> {
+    let result = fmt.write_str(name);
+    DebugTuple {
+        fmt,
+        result,
+        fields: 0,
+        empty_name: name.is_empty(),
+    }
+}
+
+impl<'a, 'b: 'a> DebugTuple<'a, 'b> {
+    /// Adds a new field to the generated tuple struct output.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// use derive_more::fmt;
+    ///
+    /// struct Foo(i32, String);
+    ///
+    /// impl fmt::Debug for Foo {
+    ///     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+    ///         fmt::debug_tuple(fmt, "Foo")
+    ///             .field(&self.0) // We add the first field.
+    ///             .field(&self.1) // We add the second field.
+    ///             .finish() // We're good to go!
+    ///     }
+    /// }
+    ///
+    /// assert_eq!(
+    ///     format!("{:?}", Foo(10, "Hello World".to_string())),
+    ///     "Foo(10, \"Hello World\")",
+    /// );
+    /// ```
+    pub fn field(&mut self, value: &dyn Debug) -> &mut Self {
+        self.result = self.result.and_then(|_| {
+            if self.is_pretty() {
+                if self.fields == 0 {
+                    self.fmt.write_str("(\n")?;
+                }
+
+                let mut padded_formatter = Padded::new(self.fmt);
+                padded_formatter.write_fmt(format_args!("{value:#?}"))?;
+                padded_formatter.write_str(",\n")
+            } else {
+                let prefix = if self.fields == 0 { "(" } else { ", " };
+                self.fmt.write_str(prefix)?;
+                value.fmt(self.fmt)
+            }
+        });
+
+        self.fields += 1;
+        self
+    }
+
+    /// Finishes output and returns any error encountered.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use derive_more::fmt;
+    ///
+    /// struct Foo(i32, String);
+    ///
+    /// impl fmt::Debug for Foo {
+    ///     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+    ///         fmt::debug_tuple(fmt, "Foo")
+    ///             .field(&self.0)
+    ///             .field(&self.1)
+    ///             .finish() // You need to call it to "finish" the
+    ///                       // tuple formatting.
+    ///     }
+    /// }
+    ///
+    /// assert_eq!(
+    ///     format!("{:?}", Foo(10, "Hello World".to_string())),
+    ///     "Foo(10, \"Hello World\")",
+    /// );
+    /// ```
+    pub fn finish(&mut self) -> Result {
+        if self.fields > 0 {
+            self.result = self.result.and_then(|_| {
+                if self.fields == 1 && self.empty_name && !self.is_pretty() {
+                    self.fmt.write_str(",")?;
+                }
+                self.fmt.write_str(")")
+            });
+        }
+        self.result
+    }
+
+    /// Marks the struct as non-exhaustive, indicating to the reader that there are some other
+    /// fields that are not shown in the debug representation, and finishes output, returning any
+    /// error encountered.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// use derive_more::fmt;
+    ///
+    /// struct Bar(i32, f32);
+    ///
+    /// impl fmt::Debug for Bar {
+    ///     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
+    ///         fmt::debug_tuple(fmt, "Bar")
+    ///             .field(&self.0)
+    ///             .finish_non_exhaustive() // Show that some other field(s) exist.
+    ///     }
+    /// }
+    ///
+    /// assert_eq!(format!("{:?}", Bar(10, 1.0)), "Bar(10, ..)");
+    /// ```
+    pub fn finish_non_exhaustive(&mut self) -> Result {
+        self.result = self.result.and_then(|_| {
+            if self.fields > 0 {
+                if self.is_pretty() {
+                    let mut padded_formatter = Padded::new(self.fmt);
+                    padded_formatter.write_str("..\n")?;
+                    self.fmt.write_str(")")
+                } else {
+                    self.fmt.write_str(", ..)")
+                }
+            } else {
+                self.fmt.write_str("(..)")
+            }
+        });
+        self.result
+    }
+
+    fn is_pretty(&self) -> bool {
+        self.fmt.alternate()
+    }
+}
+
+/// Wrapper for a [`Formatter`] adding 4 spaces on newlines for inner pretty
+/// printed [`Debug`] values.
+struct Padded<'a, 'b> {
+    formatter: &'a mut Formatter<'b>,
+    on_newline: bool,
+}
+
+impl<'a, 'b> Padded<'a, 'b> {
+    fn new(formatter: &'a mut Formatter<'b>) -> Self {
+        Self {
+            formatter,
+            on_newline: true,
+        }
+    }
+}
+
+impl<'a, 'b> Write for Padded<'a, 'b> {
+    fn write_str(&mut self, s: &str) -> Result {
+        for s in s.split_inclusive('\n') {
+            if self.on_newline {
+                self.formatter.write_str("    ")?;
+            }
+
+            self.on_newline = s.ends_with('\n');
+            self.formatter.write_str(s)?;
+        }
+
+        Ok(())
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index bd2fd17..7602c24 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,6 +8,7 @@
 //! [`IntoIterator`]: crate::IntoIterator
 //! [`AsRef`]: crate::AsRef
 //!
+//! [`Debug`]: crate::Debug
 //! [`Display`-like]: crate::Display
 //!
 //! [`Error`]: crate::Error
@@ -51,6 +52,9 @@ mod convert;
 #[cfg(feature = "try_into")]
 pub use self::convert::TryIntoError;
 
+#[cfg(feature = "debug")]
+pub mod fmt;
+
 #[cfg(any(feature = "add", feature = "not"))]
 pub mod ops;
 


Test patch:
diff --git a/tests/compile_fail/debug/both_fmt_and_skip_on_field.rs b/tests/compile_fail/debug/both_fmt_and_skip_on_field.rs
new file mode 100644
index 0000000..2422790
--- /dev/null
+++ b/tests/compile_fail/debug/both_fmt_and_skip_on_field.rs
@@ -0,0 +1,8 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug("Stuff({}): {}", bar)]
+    #[debug(skip)]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/both_fmt_and_skip_on_field.stderr b/tests/compile_fail/debug/both_fmt_and_skip_on_field.stderr
new file mode 100644
index 0000000..31167d3
--- /dev/null
+++ b/tests/compile_fail/debug/both_fmt_and_skip_on_field.stderr
@@ -0,0 +1,5 @@
+error: only single `#[debug(...)]` attribute is allowed here
+ --> tests/compile_fail/debug/both_fmt_and_skip_on_field.rs:3:7
+  |
+3 |     #[debug("Stuff({}): {}", bar)]
+  |       ^^^^^
diff --git a/tests/compile_fail/debug/extra_placeholder.rs b/tests/compile_fail/debug/extra_placeholder.rs
new file mode 100644
index 0000000..b318701
--- /dev/null
+++ b/tests/compile_fail/debug/extra_placeholder.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug("Stuff({}): {}", bar)]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/extra_placeholder.stderr b/tests/compile_fail/debug/extra_placeholder.stderr
new file mode 100644
index 0000000..ed2792c
--- /dev/null
+++ b/tests/compile_fail/debug/extra_placeholder.stderr
@@ -0,0 +1,5 @@
+error: 2 positional arguments in format string, but there is 1 argument
+ --> tests/compile_fail/debug/extra_placeholder.rs:3:20
+  |
+3 |     #[debug("Stuff({}): {}", bar)]
+  |                    ^^   ^^   ---
diff --git a/tests/compile_fail/debug/legacy_bound_syntax.rs b/tests/compile_fail/debug/legacy_bound_syntax.rs
new file mode 100644
index 0000000..72d1e0a
--- /dev/null
+++ b/tests/compile_fail/debug/legacy_bound_syntax.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+#[debug(bound = "String: std::fmt::Display")]
+pub struct Foo {
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/legacy_bound_syntax.stderr b/tests/compile_fail/debug/legacy_bound_syntax.stderr
new file mode 100644
index 0000000..b5ff58e
--- /dev/null
+++ b/tests/compile_fail/debug/legacy_bound_syntax.stderr
@@ -0,0 +1,5 @@
+error: legacy syntax, use `bound(String: std::fmt::Display)` instead
+ --> tests/compile_fail/debug/legacy_bound_syntax.rs:2:8
+  |
+2 | #[debug(bound = "String: std::fmt::Display")]
+  |        ^
diff --git a/tests/compile_fail/debug/legacy_fmt_syntax.rs b/tests/compile_fail/debug/legacy_fmt_syntax.rs
new file mode 100644
index 0000000..ff49445
--- /dev/null
+++ b/tests/compile_fail/debug/legacy_fmt_syntax.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug(fmt = "Stuff({}): {}", "bar")]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/legacy_fmt_syntax.stderr b/tests/compile_fail/debug/legacy_fmt_syntax.stderr
new file mode 100644
index 0000000..3d182ca
--- /dev/null
+++ b/tests/compile_fail/debug/legacy_fmt_syntax.stderr
@@ -0,0 +1,5 @@
+error: legacy syntax, use `"Stuff({}): {}", bar` instead
+ --> tests/compile_fail/debug/legacy_fmt_syntax.rs:3:12
+  |
+3 |     #[debug(fmt = "Stuff({}): {}", "bar")]
+  |            ^
diff --git a/tests/compile_fail/debug/missing_placeholder.rs b/tests/compile_fail/debug/missing_placeholder.rs
new file mode 100644
index 0000000..1a27e9e
--- /dev/null
+++ b/tests/compile_fail/debug/missing_placeholder.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug("Stuff()", bar)]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/missing_placeholder.stderr b/tests/compile_fail/debug/missing_placeholder.stderr
new file mode 100644
index 0000000..9e084d1
--- /dev/null
+++ b/tests/compile_fail/debug/missing_placeholder.stderr
@@ -0,0 +1,7 @@
+error: argument never used
+ --> tests/compile_fail/debug/missing_placeholder.rs:3:24
+  |
+3 |     #[debug("Stuff()", bar)]
+  |             ---------  ^^^ argument never used
+  |             |
+  |             formatting specifier missing
diff --git a/tests/compile_fail/debug/named_field_prefixed_with_dot.rs b/tests/compile_fail/debug/named_field_prefixed_with_dot.rs
new file mode 100644
index 0000000..0c2284a
--- /dev/null
+++ b/tests/compile_fail/debug/named_field_prefixed_with_dot.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug("Stuff({})", .bar)]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/named_field_prefixed_with_dot.stderr b/tests/compile_fail/debug/named_field_prefixed_with_dot.stderr
new file mode 100644
index 0000000..48af5a4
--- /dev/null
+++ b/tests/compile_fail/debug/named_field_prefixed_with_dot.stderr
@@ -0,0 +1,5 @@
+error: expected expression, found `.`
+ --> tests/compile_fail/debug/named_field_prefixed_with_dot.rs:3:26
+  |
+3 |     #[debug("Stuff({})", .bar)]
+  |                          ^ expected expression
diff --git a/tests/compile_fail/debug/unclosed_brace.rs b/tests/compile_fail/debug/unclosed_brace.rs
new file mode 100644
index 0000000..47414f5
--- /dev/null
+++ b/tests/compile_fail/debug/unclosed_brace.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug("Stuff({)", bar)]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/unclosed_brace.stderr b/tests/compile_fail/debug/unclosed_brace.stderr
new file mode 100644
index 0000000..b433aec
--- /dev/null
+++ b/tests/compile_fail/debug/unclosed_brace.stderr
@@ -0,0 +1,9 @@
+error: invalid format string: expected `'}'`, found `')'`
+ --> tests/compile_fail/debug/unclosed_brace.rs:3:21
+  |
+3 |     #[debug("Stuff({)", bar)]
+  |                    -^ expected `}` in format string
+  |                    |
+  |                    because of this opening brace
+  |
+  = note: if you intended to print `{`, you can escape it using `{{`
diff --git a/tests/compile_fail/debug/union.rs b/tests/compile_fail/debug/union.rs
new file mode 100644
index 0000000..235f46a
--- /dev/null
+++ b/tests/compile_fail/debug/union.rs
@@ -0,0 +1,6 @@
+#[derive(derive_more::Debug)]
+pub union Foo {
+    bar: i32,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/union.stderr b/tests/compile_fail/debug/union.stderr
new file mode 100644
index 0000000..1b7c723
--- /dev/null
+++ b/tests/compile_fail/debug/union.stderr
@@ -0,0 +1,5 @@
+error: `Debug` cannot be derived for unions
+ --> tests/compile_fail/debug/union.rs:2:1
+  |
+2 | pub union Foo {
+  | ^^^
diff --git a/tests/compile_fail/debug/unknown_attribute.rs b/tests/compile_fail/debug/unknown_attribute.rs
new file mode 100644
index 0000000..d9c8f88
--- /dev/null
+++ b/tests/compile_fail/debug/unknown_attribute.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+#[debug(unknown = "unknown")]
+pub struct Foo {
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/unknown_attribute.stderr b/tests/compile_fail/debug/unknown_attribute.stderr
new file mode 100644
index 0000000..63b1896
--- /dev/null
+++ b/tests/compile_fail/debug/unknown_attribute.stderr
@@ -0,0 +1,5 @@
+error: unknown attribute, expected `bound(...)`
+ --> tests/compile_fail/debug/unknown_attribute.rs:2:9
+  |
+2 | #[debug(unknown = "unknown")]
+  |         ^^^^^^^
diff --git a/tests/compile_fail/debug/unnamed_field_prefixed_with_dot.rs b/tests/compile_fail/debug/unnamed_field_prefixed_with_dot.rs
new file mode 100644
index 0000000..2bff1e9
--- /dev/null
+++ b/tests/compile_fail/debug/unnamed_field_prefixed_with_dot.rs
@@ -0,0 +1,4 @@
+#[derive(derive_more::Debug)]
+pub struct Foo(#[debug("Stuff({})", .0)] String);
+
+fn main() {}
diff --git a/tests/compile_fail/debug/unnamed_field_prefixed_with_dot.stderr b/tests/compile_fail/debug/unnamed_field_prefixed_with_dot.stderr
new file mode 100644
index 0000000..57db855
--- /dev/null
+++ b/tests/compile_fail/debug/unnamed_field_prefixed_with_dot.stderr
@@ -0,0 +1,5 @@
+error: float literals must have an integer part
+ --> tests/compile_fail/debug/unnamed_field_prefixed_with_dot.rs:2:37
+  |
+2 | pub struct Foo(#[debug("Stuff({})", .0)] String);
+  |                                     ^^ help: must have an integer part: `0.0`
diff --git a/tests/compile_fail/debug/wrong_formatting_argument.rs b/tests/compile_fail/debug/wrong_formatting_argument.rs
new file mode 100644
index 0000000..c280dec
--- /dev/null
+++ b/tests/compile_fail/debug/wrong_formatting_argument.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug("Stuff({bar:M})")]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/wrong_formatting_argument.stderr b/tests/compile_fail/debug/wrong_formatting_argument.stderr
new file mode 100644
index 0000000..c340470
--- /dev/null
+++ b/tests/compile_fail/debug/wrong_formatting_argument.stderr
@@ -0,0 +1,16 @@
+error: unknown format trait `M`
+ --> tests/compile_fail/debug/wrong_formatting_argument.rs:3:25
+  |
+3 |     #[debug("Stuff({bar:M})")]
+  |                         ^
+  |
+  = note: the only appropriate formatting traits are:
+          - ``, which uses the `Display` trait
+          - `?`, which uses the `Debug` trait
+          - `e`, which uses the `LowerExp` trait
+          - `E`, which uses the `UpperExp` trait
+          - `o`, which uses the `Octal` trait
+          - `p`, which uses the `Pointer` trait
+          - `b`, which uses the `Binary` trait
+          - `x`, which uses the `LowerHex` trait
+          - `X`, which uses the `UpperHex` trait
diff --git a/tests/compile_fail/debug/wrong_named_parameter.rs b/tests/compile_fail/debug/wrong_named_parameter.rs
new file mode 100644
index 0000000..292b8ab
--- /dev/null
+++ b/tests/compile_fail/debug/wrong_named_parameter.rs
@@ -0,0 +1,7 @@
+#[derive(derive_more::Debug)]
+pub struct Foo {
+    #[debug("Stuff({bars})")]
+    bar: String,
+}
+
+fn main() {}
diff --git a/tests/compile_fail/debug/wrong_named_parameter.stderr b/tests/compile_fail/debug/wrong_named_parameter.stderr
new file mode 100644
index 0000000..23dae36
--- /dev/null
+++ b/tests/compile_fail/debug/wrong_named_parameter.stderr
@@ -0,0 +1,5 @@
+error[E0425]: cannot find value `bars` in this scope
+ --> tests/compile_fail/debug/wrong_named_parameter.rs:3:21
+  |
+3 |     #[debug("Stuff({bars})")]
+  |                     ^^^^ help: a local variable with a similar name exists: `bar`
diff --git a/tests/compile_fail/debug/wrong_unnamed_parameter.rs b/tests/compile_fail/debug/wrong_unnamed_parameter.rs
new file mode 100644
index 0000000..84892e3
--- /dev/null
+++ b/tests/compile_fail/debug/wrong_unnamed_parameter.rs
@@ -0,0 +1,4 @@
+#[derive(derive_more::Debug)]
+pub struct Foo(#[debug("Stuff({_1})")] String);
+
+fn main() {}
diff --git a/tests/compile_fail/debug/wrong_unnamed_parameter.stderr b/tests/compile_fail/debug/wrong_unnamed_parameter.stderr
new file mode 100644
index 0000000..b773ab8
--- /dev/null
+++ b/tests/compile_fail/debug/wrong_unnamed_parameter.stderr
@@ -0,0 +1,5 @@
+error[E0425]: cannot find value `_1` in this scope
+ --> tests/compile_fail/debug/wrong_unnamed_parameter.rs:2:32
+  |
+2 | pub struct Foo(#[debug("Stuff({_1})")] String);
+  |                                ^^ help: a local variable with a similar name exists: `_0`
diff --git a/tests/compile_fail/display/legacy_bound_syntax.stderr b/tests/compile_fail/display/legacy_bound_syntax.stderr
index b5ed454..6d0daef 100644
--- a/tests/compile_fail/display/legacy_bound_syntax.stderr
+++ b/tests/compile_fail/display/legacy_bound_syntax.stderr
@@ -1,4 +1,4 @@
-error: Legacy syntax, use: `bound(String: std::fmt::Display)`
+error: legacy syntax, use `bound(String: std::fmt::Display)` instead
  --> tests/compile_fail/display/legacy_bound_syntax.rs:2:10
   |
 2 | #[display(bound = "String: std::fmt::Display")]
diff --git a/tests/compile_fail/display/legacy_fmt_syntax.stderr b/tests/compile_fail/display/legacy_fmt_syntax.stderr
index 29f41ee..0cbb253 100644
--- a/tests/compile_fail/display/legacy_fmt_syntax.stderr
+++ b/tests/compile_fail/display/legacy_fmt_syntax.stderr
@@ -1,4 +1,4 @@
-error: Legacy syntax, use: `"Stuff({}): {}", bar`
+error: legacy syntax, use `"Stuff({}): {}", bar` instead
  --> tests/compile_fail/display/legacy_fmt_syntax.rs:2:10
   |
 2 | #[display(fmt = "Stuff({}): {}", "bar")]
diff --git a/tests/compile_fail/display/non_display_implicit_enum_unit_variant.stderr b/tests/compile_fail/display/non_display_implicit_enum_unit_variant.stderr
index 3607a2e..5248df9 100644
--- a/tests/compile_fail/display/non_display_implicit_enum_unit_variant.stderr
+++ b/tests/compile_fail/display/non_display_implicit_enum_unit_variant.stderr
@@ -1,4 +1,4 @@
-error: Implicit formatting of unit enum variant is supported only for `Display` macro. Use `#[upper_hex("...")]` to explicitly specify the formatting.
+error: implicit formatting of unit enum variant is supported only for `Display` macro, use `#[upper_hex("...")]` to explicitly specify the formatting
  --> tests/compile_fail/display/non_display_implicit_enum_unit_variant.rs:3:5
   |
 3 |     UnitVariant,
diff --git a/tests/compile_fail/display/unknown_attribute.stderr b/tests/compile_fail/display/unknown_attribute.stderr
index 54e789f..5750586 100644
--- a/tests/compile_fail/display/unknown_attribute.stderr
+++ b/tests/compile_fail/display/unknown_attribute.stderr
@@ -1,4 +1,4 @@
-error: Unknown attribute. Expected `"...", ...` or `bound(...)`
+error: unknown attribute, expected `bound(...)`
  --> tests/compile_fail/display/unknown_attribute.rs:3:11
   |
 3 | #[display(unknown = "unknown")]
diff --git a/tests/debug.rs b/tests/debug.rs
new file mode 100644
index 0000000..daf3dab
--- /dev/null
+++ b/tests/debug.rs
@@ -0,0 +1,1097 @@
+#![allow(dead_code)]
+
+mod structs {
+    mod unit {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        struct Unit;
+
+        #[derive(Debug)]
+        struct Tuple();
+
+        #[derive(Debug)]
+        struct Struct {}
+
+        #[test]
+        fn assert() {
+            assert_eq!(format!("{:?}", Unit), "Unit");
+            assert_eq!(format!("{:#?}", Unit), "Unit");
+            assert_eq!(format!("{:?}", Tuple()), "Tuple");
+            assert_eq!(format!("{:#?}", Tuple()), "Tuple");
+            assert_eq!(format!("{:?}", Struct {}), "Struct");
+            assert_eq!(format!("{:#?}", Struct {}), "Struct");
+        }
+    }
+
+    mod single_field {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        struct Tuple(i32);
+
+        #[derive(Debug)]
+        struct Struct {
+            field: i32,
+        }
+
+        #[test]
+        fn assert() {
+            assert_eq!(format!("{:?}", Tuple(0)), "Tuple(0)");
+            assert_eq!(format!("{:#?}", Tuple(0)), "Tuple(\n    0,\n)");
+            assert_eq!(format!("{:?}", Struct { field: 0 }), "Struct { field: 0 }");
+            assert_eq!(
+                format!("{:#?}", Struct { field: 0 }),
+                "Struct {\n    field: 0,\n}",
+            );
+        }
+
+        mod str_field {
+            use derive_more::Debug;
+
+            #[derive(Debug)]
+            struct Tuple(#[debug("i32")] i32);
+
+            #[derive(Debug)]
+            struct Struct {
+                #[debug("i32")]
+                field: i32,
+            }
+
+            #[test]
+            fn assert() {
+                assert_eq!(format!("{:?}", Tuple(0)), "Tuple(i32)");
+                assert_eq!(format!("{:#?}", Tuple(0)), "Tuple(\n    i32,\n)");
+                assert_eq!(
+                    format!("{:?}", Struct { field: 0 }),
+                    "Struct { field: i32 }",
+                );
+                assert_eq!(
+                    format!("{:#?}", Struct { field: 0 }),
+                    "Struct {\n    field: i32,\n}",
+                );
+            }
+        }
+
+        mod interpolated_field {
+            use derive_more::Debug;
+
+            #[derive(Debug)]
+            struct Tuple(#[debug("{_0}.{}", _0)] i32);
+
+            #[derive(Debug)]
+            struct Struct {
+                #[debug("{field}.{}", field)]
+                field: i32,
+            }
+
+            #[test]
+            fn assert() {
+                assert_eq!(format!("{:?}", Tuple(0)), "Tuple(0.0)");
+                assert_eq!(format!("{:#?}", Tuple(0)), "Tuple(\n    0.0,\n)");
+                assert_eq!(
+                    format!("{:?}", Struct { field: 0 }),
+                    "Struct { field: 0.0 }",
+                );
+                assert_eq!(
+                    format!("{:#?}", Struct { field: 0 }),
+                    "Struct {\n    field: 0.0,\n}",
+                );
+            }
+        }
+
+        mod ignore {
+            use derive_more::Debug;
+
+            #[derive(Debug)]
+            struct Tuple(#[debug(ignore)] i32);
+
+            #[derive(Debug)]
+            struct Struct {
+                #[debug(skip)]
+                field: i32,
+            }
+
+            #[test]
+            fn assert() {
+                assert_eq!(format!("{:?}", Tuple(0)), "Tuple(..)");
+                assert_eq!(format!("{:#?}", Tuple(0)), "Tuple(..)");
+                assert_eq!(format!("{:?}", Struct { field: 0 }), "Struct { .. }");
+                assert_eq!(format!("{:#?}", Struct { field: 0 }), "Struct { .. }");
+            }
+        }
+    }
+
+    mod multi_field {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        struct Tuple(i32, i32);
+
+        #[derive(Debug)]
+        struct Struct {
+            field1: i32,
+            field2: i32,
+        }
+
+        #[test]
+        fn assert() {
+            assert_eq!(format!("{:?}", Tuple(1, 2)), "Tuple(1, 2)");
+            assert_eq!(format!("{:#?}", Tuple(1, 2)), "Tuple(\n    1,\n    2,\n)");
+            assert_eq!(
+                format!(
+                    "{:?}",
+                    Struct {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "Struct { field1: 1, field2: 2 }",
+            );
+            assert_eq!(
+                format!(
+                    "{:#?}",
+                    Struct {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "Struct {\n    field1: 1,\n    field2: 2,\n}",
+            );
+        }
+
+        mod str_field {
+            use derive_more::Debug;
+
+            #[derive(Debug)]
+            struct Tuple(i32, #[debug("i32")] i32);
+
+            #[derive(Debug)]
+            struct Struct {
+                #[debug("i32")]
+                field1: i32,
+                field2: i32,
+            }
+
+            #[test]
+            fn assert() {
+                assert_eq!(format!("{:?}", Tuple(1, 2)), "Tuple(1, i32)");
+                assert_eq!(
+                    format!("{:#?}", Tuple(1, 2)),
+                    "Tuple(\n    1,\n    i32,\n)",
+                );
+                assert_eq!(
+                    format!(
+                        "{:?}",
+                        Struct {
+                            field1: 1,
+                            field2: 2,
+                        }
+                    ),
+                    "Struct { field1: i32, field2: 2 }",
+                );
+                assert_eq!(
+                    format!(
+                        "{:#?}",
+                        Struct {
+                            field1: 1,
+                            field2: 2,
+                        }
+                    ),
+                    "Struct {\n    field1: i32,\n    field2: 2,\n}",
+                );
+            }
+        }
+
+        mod interpolated_field {
+            use derive_more::Debug;
+
+            #[derive(Debug)]
+            struct Tuple(i32, #[debug("{_0}.{}", _1)] i32);
+
+            #[derive(Debug)]
+            struct Struct {
+                #[debug("{field1}.{}", field2)]
+                field1: i32,
+                field2: i32,
+            }
+
+            #[test]
+            fn assert() {
+                assert_eq!(format!("{:?}", Tuple(1, 2)), "Tuple(1, 1.2)");
+                assert_eq!(
+                    format!("{:#?}", Tuple(1, 2)),
+                    "Tuple(\n    1,\n    1.2,\n)",
+                );
+                assert_eq!(
+                    format!(
+                        "{:?}",
+                        Struct {
+                            field1: 1,
+                            field2: 2,
+                        }
+                    ),
+                    "Struct { field1: 1.2, field2: 2 }",
+                );
+                assert_eq!(
+                    format!(
+                        "{:#?}",
+                        Struct {
+                            field1: 1,
+                            field2: 2,
+                        }
+                    ),
+                    "Struct {\n    field1: 1.2,\n    field2: 2,\n}",
+                );
+            }
+        }
+
+        mod ignore {
+            use derive_more::Debug;
+
+            #[derive(Debug)]
+            struct Tuple(#[debug(ignore)] i32, i32);
+
+            #[derive(Debug)]
+            struct Struct {
+                field1: i32,
+                #[debug(skip)]
+                field2: i32,
+            }
+
+            #[test]
+            fn assert() {
+                assert_eq!(format!("{:?}", Tuple(1, 2)), "Tuple(2, ..)");
+                assert_eq!(format!("{:#?}", Tuple(1, 2)), "Tuple(\n    2,\n    ..\n)",);
+                assert_eq!(
+                    format!(
+                        "{:?}",
+                        Struct {
+                            field1: 1,
+                            field2: 2
+                        }
+                    ),
+                    "Struct { field1: 1, .. }",
+                );
+                assert_eq!(
+                    format!(
+                        "{:#?}",
+                        Struct {
+                            field1: 1,
+                            field2: 2
+                        }
+                    ),
+                    "Struct {\n    field1: 1,\n    ..\n}",
+                );
+            }
+        }
+    }
+}
+
+mod enums {
+    mod no_variants {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        enum Void {}
+
+        const fn assert<T: std::fmt::Debug>() {}
+        const _: () = assert::<Void>();
+    }
+
+    mod unit_variant {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        enum Enum {
+            Unit,
+            Unnamed(),
+            Named {},
+        }
+
+        #[test]
+        fn assert() {
+            assert_eq!(format!("{:?}", Enum::Unit), "Unit");
+            assert_eq!(format!("{:#?}", Enum::Unit), "Unit");
+            assert_eq!(format!("{:?}", Enum::Unnamed()), "Unnamed");
+            assert_eq!(format!("{:#?}", Enum::Unnamed()), "Unnamed");
+            assert_eq!(format!("{:?}", Enum::Named {}), "Named");
+            assert_eq!(format!("{:#?}", Enum::Named {}), "Named");
+        }
+    }
+
+    mod single_field_variant {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        enum Enum {
+            Unnamed(i32),
+            Named {
+                field: i32,
+            },
+            StrUnnamed(#[debug("i32")] i32),
+            StrNamed {
+                #[debug("i32")]
+                field: i32,
+            },
+            InterpolatedUnnamed(#[debug("{_0}.{}", _0)] i32),
+            InterpolatedNamed {
+                #[debug("{field}.{}", field)]
+                field: i32,
+            },
+            SkippedUnnamed(#[debug(skip)] i32),
+            SkippedNamed {
+                #[debug(skip)]
+                field: i32,
+            },
+        }
+
+        #[test]
+        fn assert() {
+            assert_eq!(format!("{:?}", Enum::Unnamed(1)), "Unnamed(1)");
+            assert_eq!(format!("{:#?}", Enum::Unnamed(1)), "Unnamed(\n    1,\n)");
+            assert_eq!(
+                format!("{:?}", Enum::Named { field: 1 }),
+                "Named { field: 1 }",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::Named { field: 1 }),
+                "Named {\n    field: 1,\n}",
+            );
+            assert_eq!(format!("{:?}", Enum::StrUnnamed(1)), "StrUnnamed(i32)");
+            assert_eq!(
+                format!("{:#?}", Enum::StrUnnamed(1)),
+                "StrUnnamed(\n    i32,\n)",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::StrNamed { field: 1 }),
+                "StrNamed { field: i32 }",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::StrNamed { field: 1 }),
+                "StrNamed {\n    field: i32,\n}",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::InterpolatedUnnamed(1)),
+                "InterpolatedUnnamed(1.1)",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::InterpolatedUnnamed(1)),
+                "InterpolatedUnnamed(\n    1.1,\n)",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::InterpolatedNamed { field: 1 }),
+                "InterpolatedNamed { field: 1.1 }",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::InterpolatedNamed { field: 1 }),
+                "InterpolatedNamed {\n    field: 1.1,\n}",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::SkippedUnnamed(1)),
+                "SkippedUnnamed(..)",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::SkippedUnnamed(1)),
+                "SkippedUnnamed(..)",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::SkippedNamed { field: 1 }),
+                "SkippedNamed { .. }",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::SkippedNamed { field: 1 }),
+                "SkippedNamed { .. }",
+            );
+        }
+    }
+
+    mod multi_field_variant {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        enum Enum {
+            Unnamed(i32, i32),
+            Named {
+                field1: i32,
+                field2: i32,
+            },
+            StrUnnamed(#[debug("i32")] i32, i32),
+            StrNamed {
+                field1: i32,
+                #[debug("i32")]
+                field2: i32,
+            },
+            InterpolatedUnnamed(i32, #[debug("{_0}.{}", _1)] i32),
+            InterpolatedNamed {
+                #[debug("{field1}.{}", field2)]
+                field1: i32,
+                field2: i32,
+            },
+            SkippedUnnamed(i32, #[debug(skip)] i32),
+            SkippedNamed {
+                #[debug(skip)]
+                field1: i32,
+                field2: i32,
+            },
+        }
+
+        #[test]
+        fn assert() {
+            assert_eq!(format!("{:?}", Enum::Unnamed(1, 2)), "Unnamed(1, 2)");
+            assert_eq!(
+                format!("{:#?}", Enum::Unnamed(1, 2)),
+                "Unnamed(\n    1,\n    2,\n)",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::StrUnnamed(1, 2)),
+                "StrUnnamed(i32, 2)",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::StrUnnamed(1, 2)),
+                "StrUnnamed(\n    i32,\n    2,\n)",
+            );
+            assert_eq!(
+                format!(
+                    "{:?}",
+                    Enum::StrNamed {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "StrNamed { field1: 1, field2: i32 }",
+            );
+            assert_eq!(
+                format!(
+                    "{:#?}",
+                    Enum::StrNamed {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "StrNamed {\n    field1: 1,\n    field2: i32,\n}",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::InterpolatedUnnamed(1, 2)),
+                "InterpolatedUnnamed(1, 1.2)",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::InterpolatedUnnamed(1, 2)),
+                "InterpolatedUnnamed(\n    1,\n    1.2,\n)",
+            );
+            assert_eq!(
+                format!(
+                    "{:?}",
+                    Enum::InterpolatedNamed {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "InterpolatedNamed { field1: 1.2, field2: 2 }",
+            );
+            assert_eq!(
+                format!(
+                    "{:#?}",
+                    Enum::InterpolatedNamed {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "InterpolatedNamed {\n    field1: 1.2,\n    field2: 2,\n}",
+            );
+            assert_eq!(
+                format!("{:?}", Enum::SkippedUnnamed(1, 2)),
+                "SkippedUnnamed(1, ..)",
+            );
+            assert_eq!(
+                format!("{:#?}", Enum::SkippedUnnamed(1, 2)),
+                "SkippedUnnamed(\n    1,\n    ..\n)",
+            );
+            assert_eq!(
+                format!(
+                    "{:?}",
+                    Enum::SkippedNamed {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "SkippedNamed { field2: 2, .. }",
+            );
+            assert_eq!(
+                format!(
+                    "{:#?}",
+                    Enum::SkippedNamed {
+                        field1: 1,
+                        field2: 2,
+                    }
+                ),
+                "SkippedNamed {\n    field2: 2,\n    ..\n}",
+            );
+        }
+    }
+}
+
+mod generic {
+    use derive_more::Debug;
+
+    struct NotDebug;
+
+    trait Bound {}
+
+    impl Bound for () {}
+
+    fn display_bound<T: Bound>(_: &T) -> &'static str {
+        "()"
+    }
+
+    #[derive(Debug)]
+    struct NamedGenericStruct<T> {
+        field: T,
+    }
+    #[test]
+    fn named_generic_struct() {
+        assert_eq!(
+            format!("{:?}", NamedGenericStruct { field: 1 }),
+            "NamedGenericStruct { field: 1 }",
+        );
+        assert_eq!(
+            format!("{:#?}", NamedGenericStruct { field: 1 }),
+            "NamedGenericStruct {\n    field: 1,\n}",
+        );
+    }
+
+    #[derive(Debug)]
+    struct InterpolatedNamedGenericStruct<T> {
+        #[debug("{field}.{}", field)]
+        field: T,
+    }
+    #[test]
+    fn interpolated_named_generic_struct() {
+        assert_eq!(
+            format!("{:?}", InterpolatedNamedGenericStruct { field: 1 }),
+            "InterpolatedNamedGenericStruct { field: 1.1 }",
+        );
+        assert_eq!(
+            format!("{:#?}", InterpolatedNamedGenericStruct { field: 1 }),
+            "InterpolatedNamedGenericStruct {\n    field: 1.1,\n}",
+        );
+    }
+
+    #[derive(Debug)]
+    struct InterpolatedNamedGenericStructWidthPrecision<T> {
+        #[debug("{field:<>width$.prec$}.{field}")]
+        field: T,
+        width: usize,
+        prec: usize,
+    }
+    #[test]
+    fn interpolated_named_generic_struct_width_precision() {
+        assert_eq!(
+            format!(
+                "{:?}",
+                InterpolatedNamedGenericStructWidthPrecision {
+                    field: 1.2345,
+                    width: 9,
+                    prec: 2,
+                }
+            ),
+            "InterpolatedNamedGenericStructWidthPrecision { \
+                    field: <<<<<1.23.1.2345, \
+                    width: 9, \
+                    prec: 2 \
+                }",
+        );
+        assert_eq!(
+            format!(
+                "{:#?}",
+                InterpolatedNamedGenericStructWidthPrecision {
+                    field: 1.2345,
+                    width: 9,
+                    prec: 2,
+                }
+            ),
+            "InterpolatedNamedGenericStructWidthPrecision {\n    \
+                    field: <<<<<1.23.1.2345,\n    \
+                    width: 9,\n    \
+                    prec: 2,\n\
+                }",
+        );
+    }
+
+    #[derive(Debug)]
+    struct AliasedNamedGenericStruct<T> {
+        #[debug("{alias}", alias = field)]
+        field: T,
+    }
+    #[test]
+    fn aliased_named_generic_struct() {
+        assert_eq!(
+            format!("{:?}", AliasedNamedGenericStruct { field: 1 }),
+            "AliasedNamedGenericStruct { field: 1 }",
+        );
+        assert_eq!(
+            format!("{:#?}", AliasedNamedGenericStruct { field: 1 }),
+            "AliasedNamedGenericStruct {\n    field: 1,\n}",
+        );
+    }
+
+    #[derive(Debug)]
+    struct AliasedFieldNamedGenericStruct<T> {
+        #[debug("{field1}", field1 = field2)]
+        field1: T,
+        field2: i32,
+    }
+    #[test]
+    fn aliased_field_named_generic_struct() {
+        assert_eq!(
+            format!(
+                "{:?}",
+                AliasedFieldNamedGenericStruct {
+                    field1: NotDebug,
+                    field2: 1,
+                },
+            ),
+            "AliasedFieldNamedGenericStruct { field1: 1, field2: 1 }",
+        );
+        assert_eq!(
+            format!(
+                "{:#?}",
+                AliasedFieldNamedGenericStruct {
+                    field1: NotDebug,
+                    field2: 1,
+                },
+            ),
+            "AliasedFieldNamedGenericStruct {\n    field1: 1,\n    field2: 1,\n}",
+        );
+    }
+
+    #[derive(Debug)]
+    struct UnnamedGenericStruct<T>(T);
+    #[test]
+    fn unnamed_generic_struct() {
+        assert_eq!(
+            format!("{:?}", UnnamedGenericStruct(2)),
+            "UnnamedGenericStruct(2)",
+        );
+        assert_eq!(
+            format!("{:#?}", UnnamedGenericStruct(2)),
+            "UnnamedGenericStruct(\n    2,\n)",
+        );
+    }
+
+    #[derive(Debug)]
+    struct InterpolatedUnnamedGenericStruct<T>(#[debug("{}.{_0}", _0)] T);
+    #[test]
+    fn interpolated_unnamed_generic_struct() {
+        assert_eq!(
+            format!("{:?}", InterpolatedUnnamedGenericStruct(2)),
+            "InterpolatedUnnamedGenericStruct(2.2)",
+        );
+        assert_eq!(
+            format!("{:#?}", InterpolatedUnnamedGenericStruct(2)),
+            "InterpolatedUnnamedGenericStruct(\n    2.2,\n)",
+        );
+    }
+
+    #[derive(Debug)]
+    struct AliasedUnnamedGenericStruct<T>(#[debug("{alias}", alias = _0)] T);
+    #[test]
+    fn aliased_unnamed_generic_struct() {
+        assert_eq!(
+            format!("{:?}", AliasedUnnamedGenericStruct(2)),
+            "AliasedUnnamedGenericStruct(2)",
+        );
+        assert_eq!(
+            format!("{:#?}", AliasedUnnamedGenericStruct(2)),
+            "AliasedUnnamedGenericStruct(\n    2,\n)",
+        );
+    }
+
+    #[derive(Debug)]
+    struct AliasedFieldUnnamedGenericStruct<T>(#[debug("{_0}", _0 = _1)] T, i32);
+    #[test]
+    fn aliased_field_unnamed_generic_struct() {
+        assert_eq!(
+            format!("{:?}", AliasedFieldUnnamedGenericStruct(NotDebug, 2)),
+            "AliasedFieldUnnamedGenericStruct(2, 2)",
+        );
+        assert_eq!(
+            format!("{:#?}", AliasedFieldUnnamedGenericStruct(NotDebug, 2)),
+            "AliasedFieldUnnamedGenericStruct(\n    2,\n    2,\n)",
+        );
+    }
+
+    #[derive(Debug)]
+    enum GenericEnum<A, B> {
+        A { field: A },
+        B(B),
+    }
+    #[test]
+    fn generic_enum() {
+        assert_eq!(
+            format!("{:?}", GenericEnum::A::<_, u8> { field: 1 }),
+            "A { field: 1 }"
+        );
+        assert_eq!(
+            format!("{:#?}", GenericEnum::A::<_, u8> { field: 1 }),
+            "A {\n    field: 1,\n}"
+        );
+        assert_eq!(format!("{:?}", GenericEnum::B::<u8, _>(2)), "B(2)");
+        assert_eq!(
+            format!("{:#?}", GenericEnum::B::<u8, _>(2)),
+            "B(\n    2,\n)",
+        );
+    }
+
+    #[derive(Debug)]
+    enum InterpolatedGenericEnum<A, B> {
+        A {
+            #[debug("{}.{field}", field)]
+            field: A,
+        },
+        B(#[debug("{}.{_0}", _0)] B),
+    }
+    #[test]
+    fn interpolated_generic_enum() {
+        assert_eq!(
+            format!("{:?}", InterpolatedGenericEnum::A::<_, u8> { field: 1 }),
+            "A { field: 1.1 }",
+        );
+        assert_eq!(
+            format!("{:#?}", InterpolatedGenericEnum::A::<_, u8> { field: 1 }),
+            "A {\n    field: 1.1,\n}",
+        );
+        assert_eq!(
+            format!("{:?}", InterpolatedGenericEnum::B::<u8, _>(2)),
+            "B(2.2)",
+        );
+        assert_eq!(
+            format!("{:#?}", InterpolatedGenericEnum::B::<u8, _>(2)),
+            "B(\n    2.2,\n)",
+        );
+    }
+
+    #[derive(Debug)]
+    struct MultiTraitNamedGenericStruct<A, B> {
+        #[debug("{}.{}<->{0:o}.{1:#x}<->{0:?}.{1:X?}", a, b)]
+        a: A,
+        b: B,
+    }
+    #[test]
+    fn multi_trait_named_generic_struct() {
+        let s = MultiTraitNamedGenericStruct { a: 8u8, b: 255 };
+        assert_eq!(
+            format!("{s:?}"),
+            "MultiTraitNamedGenericStruct { a: 8.255<->10.0xff<->8.FF, b: 255 }",
+        );
+        assert_eq!(
+                format!("{s:#?}"),
+                "MultiTraitNamedGenericStruct {\n    a: 8.255<->10.0xff<->8.FF,\n    b: 255,\n}",
+            );
+    }
+
+    #[derive(Debug)]
+    struct MultiTraitUnnamedGenericStruct<A, B>(
+        #[debug("{}.{}.{{}}.{0:o}.{1:#x}-{0:>4?}.{1:^4X?}", _0, _1)] A,
+        B,
+    );
+    #[test]
+    fn multi_trait_unnamed_generic_struct() {
+        let s = MultiTraitUnnamedGenericStruct(8u8, 255);
+        assert_eq!(
+            format!("{s:?}"),
+            "MultiTraitUnnamedGenericStruct(8.255.{}.10.0xff-   8. FF , 255)",
+        );
+        assert_eq!(
+                format!("{s:#?}"),
+                "MultiTraitUnnamedGenericStruct(\n    8.255.{}.10.0xff-   8. FF ,\n    255,\n)",
+            );
+    }
+
+    #[derive(Debug)]
+    struct UnusedGenericStruct<T>(#[debug("{}", 3 * 4)] T);
+    #[test]
+    fn unused_generic_struct() {
+        let s = UnusedGenericStruct(NotDebug);
+        assert_eq!(format!("{s:?}"), "UnusedGenericStruct(12)");
+        assert_eq!(format!("{s:#?}"), "UnusedGenericStruct(\n    12,\n)");
+    }
+
+    mod associated_type_field_enumerator {
+        use derive_more::Debug;
+
+        trait Trait {
+            type Type;
+        }
+
+        struct Struct;
+
+        impl Trait for Struct {
+            type Type = i32;
+        }
+
+        #[test]
+        fn auto_generic_named_struct_associated() {
+            #[derive(Debug)]
+            struct AutoGenericNamedStructAssociated<T: Trait> {
+                field: <T as Trait>::Type,
+            }
+
+            let s = AutoGenericNamedStructAssociated::<Struct> { field: 10 };
+            assert_eq!(
+                format!("{s:?}"),
+                "AutoGenericNamedStructAssociated { field: 10 }",
+            );
+            assert_eq!(
+                format!("{s:#?}"),
+                "AutoGenericNamedStructAssociated {\n    field: 10,\n}",
+            );
+        }
+
+        #[test]
+        fn auto_generic_unnamed_struct_associated() {
+            #[derive(Debug)]
+            struct AutoGenericUnnamedStructAssociated<T: Trait>(<T as Trait>::Type);
+
+            let s = AutoGenericUnnamedStructAssociated::<Struct>(10);
+            assert_eq!(format!("{s:?}"), "AutoGenericUnnamedStructAssociated(10)",);
+            assert_eq!(
+                format!("{s:#?}"),
+                "AutoGenericUnnamedStructAssociated(\n    10,\n)",
+            );
+        }
+
+        #[test]
+        fn auto_generic_enum_associated() {
+            #[derive(Debug)]
+            enum AutoGenericEnumAssociated<T: Trait> {
+                Enumerator(<T as Trait>::Type),
+            }
+
+            let e = AutoGenericEnumAssociated::<Struct>::Enumerator(10);
+            assert_eq!(format!("{:?}", e), "Enumerator(10)");
+            assert_eq!(format!("{:#?}", e), "Enumerator(\n    10,\n)");
+        }
+    }
+
+    mod complex_type_field_enumerator {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        struct Struct<T>(T);
+
+        #[test]
+        fn auto_generic_named_struct_complex() {
+            #[derive(Debug)]
+            struct AutoGenericNamedStructComplex<T> {
+                field: Struct<T>,
+            }
+
+            let s = AutoGenericNamedStructComplex { field: Struct(10) };
+            assert_eq!(
+                format!("{s:?}"),
+                "AutoGenericNamedStructComplex { field: Struct(10) }",
+            );
+            assert_eq!(
+                    format!("{s:#?}"),
+                    "AutoGenericNamedStructComplex {\n    field: Struct(\n        10,\n    ),\n}",
+                );
+        }
+
+        #[test]
+        fn auto_generic_unnamed_struct_complex() {
+            #[derive(Debug)]
+            struct AutoGenericUnnamedStructComplex<T>(Struct<T>);
+
+            let s = AutoGenericUnnamedStructComplex(Struct(10));
+            assert_eq!(
+                format!("{s:?}"),
+                "AutoGenericUnnamedStructComplex(Struct(10))",
+            );
+            assert_eq!(
+                format!("{s:#?}"),
+                "AutoGenericUnnamedStructComplex(\n    Struct(\n        10,\n    ),\n)",
+            );
+        }
+
+        #[test]
+        fn auto_generic_enum_complex() {
+            #[derive(Debug)]
+            enum AutoGenericEnumComplex<T> {
+                Enumerator(Struct<T>),
+            }
+
+            let e = AutoGenericEnumComplex::Enumerator(Struct(10));
+            assert_eq!(format!("{:?}", e), "Enumerator(Struct(10))");
+            assert_eq!(
+                format!("{:#?}", e),
+                "Enumerator(\n    Struct(\n        10,\n    ),\n)",
+            )
+        }
+    }
+
+    mod reference {
+        use derive_more::Debug;
+
+        #[test]
+        fn auto_generic_reference() {
+            #[derive(Debug)]
+            struct AutoGenericReference<'a, T>(&'a T);
+
+            let s = AutoGenericReference(&10);
+            assert_eq!(format!("{s:?}"), "AutoGenericReference(10)");
+            assert_eq!(format!("{s:#?}"), "AutoGenericReference(\n    10,\n)");
+        }
+
+        #[test]
+        fn auto_generic_static_reference() {
+            #[derive(Debug)]
+            struct AutoGenericStaticReference<T: 'static>(&'static T);
+
+            let s = AutoGenericStaticReference(&10);
+            assert_eq!(format!("{s:?}"), "AutoGenericStaticReference(10)");
+            assert_eq!(format!("{s:#?}"), "AutoGenericStaticReference(\n    10,\n)",);
+        }
+    }
+
+    mod indirect {
+        use derive_more::Debug;
+
+        #[derive(Debug)]
+        struct Struct<T>(T);
+
+        #[test]
+        fn auto_generic_indirect() {
+            #[derive(Debug)]
+            struct AutoGenericIndirect<T: 'static>(Struct<&'static T>);
+
+            const V: i32 = 10;
+            let s = AutoGenericIndirect(Struct(&V));
+            assert_eq!(format!("{s:?}"), "AutoGenericIndirect(Struct(10))");
+            assert_eq!(
+                format!("{s:#?}"),
+                "AutoGenericIndirect(\n    Struct(\n        10,\n    ),\n)",
+            );
+        }
+    }
+
+    mod bound {
+        use derive_more::Debug;
+
+        #[test]
+        fn simple() {
+            #[derive(Debug)]
+            struct Struct<T1, T2>(#[debug("{}.{}", _0, _1)] T1, #[debug(skip)] T2);
+
+            let s = Struct(10, 20);
+            assert_eq!(format!("{s:?}"), "Struct(10.20, ..)");
+            assert_eq!(format!("{s:#?}"), "Struct(\n    10.20,\n    ..\n)");
+        }
+
+        #[test]
+        fn underscored_simple() {
+            #[derive(Debug)]
+            struct Struct<T1, T2>(#[debug("{_0}.{_1}")] T1, #[debug(skip)] T2);
+
+            let s = Struct(10, 20);
+            assert_eq!(format!("{s:?}"), "Struct(10.20, ..)");
+            assert_eq!(format!("{s:#?}"), "Struct(\n    10.20,\n    ..\n)");
+        }
+
+        #[test]
+        fn redundant() {
+            #[derive(Debug)]
+            #[debug(bound(T1: ::core::fmt::Display, T2: ::core::fmt::Display))]
+            struct Struct<T1, T2>(#[debug("{}.{}", _0, _1)] T1, #[debug(skip)] T2);
+
+            let s = Struct(10, 20);
+            assert_eq!(format!("{s:?}"), "Struct(10.20, ..)");
+            assert_eq!(format!("{s:#?}"), "Struct(\n    10.20,\n    ..\n)");
+        }
+
+        #[test]
+        fn underscored_redundant() {
+            #[derive(Debug)]
+            #[debug(bound(T1: ::core::fmt::Display, T2: ::core::fmt::Display))]
+            struct Struct<T1, T2>(#[debug("{_0}.{_1}")] T1, #[debug(ignore)] T2);
+
+            let s = Struct(10, 20);
+            assert_eq!(format!("{s:?}"), "Struct(10.20, ..)");
+            assert_eq!(format!("{s:#?}"), "Struct(\n    10.20,\n    ..\n)");
+        }
+
+        #[test]
+        fn complex() {
+            trait Trait1 {
+                fn function1(&self) -> &'static str;
+            }
+
+            trait Trait2 {
+                fn function2(&self) -> &'static str;
+            }
+
+            impl Trait1 for i32 {
+                fn function1(&self) -> &'static str {
+                    "WHAT"
+                }
+            }
+
+            impl Trait2 for i32 {
+                fn function2(&self) -> &'static str {
+                    "EVER"
+                }
+            }
+
+            #[derive(Debug)]
+            #[debug(bound(T1: Trait1 + Trait2, T2: Trait1 + Trait2))]
+            struct Struct<T1, T2>(
+                #[debug("{}_{}_{}_{}", _0.function1(), _0, _1.function2(), _1)] T1,
+                T2,
+            );
+
+            let s = Struct(10, 20);
+            assert_eq!(format!("{s:?}"), "Struct(WHAT_10_EVER_20, 20)");
+            assert_eq!(
+                format!("{s:#?}"),
+                "Struct(\n    WHAT_10_EVER_20,\n    20,\n)",
+            );
+        }
+
+        #[test]
+        fn underscored_complex() {
+            trait Trait1 {
+                fn function1(&self) -> &'static str;
+            }
+
+            trait Trait2 {
+                fn function2(&self) -> &'static str;
+            }
+
+            impl Trait1 for i32 {
+                fn function1(&self) -> &'static str {
+                    "WHAT"
+                }
+            }
+
+            impl Trait2 for i32 {
+                fn function2(&self) -> &'static str {
+                    "EVER"
+                }
+            }
+
+            #[derive(Debug)]
+            #[debug(bound(T1: Trait1 + Trait2, T2: Trait1 + Trait2))]
+            struct Struct<T1, T2>(
+                #[debug("{}_{_0}_{}_{_1}", _0.function1(), _1.function2())] T1,
+                T2,
+            );
+
+            let s = Struct(10, 20);
+            assert_eq!(format!("{s:?}"), "Struct(WHAT_10_EVER_20, 20)");
+            assert_eq!(
+                format!("{s:#?}"),
+                "Struct(\n    WHAT_10_EVER_20,\n    20,\n)",
+            );
+        }
+    }
+}
diff --git a/tests/display.rs b/tests/display.rs
index 5cf29da..e12c07f 100644
--- a/tests/display.rs
+++ b/tests/display.rs
@@ -5,7 +5,7 @@ use std::{
     path::PathBuf,
 };
 
-use derive_more::{Binary, DebugCustom, Display, Octal, UpperHex};
+use derive_more::{Binary, Display, Octal, UpperHex};
 
 mod structs {
     use super::*;