Repository: near/borsh-rs (rust)

Issue:
Schema does not clean unused attrs from cloned enum struct variants
```rust
use borsh::{self, BorshSchema};
use serde::Serialize;

#[derive(Serialize, BorshSchema)]
enum A {
    #[serde(rename = "ab")]
    B {
        #[serde(rename = "abc")]
        c: (),
    },
}
```

Roughly expands into:

```rust
enum A {
    #[serde(rename = "ab")]
    B {
        #[serde(rename = "abc")]
        c: (),
    },
}

impl borsh::BorshSchema for A {
    fn declaration() -> borsh::schema::Declaration {
        "A".to_string()
    }

    fn add_definitions_recursively(
        definitions: &mut borsh::maybestd::collections::HashMap<
            borsh::schema::Declaration,
            borsh::schema::Definition,
        >,
    ) {
        #[derive(BorshSchema)]
        struct AB {
            #[serde(rename = "abc")]
            c: (),
        }

        <AB as BorshSchema>::add_definitions_recursively(definitions);

        let variants = <[_]>::into_vec(
            #[rustc_box]
            ::alloc::boxed::Box::new([("B".to_string(), <AB>::declaration())]),
        );

        let definition = borsh::schema::Definition::Enum { variants };

        Self::add_definition(Self::declaration(), definition, definitions);
    }
}
```

As seen in the function `add_definitions_recursively`, the enum variant `A::B` is effectively cloned into a struct `AB`:

```rust
#[derive(BorshSchema)]
struct AB {
    #[serde(rename = "abc")]
    c: (),
}
```

Which, oddly, still retains any non-borsh attributes the origin had.

Causing builds to fail, because rust can't find a source for those attributes:

```console
$ cargo build
error: cannot find attribute `serde` in this scope
 --> src/lib.rs:8:11
  |
8 |         #[serde(rename = "abc")]
  |           ^^^^^
  |
  = note: `serde` is in scope, but it is a crate, not an attribute
```

Pull request:
fix: filter out foreign attributes in `BorshSchema` derive for enum
Fixes #110 

Patch:
diff --git a/borsh-schema-derive-internal/src/enum_schema.rs b/borsh-schema-derive-internal/src/enum_schema.rs
index 3392e302..f61fa9d7 100644
--- a/borsh-schema-derive-internal/src/enum_schema.rs
+++ b/borsh-schema-derive-internal/src/enum_schema.rs
@@ -5,7 +5,26 @@ use syn::{
     ItemEnum, ItemStruct, Meta, Visibility,
 };
 
-use crate::helpers::{declaration, quote_where_clause};
+use crate::helpers::{declaration, filter_skip, quote_where_clause};
+
+fn transform_variant_fields(mut input: Fields) -> Fields {
+    match input {
+        Fields::Named(ref mut named) => {
+            for field in &mut named.named {
+                let field_attrs = filter_skip(field.attrs.drain(..)).collect::<Vec<_>>();
+                field.attrs = field_attrs;
+            }
+        }
+        Fields::Unnamed(ref mut unnamed) => {
+            for field in &mut unnamed.unnamed {
+                let field_attrs = filter_skip(field.attrs.drain(..)).collect::<Vec<_>>();
+                field.attrs = field_attrs;
+            }
+        }
+        _ => {}
+    }
+    input
+}
 
 pub fn process_enum(input: &ItemEnum, cratename: Ident) -> syn::Result<TokenStream2> {
     let name = &input.ident;
@@ -33,7 +52,7 @@ pub fn process_enum(input: &ItemEnum, cratename: Ident) -> syn::Result<TokenStre
             struct_token: Default::default(),
             ident: full_variant_ident.clone(),
             generics: (*generics).clone(),
-            fields: variant.fields.clone(),
+            fields: transform_variant_fields(variant.fields.clone()),
             semi_token: Some(Default::default()),
         };
         let generic_params = generics
