Repository: libp2p/rust-libp2p (Rust)

Issue:
swarm-derive/: Don't fail when ignored fields are first
The `NetworkBehaviour` derive macro seems to fail when the `#[behaviour(ignore)]` fields are not the very last fields.

E.g. while the first here fails, the second succeeds:


``` rust
/// Behaviour type.
#[derive(libp2p::NetworkBehaviour)]
#[behaviour(out_event = "BehaviourEvent", event_process = true)]
pub struct Behaviour<Types: IpfsTypes> {
    #[behaviour(ignore)]
    repo: Arc<Repo<Types>>,
    // mdns: Toggle<TokioMdns>,
    kademlia: Kademlia<MemoryStore>,
    #[behaviour(ignore)]
    kad_subscriptions: SubscriptionRegistry<KadResult, String>,
    bitswap: Bitswap,
    ping: Ping,
    identify: Identify,
    pubsub: Pubsub,
    pub swarm: SwarmApi,
}
```

``` rust
/// Behaviour type.
#[derive(libp2p::NetworkBehaviour)]
#[behaviour(event_process = true)]
pub struct Behaviour<Types: IpfsTypes> {
    // mdns: Toggle<TokioMdns>,
    kademlia: Kademlia<MemoryStore>,
    bitswap: Bitswap,
    ping: Ping,
    identify: Identify,
    pubsub: Pubsub,
    pub swarm: SwarmApi,
    #[behaviour(ignore)]
    repo: Arc<Repo<Types>>,
    #[behaviour(ignore)]
    kad_subscriptions: SubscriptionRegistry<KadResult, String>,
}
```

See https://github.com/rs-ipfs/rust-ipfs/pull/499#discussion_r826401633 for a failure in the wild.


Discussion:
Specifically, the errors generated from the bad example are the following:
```
error[E0599]: no method named `into_inner` found for struct `KademliaHandler` in the current scope
  --> src/p2p/behaviour.rs:25:10
   |
25 | #[derive(libp2p::NetworkBehaviour)]
   |          ^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `KademliaHandler<QueryId>`
   |
   = note: this error originates in the derive macro `libp2p::NetworkBehaviour` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0599]: no method named `into_inner` found for struct `KademliaHandlerProto` in the current scope
  --> src/p2p/behaviour.rs:25:10
   |
25 | #[derive(libp2p::NetworkBehaviour)]
   |          ^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `KademliaHandlerProto<QueryId>`
   |
   = note: this error originates in the derive macro `libp2p::NetworkBehaviour` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0599`.
```



Patch:
diff --git a/swarm-derive/CHANGELOG.md b/swarm-derive/CHANGELOG.md
index 7ac48039927..91bf8da9625 100644
--- a/swarm-derive/CHANGELOG.md
+++ b/swarm-derive/CHANGELOG.md
@@ -1,3 +1,9 @@
+# 0.27.1
+
+- Allow mixing of ignored fields. See [PR 2570].
+
+[PR 2570]: https://github.com/libp2p/rust-libp2p/pull/2570
+
 # 0.27.0 [2022-02-22]
 
 - Adjust to latest changes in `libp2p-swarm`.
diff --git a/swarm-derive/Cargo.toml b/swarm-derive/Cargo.toml
index 4b04b553cab..1ab584c12e3 100644
--- a/swarm-derive/Cargo.toml
+++ b/swarm-derive/Cargo.toml
@@ -3,7 +3,7 @@ name = "libp2p-swarm-derive"
 edition = "2021"
 rust-version = "1.56.1"
 description = "Procedural macros of libp2p-core"
-version = "0.27.0"
+version = "0.27.1"
 authors = ["Parity Technologies <admin@parity.io>"]
 license = "MIT"
 repository = "https://github.com/libp2p/rust-libp2p"
diff --git a/swarm-derive/src/lib.rs b/swarm-derive/src/lib.rs
index 67499e7a603..2ee311057ec 100644
--- a/swarm-derive/src/lib.rs
+++ b/swarm-derive/src/lib.rs
@@ -92,6 +92,13 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
         event_process
     };
 
+    // The fields of the struct we are interested in (no ignored fields).
+    let data_struct_fields = data_struct
+        .fields
+        .iter()
+        .filter(|f| !is_ignored(f))
+        .collect::<Vec<_>>();
+
     // The final out event.
     // If we find a `#[behaviour(out_event = "Foo")]` attribute on the struct, we set `Foo` as
     // the out event. Otherwise we use `()`.
