Repository: rust-lang/rust-analyzer (rust)

Issue:
`Generate trait impl for type` assist
We currently have a `Generate impl for 'foo'` assist when the cursor is on an struct, enum or union that generates an inherent `impl Type {}`. A second version that generates `impl $0 for Type {}` seems convenient to have. Relevant parts are here https://github.com/Veykril/rust-analyzer/blob/cd2603299c4ea2a82511e4dc4a876ff2a05fe26e/crates/ide-assists/src/handlers/generate_impl.rs#L24-L47
We can just duplicate this and extract the common parts out, like most other assist modules do.

(Also I feel like we should change the trigger range, and not trigger these two assists in the field list)

<!-- TRIAGEBOT_START -->

<!-- TRIAGEBOT_ASSIGN_START -->

<!-- TRIAGEBOT_ASSIGN_DATA_START$${"user":"MihailMihov"}$$TRIAGEBOT_ASSIGN_DATA_END -->

<!-- TRIAGEBOT_ASSIGN_END -->
<!-- TRIAGEBOT_END -->

Pull request description:
Add assist to generate trait impl's
resolves #13553 

This pull request adds a `generate_trait_impl` assist, which generates trait impl's for a type. It is almost the same as the one to generate impl's and I also reduced the trigger range to only outside the `RecordFieldList`. Also moved all the tests into separate test functions. A few of the old tests seemed redundant, so I didn't port them.

Patch:
diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs
index 9af26c04eb..690c97e26d 100644
--- a/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/crates/ide-assists/src/handlers/generate_impl.rs
@@ -1,14 +1,17 @@
 use syntax::ast::{self, AstNode, HasName};
 
-use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists};
+use crate::{
+    utils::{generate_impl_text, generate_trait_impl_text},
+    AssistContext, AssistId, AssistKind, Assists,
+};
 
 // Assist: generate_impl
 //
 // Adds a new inherent impl for a type.
 //
 // ```
-// struct Ctx<T: Clone> {
-//     data: T,$0
+// struct Ctx$0<T: Clone> {
+//     data: T,
 // }
 // ```
 // ->
@@ -26,6 +29,10 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
     let name = nominal.name()?;
     let target = nominal.syntax().text_range();
 
+    if let Some(_) = ctx.find_node_at_offset::<ast::RecordFieldList>() {
+        return None;
+    }
+
     acc.add(
         AssistId("generate_impl", AssistKind::Generate),
         format!("Generate impl for `{name}`"),
@@ -46,145 +53,393 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
     )
 }
 
+// Assist: generate_trait_impl
+//
+// Adds a new trait impl for a type.
+//
+// ```
+// struct $0Ctx<T: Clone> {
+//     data: T,
+// }
+// ```
+// ->
+// ```
+// struct Ctx<T: Clone> {
+//     data: T,
+// }
+//
+// impl<T: Clone> $0 for Ctx<T> {
+//
+// }
+// ```
+pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
+    let name = nominal.name()?;
+    let target = nominal.syntax().text_range();
+
+    if let Some(_) = ctx.find_node_at_offset::<ast::RecordFieldList>() {
+        return None;
+    }
+
+    acc.add(
+        AssistId("generate_trait_impl", AssistKind::Generate),
+        format!("Generate trait impl for `{name}`"),
+        target,
+        |edit| {
+            let start_offset = nominal.syntax().text_range().end();
+            match ctx.config.snippet_cap {
+                Some(cap) => {
+                    let snippet = generate_trait_impl_text(&nominal, "$0", "");
+                    edit.insert_snippet(cap, start_offset, snippet);
+                }
+                None => {
+                    let text = generate_trait_impl_text(&nominal, "", "");
+                    edit.insert(start_offset, text);
+                }
+            }
+        },
+    )
+}
+
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_assist, check_assist_target};
 
     use super::*;
 
-    // FIXME: break up into separate test fns
     #[test]
     fn test_add_impl() {
         check_assist(
             generate_impl,
-            "struct Foo {$0}\n",
-            "struct Foo {}\n\nimpl Foo {\n    $0\n}\n",
+            r#"
+                struct Foo$0 {}
+            "#,
+            r#"
+                struct Foo {}
+
+                impl Foo {
+                    $0
+                }
+            "#,
         );
+    }
+
+    #[test]
+    fn test_add_impl_with_generics() {
         check_assist(
             generate_impl,
-            "struct Foo<T: Clone> {$0}",
-            "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n    $0\n}",
+            r#"
+                struct Foo$0<T: Clone> {}
+            "#,
+            r#"
+                struct Foo<T: Clone> {}
+
+                impl<T: Clone> Foo<T> {
+                    $0
+                }
+            "#,
         );
+    }
+
+    #[test]
+    fn test_add_impl_with_generics_and_lifetime_parameters() {
         check_assist(
             generate_impl,
-            "struct Foo<'a, T: Foo<'a>> {$0}",
-            "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n    $0\n}",
+            r#"
+                struct Foo<'a, T: Foo<'a>>$0 {}
+            "#,
+            r#"
+                struct Foo<'a, T: Foo<'a>> {}
+
+                impl<'a, T: Foo<'a>> Foo<'a, T> {
+                    $0
+                }
+            "#,
         );
