Repository: JelteF/derive_more
Pull request #399 (2024-08-19 12:44:55)

Consider associated types of type parameters for implied bounds
Related to #387

## Synopsis

After #387, the following snippet fails to compile:
```rust
#[derive(Debug)]
struct AssocType<I: Iterator> {
    iter: I,
    elem: Option<I::Item>,
}
```
This happens, because the implied bound `Option<I::Item>: Debug` is not generated.

## Solution

Correct the `ContainsGenericsExt::contains_generics()` implementations to consider associated types of the type parameter.

## Checklist

- [x] ~~Documentation is updated~~ (not required)
- [x] Tests are added/updated
- [x] [CHANGELOG entry](/CHANGELOG.md) is added



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 460b415..49e08e0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 
 ## 1.0.1 - Unreleased
 
+### Fixed
+
+- Associated types of type parameters not being treated as generics in `Debug`
+  and `Display` expansions.
+  ([#399](https://github.com/JelteF/derive_more/pull/399))
 
 
 ## 1.0.0 - 2024-08-07
diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs
index 6044745..e09d3d6 100644
--- a/impl/src/fmt/mod.rs
+++ b/impl/src/fmt/mod.rs
@@ -632,8 +632,12 @@ impl ContainsGenericsExt for syn::Path {
         }
         self.segments
             .iter()
-            .any(|segment| match &segment.arguments {
-                syn::PathArguments::None => false,
+            .enumerate()
+            .any(|(n, segment)| match &segment.arguments {
+                syn::PathArguments::None => {
+                    // `TypeParam::AssocType` case.
+                    (n == 0) && type_params.contains(&&segment.ident)
+                }
                 syn::PathArguments::AngleBracketed(
                     syn::AngleBracketedGenericArguments { args, .. },
                 ) => args.iter().any(|generic| match generic {


Test patch:
diff --git a/tests/debug.rs b/tests/debug.rs
index 40bdf19..d88903b 100644
--- a/tests/debug.rs
+++ b/tests/debug.rs
@@ -2019,11 +2019,13 @@ mod type_variables {
     mod our_alloc {
         #[cfg(not(feature = "std"))]
         pub use alloc::{boxed::Box, format, vec, vec::Vec};
+        #[cfg(not(feature = "std"))]
+        pub use core::iter;
         #[cfg(feature = "std")]
-        pub use std::{boxed::Box, format, vec, vec::Vec};
+        pub use std::{boxed::Box, format, iter, vec, vec::Vec};
     }
 
-    use our_alloc::{format, vec, Box, Vec};
+    use our_alloc::{format, iter, vec, Box, Vec};
 
     use derive_more::Debug;
 
@@ -2110,6 +2112,25 @@ mod type_variables {
         t: Box<dyn MyTrait<T>>,
     }
 
+    #[derive(Debug)]
+    struct AssocType<I: Iterator> {
+        iter: I,
+        elem: Option<I::Item>,
+    }
+
+    #[derive(derive_more::Debug)]
+    struct CollidedPathName<Item> {
+        item: Item,
+        elem: Option<some_path::Item>,
+    }
+
+    mod some_path {
+        use super::Debug;
+
+        #[derive(Debug)]
+        pub struct Item;
+    }
+
     #[test]
     fn assert() {
         assert_eq!(
@@ -2148,6 +2169,28 @@ mod type_variables {
         assert_eq!(
             format!("{item:?}"),
             "Node { children: [Node { children: [], inner: 0 }, Leaf { inner: 1 }], inner: 2 }",
-        )
+        );
+
+        assert_eq!(
+            format!(
+                "{:?}",
+                AssocType {
+                    iter: iter::empty::<bool>(),
+                    elem: None,
+                },
+            ),
+            "AssocType { iter: Empty, elem: None }",
+        );
+
+        assert_eq!(
+            format!(
+                "{:?}",
+                CollidedPathName {
+                    item: true,
+                    elem: None,
+                },
+            ),
+            "CollidedPathName { item: true, elem: None }",
+        );
     }
 }
diff --git a/tests/display.rs b/tests/display.rs
index 2a9dbb8..c3cf1c1 100644
--- a/tests/display.rs
+++ b/tests/display.rs
@@ -2388,11 +2388,13 @@ mod type_variables {
     mod our_alloc {
         #[cfg(not(feature = "std"))]
         pub use alloc::{boxed::Box, format, vec::Vec};
+        #[cfg(not(feature = "std"))]
+        pub use core::iter;
         #[cfg(feature = "std")]
-        pub use std::{boxed::Box, format, vec::Vec};
+        pub use std::{boxed::Box, format, iter, vec::Vec};
     }
 
-    use our_alloc::{format, Box};
+    use our_alloc::{format, iter, Box};
 
     // We want `Vec` in scope to test that code generation works if it is there.
     #[allow(unused_imports)]
@@ -2512,6 +2514,25 @@ mod type_variables {
         t: Box<dyn MyTrait<T>>,
     }
 
+    #[derive(Display)]
+    #[display("{iter:?} with {elem:?}")]
+    struct AssocType<I: Iterator> {
+        iter: I,
+        elem: Option<I::Item>,
+    }
+
+    #[derive(Display)]
+    #[display("{item:?} with {elem:?}")]
+    struct CollidedPathName<Item> {
+        item: Item,
+        elem: Option<some_path::Item>,
+    }
+
+    mod some_path {
+        #[derive(Debug)]
+        pub struct Item;
+    }
+
     #[test]
     fn assert() {
         assert_eq!(
@@ -2560,6 +2581,28 @@ mod type_variables {
         assert_eq!(
             format!("{item}"),
             "Some(Variant2 { next: OptionalBox { inner: None } })",
-        )
+        );
+
+        assert_eq!(
+            format!(
+                "{}",
+                AssocType {
+                    iter: iter::empty::<bool>(),
+                    elem: None,
+                },
+            ),
+            "Empty with None",
+        );
+
+        assert_eq!(
+            format!(
+                "{}",
+                CollidedPathName {
+                    item: false,
+                    elem: Some(some_path::Item),
+                },
+            ),
+            "false with Some(Item)",
+        );
     }
 }