Repository: TedDriggs/darling (rust)

Issue:
Emit error when attempting to both forward and parse an attribute path
Parsing and forwarding an attribute are mutually exclusive behaviors in `darling`-generated impls. Declaring the same attribute for both will always result in the attribute only being parsed.

Given the following code:

```rust
#[derive(FromDeriveInput)]
#[darling(attributes(example), forward_attrs(example, cfg))]
pub struct Receiver {
    attrs: Vec<syn::Attribute>
}
```

The placement of `example` in `forward_attrs` is erroneous. It would be useful if `darling` generated a compile-time error to flag the mistake.

Pull request description:
Emit error when an attribute path is present in both attributes and forward_attrs
This is an error on the part of the derive-macro user.

Fix #336

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0d6150a..dcaa7a3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 # Changelog
 
+## Unreleased
+
+-  Potentially breaking: Emit error when an attribute path is present in both `attributes` and `forward_attrs`. [#336](https://github.com/TedDriggs/darling/issues/336)
+
 ## v0.20.11 (March 28, 2025)
 
 -   Support `#[darling(with = ...)]` on the `data` field when deriving `FromDeriveInput`. This allows the use of simpler receiver types, such as a `Vec` of enum variants.
diff --git a/core/src/options/outer_from.rs b/core/src/options/outer_from.rs
index 5a02a1a..bcdf65c 100644
--- a/core/src/options/outer_from.rs
+++ b/core/src/options/outer_from.rs
@@ -1,3 +1,4 @@
+use quote::ToTokens;
 use syn::spanned::Spanned;
 use syn::{Field, Ident, Meta};
 
@@ -107,5 +108,17 @@ impl ParseData for OuterFrom {
                 );
             }
         }
+
+        if let Some(ForwardAttrsFilter::Only(fwd)) = &self.forward_attrs {
+            for path in fwd.intersection(&self.attr_names) {
+                errors.push(
+                    Error::custom(format!(
+                        "attribute path `{}` will not be forwarded because it is also listed in `attributes`",
+                        path.to_token_stream()
+                    ))
+                    .with_span(path),
+                );
+            }
+        };
     }
 }
diff --git a/core/src/util/path_list.rs b/core/src/util/path_list.rs
index bea25fd..0dee093 100644
--- a/core/src/util/path_list.rs
+++ b/core/src/util/path_list.rs
@@ -31,6 +31,20 @@ impl PathList {
     pub fn to_strings(&self) -> Vec<String> {
         self.0.iter().map(path_to_string).collect()
     }
+
+    /// Visits the values representing the intersection, i.e., the values that are both in `self` and `other`.
+    ///
+    /// Values will be returned in the order they appear in `self`.
+    ///
+    /// Values that are present multiple times in `self` and present at least once in `other` will be returned multiple times.
+    ///
+    /// # Performance
+    /// This function runs in `O(n * m)` time.
+    /// It is believed that path lists are usually short enough that this is better than allocating a set containing the values
+    /// of `other` or adding a conditional solution.
+    pub fn intersection<'a>(&'a self, other: &'a PathList) -> impl Iterator<Item = &'a Path> {
+        self.0.iter().filter(|path| other.0.contains(path))
+    }
 }
 
 impl Deref for PathList {
@@ -102,4 +116,14 @@ mod tests {
         let err = input.unwrap_err();
         assert!(err.has_span());
     }
+
+    #[test]
+    fn intersection() {
+        let left = fm::<PathList>(quote!(ignore(Debug, Clone, Eq)));
+        let right = fm::<PathList>(quote!(ignore(Clone, Eq, Clone)));
+        assert_eq!(
+            left.intersection(&right).cloned().collect::<Vec<_>>(),
+            vec![parse_quote!(Clone), parse_quote!(Eq)],
+        );
+    }
 }


Test patch:
diff --git a/tests/compile-fail/attr_forwarded_and_parsed.rs b/tests/compile-fail/attr_forwarded_and_parsed.rs
new file mode 100644
index 0000000..a118a36
--- /dev/null
+++ b/tests/compile-fail/attr_forwarded_and_parsed.rs
@@ -0,0 +1,13 @@
+#[derive(darling::FromDeriveInput)]
+#[darling(attributes(example), forward_attrs(example))]
+pub struct Example {
+    ignored: String,
+}
+
+#[derive(darling::FromField)]
+#[darling(attributes(example), forward_attrs(example))]
+pub struct ExampleField {
+    ignored: String,
+}
+
+fn main() {}
diff --git a/tests/compile-fail/attr_forwarded_and_parsed.stderr b/tests/compile-fail/attr_forwarded_and_parsed.stderr
new file mode 100644
index 0000000..c958442
--- /dev/null
+++ b/tests/compile-fail/attr_forwarded_and_parsed.stderr
@@ -0,0 +1,11 @@
+error: attribute path `example` will not be forwarded because it is also listed in `attributes`
+ --> tests/compile-fail/attr_forwarded_and_parsed.rs:2:46
+  |
+2 | #[darling(attributes(example), forward_attrs(example))]
+  |                                              ^^^^^^^
+
+error: attribute path `example` will not be forwarded because it is also listed in `attributes`
+ --> tests/compile-fail/attr_forwarded_and_parsed.rs:8:46
+  |
+8 | #[darling(attributes(example), forward_attrs(example))]
+  |                                              ^^^^^^^