Repository: TedDriggs/darling (rust)

Issue:
Add support for `flatten`
Suppose we have the following traits:

```rust
trait Foo { /* */ }
trait Bar: Foo { /* */ }
```

and we want to provide derive macros for both of them where `#[derive(Foo)]` derives only `Foo` trait and `#[derive(Bar)]` derives both `Foo` and `Bar` traits (basically `#[derive(Bar)]` automatically adds `#[derive(Foo)]` without the user needing to write additional derive explicitly.

When implementing `#[derive(Bar)]` we would also want to access the field or enum variant attributes that `#[derive(Foo)]` requires.

With `darling` this isn't very simple to do this for now.
As for now we would need to do it like this:

<details>
<summary>Long example</summary>


```rust
// Declaring derive input types for `Foo` is trivial

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(foo_bar))]
struct FooInput { /* regular usage of darling here */ }

#[derive(Debug, FromVariant)]
#[darling(attributes(foo_bar))]
struct FooVariant { /* regular usage of darling here */ }

#[derive(Debug, FromField)]
#[darling(attributes(foo_bar))]
struct FooField { /* regular usage of darling here */ }


// However derive input types for `Bar` need to be composed with `Foo` inputs
// and this isn't very trivial

#[derive(Debug)]
struct BarInput {
    foo: FooInput,
    bar: BarInputData
}

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(foo_bar))]
struct BarInputData {
   /* regular usage of darling here with attributes only specific to `Bar` derive */
}

impl FromDeriveInput for ItemInput {
    fn from_derive_input(input: &syn::DeriveInput) -> darling::Result<Self> {
        Ok(Self {
            foo: FromDeriveInput::from_derive_input(input)?,
            bar: FromDeriveInput::from_derive_input(input)?,
        })
    }
}

#[derive(Debug)]
struct BarField {
    foo: FooField,
    bar: BarFieldData,
}

#[derive(Debug, FromField)]
#[darling(attributes(foo_bar))]
struct BarFieldData {
   /* regular usage of darling here with attributes only specific to `Bar` derive */
}

impl FromField for BarField {
    fn from_field(field: &Field) -> darling::Result<Self> {
        Ok(Self {
            foo: FromField::from_field(field)?,
            bar: FromField::from_field(field)?,
        })
    }
}

// Same story for `BarVariant`, it's definition is ommitted here...
```

</details>


Instead of this I propose to add `#[darling(flatten)]` attribute that works similar to [`serde`'s analogue](https://serde.rs/attr-flatten.html).

Something like this:

```rust
// Declaring derive input types for `Foo` is trivial

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(foo_bar))]
struct FooInput { /* regular usage of darling here */ }

#[derive(Debug, FromVariant)]
#[darling(attributes(foo_bar))]
struct FooVariant { /* regular usage of darling here */ }

#[derive(Debug, FromField)]
#[darling(attributes(foo_bar))]
struct FooField { /* regular usage of darling here */ }

// Declaring derive input types for `Bar` is also trivial

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(foo_bar))]
struct BarInput {
    /* regular usage of darling here */
    #[darling(flatten)]
    foo: FooInput,
}

#[derive(Debug, FromVariant)]
#[darling(attributes(foo_bar))]
struct BarVariant {
    /* regular usage of darling here */
    #[darling(flatten)]
    foo: FooVariant,
}

#[derive(Debug, FromField)]
#[darling(attributes(foo_bar))]
struct BarField {
    /* regular usage of darling here */
    #[darling(flatten)]
    foo: FooField,
}
```

However, there are still open questions about how `#[darling(attributes, supports)]` should behave when flattening and there will also be several `ident, attrs` and other data extracted from the parsed field, derive input or enum variant which make flattening feature no so straightforward...

Pull request:
Flatten
Add support for `#[darling(flatten)]`; this does the same thing that `#[serde(flatten)]` does, removing a layer of structure in the AST representation to allow for "merging" two structs together.

Fixes #146 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 55841d8..f202a07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## Unreleased
 
+-  Add `#[darling(flatten)]` to allow forwarding unknown fields to another struct [#146](https://github.com/TedDriggs/darling/issues/146)
 -  Don't suggest names of skipped fields in derived impls [#268](https://github.com/TedDriggs/darling/issues/268)
 
 ## v0.20.6 (February 14, 2024)
diff --git a/README.md b/README.md
index a53cba6..b0866d4 100644
--- a/README.md
+++ b/README.md
@@ -123,6 +123,7 @@ Darling's features are built to work well for real-world projects.
 -   **Multiple-occurrence fields**: Use `#[darling(multiple)]` on a `Vec` field to allow that field to appear multiple times in the meta-item. Each occurrence will be pushed into the `Vec`.
 -   **Span access**: Use `darling::util::SpannedValue` in a struct to get access to that meta item's source code span. This can be used to emit warnings that point at a specific field from your proc macro. In addition, you can use `darling::Error::write_errors` to automatically get precise error location details in most cases.
 -   **"Did you mean" suggestions**: Compile errors from derived darling trait impls include suggestions for misspelled fields.
+-   **Struct flattening**: Use `#[darling(flatten)]` to remove one level of structure when presenting your meta item to users. Fields that are not known to the parent struct will be forwarded to the `flatten` field.
 
 ## Shape Validation
 
diff --git a/core/src/codegen/field.rs b/core/src/codegen/field.rs
index c47cf43..c17cc90 100644
--- a/core/src/codegen/field.rs
+++ b/core/src/codegen/field.rs
@@ -26,11 +26,18 @@ pub struct Field<'a> {
     pub post_transform: Option<&'a PostfixTransform>,
     pub skip: bool,
     pub multiple: bool,
+    /// If set, this field will be given all unclaimed meta items and will
+    /// not be exposed as a standard named field.
+    pub flatten: bool,
 }
 
 impl<'a> Field<'a> {
+    /// Get the name of the meta item that should be matched against input and should be used in diagnostics.
+    ///
+    /// This will be `None` if the field is `skip` or `flatten`, as neither kind of field is addressable
+    /// by name from the input meta.
     pub fn as_name(&'a self) -> Option<&'a str> {
-        if self.skip {
+        if self.skip || self.flatten {
             None
         } else {
             Some(&self.name_in_attr)
@@ -41,6 +48,16 @@ impl<'a> Field<'a> {
         Declaration(self)
     }
 
+    pub fn as_flatten_initializer(
+        &'a self,
+        parent_field_names: Vec<&'a str>,
+    ) -> FlattenInitializer<'a> {
+        FlattenInitializer {
+            field: self,
+            parent_field_names,
+        }
+    }
+
     pub fn as_match(&'a self) -> MatchArm<'a> {
         MatchArm(self)
     }
@@ -82,41 +99,84 @@ impl<'a> ToTokens for Declaration<'a> {
     }
 }
 
+pub struct FlattenInitializer<'a> {
+    field: &'a Field<'a>,
+    parent_field_names: Vec<&'a str>,
+}
+
+impl<'a> ToTokens for FlattenInitializer<'a> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        let Self {
+            field,
+            parent_field_names,
+        } = self;
+        let ident = field.ident;
+
+        let add_parent_fields = if parent_field_names.is_empty() {
+            None
+        } else {
+            Some(quote! {
+                .map_err(|e| e.add_sibling_alts_for_unknown_field(&[#(#parent_field_names),*]))
+            })
+        };
+
+        tokens.append_all(quote! {
+            #ident = (true,
+                __errors.handle(
+                    ::darling::FromMeta::from_list(
+                        __flatten
+                            .into_iter()
+                            .cloned()
+                            .map(::darling::ast::NestedMeta::Meta)
+                            .collect::<Vec<_>>().as_slice()
+                        ) #add_parent_fields
+                    )
+                );
+        });
+    }
+}
+
 /// Represents an individual field in the match.
 pub struct MatchArm<'a>(&'a Field<'a>);
 
 impl<'a> ToTokens for MatchArm<'a> {
     fn to_tokens(&self, tokens: &mut TokenStream) {
         let field: &Field = self.0;
-        if !field.skip {
-            let name_str = &field.name_in_attr;
-            let ident = field.ident;
-            let with_path = &field.with_path;
-            let post_transform = field.post_transform.as_ref();
-
-            // Errors include the location of the bad input, so we compute that here.
-            // Fields that take multiple values add the index of the error for convenience,
-            // while single-value fields only expose the name in the input attribute.
-            let location = if field.multiple {
-                // we use the local variable `len` here because location is accessed via
-                // a closure, and the borrow checker gets very unhappy if we try to immutably
-                // borrow `#ident` in that closure when it was declared `mut` outside.
-                quote!(&format!("{}[{}]", #name_str, __len))
-            } else {
-                quote!(#name_str)
-            };
-
-            // Give darling's generated code the span of the `with_path` so that if the target
-            // type doesn't impl FromMeta, darling's immediate user gets a properly-spanned error.
-            //
-            // Within the generated code, add the span immediately on extraction failure, so that it's
-            // as specific as possible.
-            // The behavior of `with_span` makes this safe to do; if the child applied an
-            // even-more-specific span, our attempt here will not overwrite that and will only cost
-            // us one `if` check.
-            let extractor = quote_spanned!(with_path.span()=>#with_path(__inner)#post_transform.map_err(|e| e.with_span(&__inner).at(#location)));
-
-            tokens.append_all(if field.multiple {
+
+        // Skipped and flattened fields cannot be populated by a meta
+        // with their name, so they do not have a match arm.
+        if field.skip || field.flatten {
+            return;
+        }
+
+        let name_str = &field.name_in_attr;
+        let ident = field.ident;
+        let with_path = &field.with_path;
+        let post_transform = field.post_transform.as_ref();
+
+        // Errors include the location of the bad input, so we compute that here.
+        // Fields that take multiple values add the index of the error for convenience,
+        // while single-value fields only expose the name in the input attribute.
+        let location = if field.multiple {
+            // we use the local variable `len` here because location is accessed via
+            // a closure, and the borrow checker gets very unhappy if we try to immutably
+            // borrow `#ident` in that closure when it was declared `mut` outside.
+            quote!(&format!("{}[{}]", #name_str, __len))
+        } else {
+            quote!(#name_str)
+        };
+
+        // Give darling's generated code the span of the `with_path` so that if the target
+        // type doesn't impl FromMeta, darling's immediate user gets a properly-spanned error.
+        //
+        // Within the generated code, add the span immediately on extraction failure, so that it's
+        // as specific as possible.
+        // The behavior of `with_span` makes this safe to do; if the child applied an
+        // even-more-specific span, our attempt here will not overwrite that and will only cost
+        // us one `if` check.
+        let extractor = quote_spanned!(with_path.span()=>#with_path(__inner)#post_transform.map_err(|e| e.with_span(&__inner).at(#location)));
+
+        tokens.append_all(if field.multiple {
                 quote!(
                     #name_str => {
                         // Store the index of the name we're assessing in case we need
@@ -138,7 +198,6 @@ impl<'a> ToTokens for MatchArm<'a> {
                     }
                 )
             });
-        }
     }
 }
 
diff --git a/core/src/codegen/outer_from_impl.rs b/core/src/codegen/outer_from_impl.rs
index c2939a6..79c5c68 100644
--- a/core/src/codegen/outer_from_impl.rs
+++ b/core/src/codegen/outer_from_impl.rs
@@ -27,6 +27,7 @@ pub trait OuterFromImpl<'a> {
         let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
         tokens.append_all(quote!(
+            #[automatically_derived]
             impl #impl_generics #trayt for #ty_ident #ty_generics
                 #where_clause
             {
diff --git a/core/src/codegen/variant_data.rs b/core/src/codegen/variant_data.rs
index 7df3345..04c1416 100644
--- a/core/src/codegen/variant_data.rs
+++ b/core/src/codegen/variant_data.rs
@@ -7,12 +7,14 @@ use crate::codegen::Field;
 pub struct FieldsGen<'a> {
     fields: &'a Fields<Field<'a>>,
     allow_unknown_fields: bool,
+    flatten_field: Option<&'a Field<'a>>,
 }
 
 impl<'a> FieldsGen<'a> {
     pub fn new(fields: &'a Fields<Field<'a>>, allow_unknown_fields: bool) -> Self {
         Self {
             fields,
+            flatten_field: fields.fields.iter().find(|f| f.flatten),
             allow_unknown_fields,
         }
     }
@@ -36,11 +38,29 @@ impl<'a> FieldsGen<'a> {
     pub(in crate::codegen) fn core_loop(&self) -> TokenStream {
         let arms = self.fields.as_ref().map(Field::as_match);
 
+        let (flatten_buffer, flatten_declaration) = if let Some(flatten_field) = self.flatten_field
+        {
+            (
+                quote! { let mut __flatten = vec![]; },
+                Some(flatten_field.as_flatten_initializer(self.field_names().collect())),
+            )
+        } else {
+            (quote!(), None)
+        };
+
+        // If there is a flatten field, buffer the unknown field so it can be passed
+        // to the flatten function with all other unknown fields.
+        let handle_unknown = if self.flatten_field.is_some() {
+            quote! {
+                __flatten.push(__inner);
+            }
+        }
         // If we're allowing unknown fields, then handling one is a no-op.
-        // Otherwise, we're going to push a new spanned error pointing at the field.
-        let handle_unknown = if self.allow_unknown_fields {
+        else if self.allow_unknown_fields {
             quote!()
-        } else {
+        }
+        // Otherwise, we're going to push a new spanned error pointing at the field.
+        else {
             let mut names = self.fields.iter().filter_map(Field::as_name).peekable();
             // We can't call `unknown_field_with_alts` with an empty slice, or else it fails to
             // infer the type of the slice item.
@@ -57,6 +77,8 @@ impl<'a> FieldsGen<'a> {
         let arms = arms.iter();
 
         quote!(
+            #flatten_buffer
+
             for __item in __items {
                 match *__item {
                     ::darling::export::NestedMeta::Meta(ref __inner) => {
@@ -72,6 +94,8 @@ impl<'a> FieldsGen<'a> {
                     }
                 }
             }
+
+            #flatten_declaration
         )
     }
 
@@ -95,4 +119,8 @@ impl<'a> FieldsGen<'a> {
 
         quote!(#(#inits),*)
     }
+
+    fn field_names(&self) -> impl Iterator<Item = &str> {
+        self.fields.iter().filter_map(Field::as_name)
+    }
 }
diff --git a/core/src/error/kind.rs b/core/src/error/kind.rs
index 0351172..1254f67 100644
--- a/core/src/error/kind.rs
+++ b/core/src/error/kind.rs
@@ -9,7 +9,7 @@ type MetaFormat = String;
 #[derive(Debug, Clone)]
 // Don't want to publicly commit to ErrorKind supporting equality yet, but
 // not having it makes testing very difficult.
-#[cfg_attr(test, derive(PartialEq, Eq))]
+#[cfg_attr(test, derive(PartialEq))]
 pub(in crate::error) enum ErrorKind {
     /// An arbitrary error message.
     Custom(String),
@@ -120,14 +120,14 @@ impl From<ErrorUnknownField> for ErrorKind {
 #[derive(Clone, Debug)]
 // Don't want to publicly commit to ErrorKind supporting equality yet, but
 // not having it makes testing very difficult.
-#[cfg_attr(test, derive(PartialEq, Eq))]
+#[cfg_attr(test, derive(PartialEq))]
 pub(in crate::error) struct ErrorUnknownField {
     name: String,
-    did_you_mean: Option<String>,
+    did_you_mean: Option<(f64, String)>,
 }
 
 impl ErrorUnknownField {
-    pub fn new<I: Into<String>>(name: I, did_you_mean: Option<String>) -> Self {
+    pub fn new<I: Into<String>>(name: I, did_you_mean: Option<(f64, String)>) -> Self {
         ErrorUnknownField {
             name: name.into(),
             did_you_mean,
@@ -142,6 +142,24 @@ impl ErrorUnknownField {
         ErrorUnknownField::new(field, did_you_mean(field, alternates))
     }
 
+    /// Add more alternate field names to the error, updating the `did_you_mean` suggestion
+    /// if a closer match to the unknown field's name is found.
+    pub fn add_alts<'a, T, I>(&mut self, alternates: I)
+    where
+        T: AsRef<str> + 'a,
+        I: IntoIterator<Item = &'a T>,
+    {
+        if let Some(bna) = did_you_mean(&self.name, alternates) {
+            if let Some(current) = &self.did_you_mean {
+                if bna.0 > current.0 {
+                    self.did_you_mean = Some(bna);
+                }
+            } else {
+                self.did_you_mean = Some(bna);
+            }
+        }
+    }
+
     #[cfg(feature = "diagnostics")]
     pub fn into_diagnostic(self, span: Option<::proc_macro2::Span>) -> ::proc_macro::Diagnostic {
         let base = span
@@ -149,7 +167,7 @@ impl ErrorUnknownField {
             .unwrap()
             .error(self.top_line());
         match self.did_you_mean {
-            Some(alt_name) => base.help(format!("did you mean `{}`?", alt_name)),
+            Some((_, alt_name)) => base.help(format!("did you mean `{}`?", alt_name)),
             None => base,
         }
     }
@@ -176,7 +194,7 @@ impl fmt::Display for ErrorUnknownField {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "Unknown field: `{}`", self.name)?;
 
-        if let Some(ref did_you_mean) = self.did_you_mean {
+        if let Some((_, ref did_you_mean)) = self.did_you_mean {
             write!(f, ". Did you mean `{}`?", did_you_mean)?;
         }
 
@@ -185,7 +203,7 @@ impl fmt::Display for ErrorUnknownField {
 }
 
 #[cfg(feature = "suggestions")]
-fn did_you_mean<'a, T, I>(field: &str, alternates: I) -> Option<String>
+fn did_you_mean<'a, T, I>(field: &str, alternates: I) -> Option<(f64, String)>
 where
     T: AsRef<str> + 'a,
     I: IntoIterator<Item = &'a T>,
@@ -198,11 +216,11 @@ where
             candidate = Some((confidence, pv.as_ref()));
         }
     }
-    candidate.map(|(_, candidate)| candidate.into())
+    candidate.map(|(score, candidate)| (score, candidate.into()))
 }
 
 #[cfg(not(feature = "suggestions"))]
-fn did_you_mean<'a, T, I>(_field: &str, _alternates: I) -> Option<String>
+fn did_you_mean<'a, T, I>(_field: &str, _alternates: I) -> Option<(f64, String)>
 where
     T: AsRef<str> + 'a,
     I: IntoIterator<Item = &'a T>,
diff --git a/core/src/error/mod.rs b/core/src/error/mod.rs
index 2c14a3e..0ebe3a9 100644
--- a/core/src/error/mod.rs
+++ b/core/src/error/mod.rs
@@ -389,6 +389,51 @@ impl Error {
         self.kind.len()
     }
 
+    /// Consider additional field names as "did you mean" suggestions for
+    /// unknown field errors **if and only if** the caller appears to be operating
+    /// at error's origin (meaning no calls to [`Self::at`] have yet taken place).
+    ///
+    /// # Usage
+    /// `flatten` fields in derived trait implementations rely on this method to offer correct
+    /// "did you mean" suggestions in errors.
+    ///
+    /// Because the `flatten` field receives _all_ unknown fields, if a user mistypes a field name
+    /// that is present on the outer struct but not the flattened struct, they would get an incomplete
+    /// or inferior suggestion unless this method was invoked.
+    pub fn add_sibling_alts_for_unknown_field<'a, T, I>(mut self, alternates: I) -> Self
+    where
+        T: AsRef<str> + 'a,
+        I: IntoIterator<Item = &'a T>,
+    {
+        // The error may have bubbled up before this method was called,
+        // and in those cases adding alternates would be incorrect.
+        if !self.locations.is_empty() {
+            return self;
+        }
+
+        if let ErrorKind::UnknownField(unknown_field) = &mut self.kind {
+            unknown_field.add_alts(alternates);
+        } else if let ErrorKind::Multiple(errors) = self.kind {
+            let alternates = alternates.into_iter().collect::<Vec<_>>();
+            self.kind = ErrorKind::Multiple(
+                errors
+                    .into_iter()
+                    .map(|err| {
+                        err.add_sibling_alts_for_unknown_field(
+                            // This clone seems like it shouldn't be necessary.
+                            // Attempting to borrow alternates here leads to the following compiler error:
+                            //
+                            // error: reached the recursion limit while instantiating `darling::Error::add_sibling_alts_for_unknown_field::<'_, &&&&..., ...>`
+                            alternates.clone(),
+                        )
+                    })
+                    .collect(),
+            )
+        }
+
+        self
+    }
+
     /// Adds a location chain to the head of the error's existing locations.
     fn prepend_at(mut self, mut locations: Vec<String>) -> Self {
         if !locations.is_empty() {
diff --git a/core/src/options/core.rs b/core/src/options/core.rs
index cc05f21..de8bdb0 100644
--- a/core/src/options/core.rs
+++ b/core/src/options/core.rs
@@ -3,6 +3,7 @@ use ident_case::RenameRule;
 use crate::ast::{Data, Fields, Style};
 use crate::codegen;
 use crate::codegen::PostfixTransform;
+use crate::error::Accumulator;
 use crate::options::{DefaultExpression, InputField, InputVariant, ParseAttribute, ParseData};
 use crate::{Error, FromMeta, Result};
 
@@ -152,6 +153,30 @@ impl ParseData for Core {
             Data::Enum(_) => panic!("Core::parse_field should never be called for an enum"),
         }
     }
+
+    fn validate_body(&self, errors: &mut Accumulator) {
+        if let Data::Struct(fields) = &self.data {
+            let flatten_targets: Vec<_> = fields
+                .iter()
+                .filter_map(|field| {
+                    if field.flatten.is_present() {
+                        Some(field.flatten)
+                    } else {
+                        None
+                    }
+                })
+                .collect();
+
+            if flatten_targets.len() > 1 {
+                for flatten in flatten_targets {
+                    errors.push(
+                        Error::custom("`#[darling(flatten)]` can only be applied to one field")
+                            .with_span(&flatten.span()),
+                    );
+                }
+            }
+        }
+    }
 }
 
 impl<'a> From<&'a Core> for codegen::TraitImpl<'a> {
diff --git a/core/src/options/from_attributes.rs b/core/src/options/from_attributes.rs
index 0a133d1..b99278d 100644
--- a/core/src/options/from_attributes.rs
+++ b/core/src/options/from_attributes.rs
@@ -51,6 +51,10 @@ impl ParseData for FromAttributesOptions {
     fn parse_field(&mut self, field: &syn::Field) -> Result<()> {
         self.base.parse_field(field)
     }
+
+    fn validate_body(&self, errors: &mut crate::error::Accumulator) {
+        self.base.validate_body(errors);
+    }
 }
 
 impl<'a> From<&'a FromAttributesOptions> for FromAttributesImpl<'a> {
diff --git a/core/src/options/from_derive.rs b/core/src/options/from_derive.rs
index 71a3c3c..6eec1a0 100644
--- a/core/src/options/from_derive.rs
+++ b/core/src/options/from_derive.rs
@@ -68,6 +68,10 @@ impl ParseData for FdiOptions {
             _ => self.base.parse_field(field),
         }
     }
+
+    fn validate_body(&self, errors: &mut crate::error::Accumulator) {
+        self.base.validate_body(errors);
+    }
 }
 
 impl<'a> From<&'a FdiOptions> for FromDeriveInputImpl<'a> {
diff --git a/core/src/options/from_field.rs b/core/src/options/from_field.rs
index 75e5fed..485a0ad 100644
--- a/core/src/options/from_field.rs
+++ b/core/src/options/from_field.rs
@@ -49,6 +49,10 @@ impl ParseData for FromFieldOptions {
             _ => self.base.parse_field(field),
         }
     }
+
+    fn validate_body(&self, errors: &mut crate::error::Accumulator) {
+        self.base.validate_body(errors);
+    }
 }
 
 impl<'a> From<&'a FromFieldOptions> for FromFieldImpl<'a> {
diff --git a/core/src/options/from_meta.rs b/core/src/options/from_meta.rs
index 62ff5aa..cc527e8 100644
--- a/core/src/options/from_meta.rs
+++ b/core/src/options/from_meta.rs
@@ -37,6 +37,8 @@ impl ParseData for FromMetaOptions {
     }
 
     fn validate_body(&self, errors: &mut Accumulator) {
+        self.base.validate_body(errors);
+
         if let Data::Enum(ref data) = self.base.data {
             // Adds errors for duplicate `#[darling(word)]` annotations across all variants.
             let word_variants: Vec<_> = data
diff --git a/core/src/options/from_type_param.rs b/core/src/options/from_type_param.rs
index 80178ff..d8f3b5b 100644
--- a/core/src/options/from_type_param.rs
+++ b/core/src/options/from_type_param.rs
@@ -49,6 +49,10 @@ impl ParseData for FromTypeParamOptions {
             _ => self.base.parse_field(field),
         }
     }
+
+    fn validate_body(&self, errors: &mut crate::error::Accumulator) {
+        self.base.validate_body(errors);
+    }
 }
 
 impl<'a> From<&'a FromTypeParamOptions> for FromTypeParamImpl<'a> {
diff --git a/core/src/options/from_variant.rs b/core/src/options/from_variant.rs
index ad761ad..c260a3a 100644
--- a/core/src/options/from_variant.rs
+++ b/core/src/options/from_variant.rs
@@ -70,6 +70,10 @@ impl ParseData for FromVariantOptions {
             _ => self.base.parse_field(field),
         }
     }
+
+    fn validate_body(&self, errors: &mut crate::error::Accumulator) {
+        self.base.validate_body(errors);
+    }
 }
 
 impl ToTokens for FromVariantOptions {
diff --git a/core/src/options/input_field.rs b/core/src/options/input_field.rs
index 27fe805..9b688d8 100644
--- a/core/src/options/input_field.rs
+++ b/core/src/options/input_field.rs
@@ -4,7 +4,7 @@ use syn::{parse_quote_spanned, spanned::Spanned};
 
 use crate::codegen;
 use crate::options::{Core, DefaultExpression, ParseAttribute};
-use crate::util::SpannedValue;
+use crate::util::{Flag, SpannedValue};
 use crate::{Error, FromMeta, Result};
 
 #[derive(Debug, Clone)]
@@ -20,6 +20,7 @@ pub struct InputField {
     pub skip: Option<SpannedValue<bool>>,
     pub post_transform: Option<codegen::PostfixTransform>,
     pub multiple: Option<bool>,
+    pub flatten: Flag,
 }
 
 impl InputField {
@@ -44,6 +45,7 @@ impl InputField {
             skip: *self.skip.unwrap_or_default(),
             post_transform: self.post_transform.as_ref(),
             multiple: self.multiple.unwrap_or_default(),
+            flatten: self.flatten.is_present(),
         }
     }
 
@@ -67,6 +69,7 @@ impl InputField {
             skip: None,
             post_transform: Default::default(),
             multiple: None,
+            flatten: Default::default(),
         }
     }
 
@@ -130,6 +133,12 @@ impl ParseAttribute for InputField {
             }
 
             self.attr_name = FromMeta::from_meta(mi)?;
+
+            if self.flatten.is_present() {
+                return Err(
+                    Error::custom("`flatten` and `rename` cannot be used together").with_span(mi),
+                );
+            }
         } else if path.is_ident("default") {
             if self.default.is_some() {
                 return Err(Error::duplicate_field_path(path).with_span(mi));
@@ -141,12 +150,24 @@ impl ParseAttribute for InputField {
             }
 
             self.with = Some(FromMeta::from_meta(mi)?);
+
+            if self.flatten.is_present() {
+                return Err(
+                    Error::custom("`flatten` and `with` cannot be used together").with_span(mi),
+                );
+            }
         } else if path.is_ident("skip") {
             if self.skip.is_some() {
                 return Err(Error::duplicate_field_path(path).with_span(mi));
             }
 
             self.skip = FromMeta::from_meta(mi)?;
+
+            if self.skip.map(|v| *v).unwrap_or_default() && self.flatten.is_present() {
+                return Err(
+                    Error::custom("`flatten` and `skip` cannot be used together").with_span(mi),
+                );
+            }
         } else if path.is_ident("map") || path.is_ident("and_then") {
             let transformer = path.get_ident().unwrap().clone();
             if let Some(post_transform) = &self.post_transform {
@@ -171,6 +192,46 @@ impl ParseAttribute for InputField {
             }
 
             self.multiple = FromMeta::from_meta(mi)?;
+
+            if self.multiple == Some(true) && self.flatten.is_present() {
+                return Err(
+                    Error::custom("`flatten` and `multiple` cannot be used together").with_span(mi),
+                );
+            }
+        } else if path.is_ident("flatten") {
+            if self.flatten.is_present() {
+                return Err(Error::duplicate_field_path(path).with_span(mi));
+            }
+
+            self.flatten = FromMeta::from_meta(mi)?;
+
+            let mut conflicts = Error::accumulator();
+
+            if self.multiple == Some(true) {
+                conflicts.push(
+                    Error::custom("`flatten` and `multiple` cannot be used together").with_span(mi),
+                );
+            }
+
+            if self.attr_name.is_some() {
+                conflicts.push(
+                    Error::custom("`flatten` and `rename` cannot be used together").with_span(mi),
+                );
+            }
+
+            if self.with.is_some() {
+                conflicts.push(
+                    Error::custom("`flatten` and `with` cannot be used together").with_span(mi),
+                );
+            }
+
+            if self.skip.map(|v| *v).unwrap_or_default() {
+                conflicts.push(
+                    Error::custom("`flatten` and `skip` cannot be used together").with_span(mi),
+                );
+            }
+
+            conflicts.finish()?;
         } else {
             return Err(Error::unknown_field_path(path).with_span(mi));
         }
diff --git a/core/src/options/outer_from.rs b/core/src/options/outer_from.rs
index 492b0cd..e3a4b47 100644
--- a/core/src/options/outer_from.rs
+++ b/core/src/options/outer_from.rs
@@ -78,4 +78,8 @@ impl ParseData for OuterFrom {
             _ => self.container.parse_field(field),
         }
     }
+
+    fn validate_body(&self, errors: &mut crate::error::Accumulator) {
+        self.container.validate_body(errors);
+    }
 }


Test patch:
diff --git a/tests/compile-fail/flatten_meta_conflicts.rs b/tests/compile-fail/flatten_meta_conflicts.rs
new file mode 100644
index 0000000..62543e7
--- /dev/null
+++ b/tests/compile-fail/flatten_meta_conflicts.rs
@@ -0,0 +1,21 @@
+use darling::FromMeta;
+
+#[derive(FromMeta)]
+struct Inner {
+    left: String,
+    right: String,
+}
+
+#[derive(FromMeta)]
+struct Outer {
+    #[darling(flatten, multiple, with = demo, skip = true)]
+    field: Inner,
+}
+
+#[derive(FromMeta)]
+struct ThisIsFine {
+    #[darling(flatten, multiple = false)]
+    field: Inner,
+}
+
+fn main() {}
diff --git a/tests/compile-fail/flatten_meta_conflicts.stderr b/tests/compile-fail/flatten_meta_conflicts.stderr
new file mode 100644
index 0000000..6fa4c28
--- /dev/null
+++ b/tests/compile-fail/flatten_meta_conflicts.stderr
@@ -0,0 +1,17 @@
+error: `flatten` and `multiple` cannot be used together
+  --> tests/compile-fail/flatten_meta_conflicts.rs:11:24
+   |
+11 |     #[darling(flatten, multiple, with = demo, skip = true)]
+   |                        ^^^^^^^^
+
+error: `flatten` and `with` cannot be used together
+  --> tests/compile-fail/flatten_meta_conflicts.rs:11:34
+   |
+11 |     #[darling(flatten, multiple, with = demo, skip = true)]
+   |                                  ^^^^
+
+error: `flatten` and `skip` cannot be used together
+  --> tests/compile-fail/flatten_meta_conflicts.rs:11:47
+   |
+11 |     #[darling(flatten, multiple, with = demo, skip = true)]
+   |                                               ^^^^
diff --git a/tests/compile-fail/flatten_multiple_fields.rs b/tests/compile-fail/flatten_multiple_fields.rs
new file mode 100644
index 0000000..a093054
--- /dev/null
+++ b/tests/compile-fail/flatten_multiple_fields.rs
@@ -0,0 +1,28 @@
+//! Test that multiple fields cannot be marked `flatten` at once.
+
+use darling::{FromDeriveInput, FromMeta};
+
+#[derive(FromMeta)]
+struct Inner {
+    left: String,
+    right: String,
+}
+
+#[derive(FromMeta)]
+pub struct Example {
+    #[darling(flatten)]
+    first: Inner,
+    #[darling(flatten)]
+    last: Inner,
+}
+
+#[derive(FromDeriveInput)]
+pub struct FdiExample {
+    ident: syn::Ident,
+    #[darling(flatten)]
+    first: Inner,
+    #[darling(flatten)]
+    last: Inner,
+}
+
+fn main() {}
diff --git a/tests/compile-fail/flatten_multiple_fields.stderr b/tests/compile-fail/flatten_multiple_fields.stderr
new file mode 100644
index 0000000..7750bfb
--- /dev/null
+++ b/tests/compile-fail/flatten_multiple_fields.stderr
@@ -0,0 +1,23 @@
+error: `#[darling(flatten)]` can only be applied to one field
+  --> tests/compile-fail/flatten_multiple_fields.rs:13:15
+   |
+13 |     #[darling(flatten)]
+   |               ^^^^^^^
+
+error: `#[darling(flatten)]` can only be applied to one field
+  --> tests/compile-fail/flatten_multiple_fields.rs:15:15
+   |
+15 |     #[darling(flatten)]
+   |               ^^^^^^^
+
+error: `#[darling(flatten)]` can only be applied to one field
+  --> tests/compile-fail/flatten_multiple_fields.rs:22:15
+   |
+22 |     #[darling(flatten)]
+   |               ^^^^^^^
+
+error: `#[darling(flatten)]` can only be applied to one field
+  --> tests/compile-fail/flatten_multiple_fields.rs:24:15
+   |
+24 |     #[darling(flatten)]
+   |               ^^^^^^^
diff --git a/tests/flatten.rs b/tests/flatten.rs
new file mode 100644
index 0000000..d49373a
--- /dev/null
+++ b/tests/flatten.rs
@@ -0,0 +1,181 @@
+use darling::{util::Flag, FromDeriveInput, FromMeta};
+use proc_macro2::Ident;
+use syn::parse_quote;
+
+#[derive(FromMeta)]
+struct Vis {
+    public: Flag,
+    private: Flag,
+}
+
+#[derive(FromDeriveInput)]
+#[darling(attributes(sample))]
+struct Example {
+    ident: Ident,
+    label: String,
+    #[darling(flatten)]
+    visibility: Vis,
+}
+
+#[test]
+fn happy_path() {
+    let di = Example::from_derive_input(&parse_quote! {
+        #[sample(label = "Hello", public)]
+        struct Demo {}
+    });
+
+    let parsed = di.unwrap();
+    assert_eq!(parsed.ident, "Demo");
+    assert_eq!(&parsed.label, "Hello");
+    assert!(parsed.visibility.public.is_present());
+    assert!(!parsed.visibility.private.is_present());
+}
+
+#[test]
+fn unknown_field_errors() {
+    let errors = Example::from_derive_input(&parse_quote! {
+        #[sample(label = "Hello", republic)]
+        struct Demo {}
+    })
+    .map(|_| "Should have failed")
+    .unwrap_err();
+
+    assert_eq!(errors.len(), 1);
+}
+
+/// This test demonstrates flatten being used recursively.
+/// Fields are expected to be consumed by the outermost matching struct.
+#[test]
+fn recursive_flattening() {
+    #[derive(FromMeta)]
+    struct Nested2 {
+        above: isize,
+        below: isize,
+        port: Option<isize>,
+    }
+
+    #[derive(FromMeta)]
+    struct Nested1 {
+        port: isize,
+        starboard: isize,
+        #[darling(flatten)]
+        z_axis: Nested2,
+    }
+
+    #[derive(FromMeta)]
+    struct Nested0 {
+        fore: isize,
+        aft: isize,
+        #[darling(flatten)]
+        cross_section: Nested1,
+    }
+
+    #[derive(FromDeriveInput)]
+    #[darling(attributes(boat))]
+    struct BoatPosition {
+        #[darling(flatten)]
+        pos: Nested0,
+    }
+
+    let parsed = BoatPosition::from_derive_input(&parse_quote! {
+        #[boat(fore = 1, aft = 1, port = 10, starboard = 50, above = 20, below = -3)]
+        struct Demo;
+    })
+    .unwrap();
+
+    assert_eq!(parsed.pos.fore, 1);
+    assert_eq!(parsed.pos.aft, 1);
+
+    assert_eq!(parsed.pos.cross_section.port, 10);
+    assert_eq!(parsed.pos.cross_section.starboard, 50);
+
+    assert_eq!(parsed.pos.cross_section.z_axis.above, 20);
+    assert_eq!(parsed.pos.cross_section.z_axis.below, -3);
+    // This should be `None` because the `port` field in `Nested1` consumed
+    // the field before the leftovers were passed to `Nested2::from_list`.
+    assert_eq!(parsed.pos.cross_section.z_axis.port, None);
+}
+
+/// This test confirms that a collection - in this case a HashMap - can
+/// be used with `flatten`.
+#[test]
+fn flattening_into_hashmap() {
+    #[derive(FromDeriveInput)]
+    #[darling(attributes(ca))]
+    struct Catchall {
+        hello: String,
+        volume: usize,
+        #[darling(flatten)]
+        others: std::collections::HashMap<String, String>,
+    }
+
+    let parsed = Catchall::from_derive_input(&parse_quote! {
+        #[ca(hello = "World", volume = 10, first_name = "Alice", second_name = "Bob")]
+        struct Demo;
+    })
+    .unwrap();
+
+    assert_eq!(parsed.hello, "World");
+    assert_eq!(parsed.volume, 10);
+    assert_eq!(parsed.others.len(), 2);
+}
+
+#[derive(FromMeta)]
+#[allow(dead_code)]
+struct Person {
+    first: String,
+    last: String,
+    parent: Option<Box<Person>>,
+}
+
+#[derive(FromDeriveInput)]
+#[darling(attributes(v))]
+#[allow(dead_code)]
+struct Outer {
+    #[darling(flatten)]
+    owner: Person,
+    #[darling(default)]
+    blast: bool,
+}
+
+/// This test makes sure that field names from parent structs are not inappropriately
+/// offered as alternates for unknown field errors in child structs.
+///
+/// A naive implementation that tried to offer all the flattened fields for "did you mean"
+/// could inspect all errors returned by the flattened field's `from_list` call and add the
+/// parent's field names as alternates to all unknown field errors.
+///
+/// THIS WOULD BE INCORRECT. Those unknown field errors may have already come from
+/// child fields within the flattened struct, where the parent's field names are not valid.
+#[test]
+fn do_not_suggest_invalid_alts() {
+    let errors = Outer::from_derive_input(&parse_quote! {
+        #[v(first = "Hello", last = "World", parent(first = "Hi", last = "Earth", blasts = "off"))]
+        struct Demo;
+    })
+    .map(|_| "Should have failed")
+    .unwrap_err()
+    .to_string();
+
+    assert!(
+        !errors.contains("`blast`"),
+        "Should not contain `blast`: {}",
+        errors
+    );
+}
+
+#[test]
+fn suggest_valid_parent_alts() {
+    let errors = Outer::from_derive_input(&parse_quote! {
+        #[v(first = "Hello", bladt = false, last = "World", parent(first = "Hi", last = "Earth"))]
+        struct Demo;
+    })
+    .map(|_| "Should have failed")
+    .unwrap_err()
+    .to_string();
+    assert!(
+        errors.contains("`blast`"),
+        "Should contain `blast` as did-you-mean suggestion: {}",
+        errors
+    );
+}
diff --git a/tests/flatten_error_accumulation.rs b/tests/flatten_error_accumulation.rs
new file mode 100644
index 0000000..5cd6280
--- /dev/null
+++ b/tests/flatten_error_accumulation.rs
@@ -0,0 +1,45 @@
+use darling::{util::Flag, Error, FromDeriveInput, FromMeta};
+use proc_macro2::Ident;
+use syn::parse_quote;
+
+#[derive(FromMeta)]
+#[darling(and_then = Self::validate)]
+struct Vis {
+    public: Flag,
+    private: Flag,
+}
+
+impl Vis {
+    fn validate(self) -> darling::Result<Self> {
+        if self.public.is_present() && self.private.is_present() {
+            return Err(Error::custom("Cannot be both public and private"));
+        }
+
+        Ok(self)
+    }
+}
+
+#[derive(FromDeriveInput)]
+#[darling(attributes(sample))]
+#[allow(dead_code)]
+struct Example {
+    ident: Ident,
+    label: String,
+    volume: usize,
+    #[darling(flatten)]
+    visibility: Vis,
+}
+
+#[test]
+fn many_errors() {
+    let e = Example::from_derive_input(&parse_quote! {
+        #[sample(volume = 10, public, private)]
+        struct Demo {}
+    })
+    .map(|_| "Should have failed")
+    .unwrap_err();
+
+    // We are expecting an error from the Vis::validate method and an error for the
+    // missing `label` field.
+    assert_eq!(e.len(), 2);
+}