@@ -117,10 +124,8 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
 
     // Build the `where ...` clause of the trait implementation.
     let where_clause = {
-        let additional = data_struct
-            .fields
+        let additional = data_struct_fields
             .iter()
-            .filter(|x| !is_ignored(x))
             .flat_map(|field| {
                 let ty = &field.ty;
                 vec![
@@ -147,57 +152,42 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
 
     // Build the list of statements to put in the body of `addresses_of_peer()`.
     let addresses_of_peer_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-
-                Some(match field.ident {
-                    Some(ref i) => quote! { out.extend(self.#i.addresses_of_peer(peer_id)); },
-                    None => quote! { out.extend(self.#field_n.addresses_of_peer(peer_id)); },
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote! { out.extend(self.#i.addresses_of_peer(peer_id)); },
+                None => quote! { out.extend(self.#field_n.addresses_of_peer(peer_id)); },
             })
     };
 
     // Build the list of statements to put in the body of `inject_connection_established()`.
     let inject_connection_established_stmts = {
-        data_struct.fields.iter().enumerate().filter_map(move |(field_n, field)| {
-            if is_ignored(field) {
-                return None;
-            }
-            Some(match field.ident {
+        data_struct_fields.iter().enumerate().map(move |(field_n, field)| {
+            match field.ident {
                 Some(ref i) => quote!{ self.#i.inject_connection_established(peer_id, connection_id, endpoint, errors, other_established); },
                 None => quote!{ self.#field_n.inject_connection_established(peer_id, connection_id, endpoint, errors, other_established); },
-            })
+            }
         })
     };
 
     // Build the list of statements to put in the body of `inject_address_change()`.
     let inject_address_change_stmts = {
-        data_struct.fields.iter().enumerate().filter_map(move |(field_n, field)| {
-            if is_ignored(field) {
-                return None;
-            }
-            Some(match field.ident {
+        data_struct_fields.iter().enumerate().map(move |(field_n, field)| {
+            match field.ident {
                 Some(ref i) => quote!{ self.#i.inject_address_change(peer_id, connection_id, old, new); },
                 None => quote!{ self.#field_n.inject_address_change(peer_id, connection_id, old, new); },
-            })
+            }
         })
     };
 
     // Build the list of statements to put in the body of `inject_connection_closed()`.
     let inject_connection_closed_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
             // The outmost handler belongs to the last behaviour.
             .rev()
-            .filter(|f| !is_ignored(f.1))
             .enumerate()
             .map(move |(enum_n, (field_n, field))| {
                 let handler = if field_n == 0 {
@@ -222,13 +212,11 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
 
     // Build the list of statements to put in the body of `inject_dial_failure()`.
     let inject_dial_failure_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
             // The outmost handler belongs to the last behaviour.
             .rev()
-            .filter(|f| !is_ignored(f.1))
             .enumerate()
             .map(move |(enum_n, (field_n, field))| {
                 let handler = if field_n == 0 {
@@ -258,12 +246,10 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
 
     // Build the list of statements to put in the body of `inject_listen_failure()`.
     let inject_listen_failure_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
             .rev()
-            .filter(|f| !is_ignored(f.1))
             .enumerate()
             .map(move |(enum_n, (field_n, field))| {
                 let handler = if field_n == 0 {
@@ -288,125 +274,78 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
 
     // Build the list of statements to put in the body of `inject_new_listener()`.
     let inject_new_listener_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-
-                Some(match field.ident {
-                    Some(ref i) => quote! { self.#i.inject_new_listener(id); },
-                    None => quote! { self.#field_n.inject_new_listener(id); },
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote! { self.#i.inject_new_listener(id); },
+                None => quote! { self.#field_n.inject_new_listener(id); },
             })
     };
 
     // Build the list of statements to put in the body of `inject_new_listen_addr()`.
     let inject_new_listen_addr_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-
-                Some(match field.ident {
-                    Some(ref i) => quote! { self.#i.inject_new_listen_addr(id, addr); },
-                    None => quote! { self.#field_n.inject_new_listen_addr(id, addr); },
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote! { self.#i.inject_new_listen_addr(id, addr); },
+                None => quote! { self.#field_n.inject_new_listen_addr(id, addr); },
             })
     };
 
     // Build the list of statements to put in the body of `inject_expired_listen_addr()`.
     let inject_expired_listen_addr_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-
-                Some(match field.ident {
-                    Some(ref i) => quote! { self.#i.inject_expired_listen_addr(id, addr); },
-                    None => quote! { self.#field_n.inject_expired_listen_addr(id, addr); },
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote! { self.#i.inject_expired_listen_addr(id, addr); },
+                None => quote! { self.#field_n.inject_expired_listen_addr(id, addr); },
             })
     };
 
     // Build the list of statements to put in the body of `inject_new_external_addr()`.
     let inject_new_external_addr_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-
-                Some(match field.ident {
-                    Some(ref i) => quote! { self.#i.inject_new_external_addr(addr); },
-                    None => quote! { self.#field_n.inject_new_external_addr(addr); },
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote! { self.#i.inject_new_external_addr(addr); },
+                None => quote! { self.#field_n.inject_new_external_addr(addr); },
             })
     };
 
     // Build the list of statements to put in the body of `inject_expired_external_addr()`.
     let inject_expired_external_addr_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-
-                Some(match field.ident {
-                    Some(ref i) => quote! { self.#i.inject_expired_external_addr(addr); },
-                    None => quote! { self.#field_n.inject_expired_external_addr(addr); },
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote! { self.#i.inject_expired_external_addr(addr); },
+                None => quote! { self.#field_n.inject_expired_external_addr(addr); },
             })
     };
 
     // Build the list of statements to put in the body of `inject_listener_error()`.
     let inject_listener_error_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-                Some(match field.ident {
-                    Some(ref i) => quote!(self.#i.inject_listener_error(id, err);),
-                    None => quote!(self.#field_n.inject_listener_error(id, err);),
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote!(self.#i.inject_listener_error(id, err);),
+                None => quote!(self.#field_n.inject_listener_error(id, err);),
             })
     };
 
     // Build the list of statements to put in the body of `inject_listener_closed()`.
     let inject_listener_closed_stmts = {
-        data_struct
-            .fields
+        data_struct_fields
             .iter()
             .enumerate()
-            .filter_map(move |(field_n, field)| {
-                if is_ignored(field) {
-                    return None;
-                }
-                Some(match field.ident {
-                    Some(ref i) => quote!(self.#i.inject_listener_closed(id, reason);),
-                    None => quote!(self.#field_n.inject_listener_closed(id, reason);),
-                })
+            .map(move |(field_n, field)| match field.ident {
+                Some(ref i) => quote!(self.#i.inject_listener_closed(id, reason);),
+                None => quote!(self.#field_n.inject_listener_closed(id, reason);),
             })
     };
 
@@ -414,14 +353,14 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
     //
     // The event type is a construction of nested `#either_ident`s of the events of the children.
     // We call `inject_event` on the corresponding child.
-    let inject_node_event_stmts = data_struct.fields.iter().enumerate().filter(|f| !is_ignored(f.1)).enumerate().map(|(enum_n, (field_n, field))| {
+    let inject_node_event_stmts = data_struct_fields.iter().enumerate().enumerate().map(|(enum_n, (field_n, field))| {
         let mut elem = if enum_n != 0 {
             quote!{ #either_ident::Second(ev) }
         } else {
             quote!{ ev }
         };
 
-        for _ in 0 .. data_struct.fields.iter().filter(|f| !is_ignored(f)).count() - 1 - enum_n {
+        for _ in 0 .. data_struct_fields.len() - 1 - enum_n {
             elem = quote!{ #either_ident::First(#elem) };
         }
 
@@ -434,10 +373,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
     // The [`ConnectionHandler`] associated type.
     let protocols_handler_ty = {
         let mut ph_ty = None;
-        for field in data_struct.fields.iter() {
-            if is_ignored(field) {
-                continue;
-            }
+        for field in data_struct_fields.iter() {
             let ty = &field.ty;
             let field_info = quote! { <#ty as #trait_to_impl>::ConnectionHandler };
             match ph_ty {
@@ -454,11 +390,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
     let new_handler = {
         let mut out_handler = None;
 
-        for (field_n, field) in data_struct.fields.iter().enumerate() {
-            if is_ignored(field) {
-                continue;
-            }
-
+        for (field_n, field) in data_struct_fields.iter().enumerate() {
             let field_name = match field.ident {
                 Some(ref i) => quote! { self.#i },
                 None => quote! { self.#field_n },
@@ -505,7 +437,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
     // List of statements to put in `poll()`.
     //
     // We poll each child one by one and wrap around the output.
-    let poll_stmts = data_struct.fields.iter().enumerate().filter(|f| !is_ignored(f.1)).enumerate().map(|(enum_n, (field_n, field))| {
+    let poll_stmts = data_struct_fields.iter().enumerate().enumerate().map(|(enum_n, (field_n, field))| {
         let field_name = match field.ident {
             Some(ref i) => quote!{ self.#i },
             None => quote!{ self.#field_n },
@@ -516,7 +448,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
         } else {
             quote!{ event }
         };
-        for _ in 0 .. data_struct.fields.iter().filter(|f| !is_ignored(f)).count() - 1 - enum_n {
+        for _ in 0 .. data_struct_fields.len() - 1 - enum_n {
             wrapped_event = quote!{ #either_ident::First(#wrapped_event) };
         }
 
@@ -527,10 +459,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
         let provided_handler_and_new_handlers = {
             let mut out_handler = None;
 
-            for (f_n, f) in data_struct.fields.iter().enumerate() {
-                if is_ignored(f) {
-                    continue;
-                }
+            for (f_n, f) in data_struct_fields.iter().enumerate() {
 
                 let f_name = match f.ident {
                     Some(ref i) => quote! { self.#i },


Test patch:
diff --git a/swarm-derive/tests/test.rs b/swarm-derive/tests/test.rs
index db50c463380..4961806d357 100644
--- a/swarm-derive/tests/test.rs
+++ b/swarm-derive/tests/test.rs
@@ -419,3 +419,30 @@ fn no_event_with_either() {
         require_net_behaviour::<Foo>();
     }
 }
+
+#[test]
+fn mixed_field_order() {
+    struct Foo {}
+
+    #[derive(NetworkBehaviour)]
+    #[behaviour(event_process = true)]
+    pub struct Behaviour {
+        #[behaviour(ignore)]
+        _foo: Foo,
+        _ping: libp2p::ping::Ping,
+        #[behaviour(ignore)]
+        _foo2: Foo,
+        _identify: libp2p::identify::Identify,
+        #[behaviour(ignore)]
+        _foo3: Foo,
+    }
+
+    impl<T> libp2p::swarm::NetworkBehaviourEventProcess<T> for Behaviour {
+        fn inject_event(&mut self, _evt: T) {}
+    }
+
+    #[allow(dead_code)]
+    fn behaviour() {
+        require_net_behaviour::<Behaviour>();
+    }
+}