+    }
+
+    #[test]
+    fn test_add_impl_with_attributes() {
         check_assist(
             generate_impl,
             r#"
-            struct MyOwnArray<T, const S: usize> {}$0"#,
+                #[cfg(feature = "foo")]
+                struct Foo<'a, T: Foo$0<'a>> {}
+            "#,
             r#"
-            struct MyOwnArray<T, const S: usize> {}
+                #[cfg(feature = "foo")]
+                struct Foo<'a, T: Foo<'a>> {}
 
-            impl<T, const S: usize> MyOwnArray<T, S> {
-                $0
-            }"#,
+                #[cfg(feature = "foo")]
+                impl<'a, T: Foo<'a>> Foo<'a, T> {
+                    $0
+                }
+            "#,
         );
+    }
+
+    #[test]
+    fn test_add_impl_with_default_generic() {
         check_assist(
             generate_impl,
             r#"
-            #[cfg(feature = "foo")]
-            struct Foo<'a, T: Foo<'a>> {$0}"#,
+                struct Defaulted$0<T = i32> {}
+            "#,
             r#"
-            #[cfg(feature = "foo")]
-            struct Foo<'a, T: Foo<'a>> {}
+                struct Defaulted<T = i32> {}
 
-            #[cfg(feature = "foo")]
-            impl<'a, T: Foo<'a>> Foo<'a, T> {
-                $0
-            }"#,
+                impl<T> Defaulted<T> {
+                    $0
+                }
+            "#,
         );
+    }
 
+    #[test]
+    fn test_add_impl_with_constrained_default_generic() {
         check_assist(
             generate_impl,
             r#"
-            #[cfg(not(feature = "foo"))]
-            struct Foo<'a, T: Foo<'a>> {$0}"#,
+                struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
+            "#,
             r#"
-            #[cfg(not(feature = "foo"))]
-            struct Foo<'a, T: Foo<'a>> {}
+                struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
 
-            #[cfg(not(feature = "foo"))]
-            impl<'a, T: Foo<'a>> Foo<'a, T> {
-                $0
-            }"#,
+                impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> {
+                    $0
+                }
+            "#,
         );
+    }
 
+    #[test]
+    fn test_add_impl_with_const_defaulted_generic() {
         check_assist(
             generate_impl,
             r#"
-            struct Defaulted<T = i32> {}$0"#,
+                struct Defaulted$0<const N: i32 = 0> {}
+            "#,
             r#"
-            struct Defaulted<T = i32> {}
+                struct Defaulted<const N: i32 = 0> {}
 
-            impl<T> Defaulted<T> {
-                $0
-            }"#,
+                impl<const N: i32> Defaulted<N> {
+                    $0
+                }
+            "#,
         );
+    }
 
+    #[test]
+    fn test_add_impl_with_trait_constraint() {
         check_assist(
             generate_impl,
             r#"
-            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}$0"#,
+                pub trait Trait {}
+                struct Struct$0<T>
+                where
+                    T: Trait,
+                {
+                    inner: T,
+                }
+            "#,
             r#"
-            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
+                pub trait Trait {}
+                struct Struct<T>
+                where
+                    T: Trait,
+                {
+                    inner: T,
+                }
 
-            impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> {
-                $0
-            }"#,
+                impl<T> Struct<T>
+                where
+                    T: Trait,
+                {
+                    $0
+                }
+            "#,
         );
+    }
 