@@ -45,6 +64,7 @@ pub fn process_enum(input: &ItemEnum, cratename: Ident) -> syn::Result<TokenStre
                     #ident ,
                 }
             });
+
         if !generic_params.is_empty() {
             let attr = Attribute {
                 pound_token: Default::default(),
@@ -215,4 +235,32 @@ mod tests {
         .unwrap();
         insta::assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
     }
+
+    #[test]
+    fn test_filter_foreign_attrs() {
+        let item_struct: ItemEnum = syn::parse2(quote! {
+            enum A {
+                #[serde(rename = "ab")]
+                B {
+                    #[serde(rename = "abc")]
+                    c: i32,
+                    #[borsh_skip]
+                    d: u32,
+                    l: u64,
+                },
+                Negative {
+                    beta: String,
+                }
+            }
+        })
+        .unwrap();
+
+        let actual = process_enum(
+            &item_struct,
+            Ident::new("borsh", proc_macro2::Span::call_site()),
+        )
+        .unwrap();
+
+        insta::assert_snapshot!(pretty_print_syn_str(&actual).unwrap());
+    }
 }
diff --git a/borsh-schema-derive-internal/src/helpers.rs b/borsh-schema-derive-internal/src/helpers.rs
index dcd85e46..95102100 100644
--- a/borsh-schema-derive-internal/src/helpers.rs
+++ b/borsh-schema-derive-internal/src/helpers.rs
@@ -8,6 +8,10 @@ pub fn contains_skip(attrs: &[Attribute]) -> bool {
         .any(|attr| attr.path().to_token_stream().to_string().as_str() == "borsh_skip")
 }
 
+pub fn filter_skip(attrs: impl Iterator<Item = Attribute>) -> impl Iterator<Item = Attribute> {
+    attrs.filter(|attr| attr.path().to_token_stream().to_string().as_str() == "borsh_skip")
+}
+
 pub fn declaration(
     ident_str: &str,
     generics: &Generics,


Test patch:
diff --git a/borsh-schema-derive-internal/src/snapshots/borsh_schema_derive_internal__enum_schema__tests__filter_foreign_attrs.snap b/borsh-schema-derive-internal/src/snapshots/borsh_schema_derive_internal__enum_schema__tests__filter_foreign_attrs.snap
new file mode 100644
index 00000000..00097475
--- /dev/null
+++ b/borsh-schema-derive-internal/src/snapshots/borsh_schema_derive_internal__enum_schema__tests__filter_foreign_attrs.snap
@@ -0,0 +1,40 @@
+---
+source: borsh-schema-derive-internal/src/enum_schema.rs
+expression: pretty_print_syn_str(&actual).unwrap()
+---
+impl borsh::BorshSchema for A {
+    fn declaration() -> borsh::schema::Declaration {
+        "A".to_string()
+    }
+    fn add_definitions_recursively(
+        definitions: &mut borsh::__private::maybestd::collections::BTreeMap<
+            borsh::schema::Declaration,
+            borsh::schema::Definition,
+        >,
+    ) {
+        #[allow(dead_code)]
+        #[derive(borsh::BorshSchema)]
+        struct AB {
+            c: i32,
+            #[borsh_skip]
+            d: u32,
+            l: u64,
+        }
+        #[allow(dead_code)]
+        #[derive(borsh::BorshSchema)]
+        struct ANegative {
+            beta: String,
+        }
+        <AB as borsh::BorshSchema>::add_definitions_recursively(definitions);
+        <ANegative as borsh::BorshSchema>::add_definitions_recursively(definitions);
+        let variants = borsh::__private::maybestd::vec![
+            ("B".to_string(), < AB > ::declaration()), ("Negative".to_string(), <
+            ANegative > ::declaration())
+        ];
+        let definition = borsh::schema::Definition::Enum {
+            variants,
+        };
+        Self::add_definition(Self::declaration(), definition, definitions);
+    }
+}
+