-        check_assist(
+    #[test]
+    fn add_impl_target() {
+        check_assist_target(
             generate_impl,
             r#"
-            struct Defaulted<const N: i32 = 0> {}$0"#,
+                struct SomeThingIrrelevant;
+                /// Has a lifetime parameter
+                struct Foo$0<'a, T: Foo<'a>> {}
+                struct EvenMoreIrrelevant;
+            "#,
+            "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}",
+        );
+    }
+
+    #[test]
+    fn test_add_trait_impl() {
+        check_assist(
+            generate_trait_impl,
+            r#"
+                struct Foo$0 {}
+            "#,
             r#"
-            struct Defaulted<const N: i32 = 0> {}
+                struct Foo {}
 
-            impl<const N: i32> Defaulted<N> {
-                $0
-            }"#,
+                impl $0 for Foo {
+
+                }
+            "#,
         );
+    }
 
+    #[test]
+    fn test_add_trait_impl_with_generics() {
         check_assist(
-            generate_impl,
-            r#"pub trait Trait {}
-struct Struct<T>$0
-where
-    T: Trait,
-{
-    inner: T,
-}"#,
-            r#"pub trait Trait {}
-struct Struct<T>
-where
-    T: Trait,
-{
-    inner: T,
-}
+            generate_trait_impl,
+            r#"
+                struct Foo$0<T: Clone> {}
+            "#,
+            r#"
+                struct Foo<T: Clone> {}
 
-impl<T> Struct<T>
-where
-    T: Trait,
-{
-    $0
-}"#,
+                impl<T: Clone> $0 for Foo<T> {
+
+                }
+            "#,
         );
     }
 
     #[test]
-    fn add_impl_target() {
+    fn test_add_trait_impl_with_generics_and_lifetime_parameters() {
+        check_assist(
+            generate_trait_impl,
+            r#"
+                struct Foo<'a, T: Foo<'a>>$0 {}
+            "#,
+            r#"
+                struct Foo<'a, T: Foo<'a>> {}
+
+                impl<'a, T: Foo<'a>> $0 for Foo<'a, T> {
+
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_trait_impl_with_attributes() {
+        check_assist(
+            generate_trait_impl,
+            r#"
+                #[cfg(feature = "foo")]
+                struct Foo<'a, T: Foo$0<'a>> {}
+            "#,
+            r#"
+                #[cfg(feature = "foo")]
+                struct Foo<'a, T: Foo<'a>> {}
+
+                #[cfg(feature = "foo")]
+                impl<'a, T: Foo<'a>> $0 for Foo<'a, T> {
+
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_trait_impl_with_default_generic() {
+        check_assist(
+            generate_trait_impl,
+            r#"
+                struct Defaulted$0<T = i32> {}
+            "#,
+            r#"
+                struct Defaulted<T = i32> {}
+
+                impl<T> $0 for Defaulted<T> {
+
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_trait_impl_with_constrained_default_generic() {
+        check_assist(
+            generate_trait_impl,
+            r#"
+                struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
+            "#,
+            r#"
+                struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
+
+                impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> $0 for Defaulted<'a, 'b, T, S> {
+
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_trait_impl_with_const_defaulted_generic() {
+        check_assist(
+            generate_trait_impl,
+            r#"
+                struct Defaulted$0<const N: i32 = 0> {}
+            "#,
+            r#"
+                struct Defaulted<const N: i32 = 0> {}
+
+                impl<const N: i32> $0 for Defaulted<N> {
+
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_trait_impl_with_trait_constraint() {
+        check_assist(
+            generate_trait_impl,
+            r#"
+                pub trait Trait {}
+                struct Struct$0<T>
+                where
+                    T: Trait,
+                {
+                    inner: T,
+                }
+            "#,
+            r#"
+                pub trait Trait {}
+                struct Struct<T>
+                where
+                    T: Trait,
+                {
+                    inner: T,
+                }
+
+                impl<T> $0 for Struct<T>
+                where
+                    T: Trait,
+                {
+
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn add_trait_impl_target() {
         check_assist_target(
-            generate_impl,
-            "
-struct SomeThingIrrelevant;
-/// Has a lifetime parameter
-struct Foo<'a, T: Foo<'a>> {$0}
-struct EvenMoreIrrelevant;
-",
-            "/// Has a lifetime parameter
-struct Foo<'a, T: Foo<'a>> {}",
+            generate_trait_impl,
+            r#"
+                struct SomeThingIrrelevant;
+                /// Has a lifetime parameter
+                struct Foo$0<'a, T: Foo<'a>> {}
+                struct EvenMoreIrrelevant;
+            "#,
+            "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}",
         );
     }
 }
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 387cc63142..b12f99cc53 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -247,6 +247,7 @@ mod handlers {
             generate_from_impl_for_enum::generate_from_impl_for_enum,
             generate_function::generate_function,
             generate_impl::generate_impl,
+            generate_impl::generate_trait_impl,
             generate_is_empty_from_len::generate_is_empty_from_len,
             generate_new::generate_new,
             inline_call::inline_call,


Test patch:
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index c09317572a..d797f07767 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -1249,8 +1249,8 @@ fn doctest_generate_impl() {
     check_doc_test(
         "generate_impl",
         r#####"
-struct Ctx<T: Clone> {
-    data: T,$0
+struct Ctx$0<T: Clone> {
+    data: T,
 }
 "#####,
         r#####"
@@ -1341,6 +1341,27 @@ impl Person {
     )
 }
 
+#[test]
+fn doctest_generate_trait_impl() {
+    check_doc_test(
+        "generate_trait_impl",
+        r#####"
+struct $0Ctx<T: Clone> {
+    data: T,
+}
+"#####,
+        r#####"
+struct Ctx<T: Clone> {
+    data: T,
+}
+
+impl<T: Clone> $0 for Ctx<T> {
+
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_inline_call() {
     check_doc_test(