about summary refs log tree commit diff
diff options
context:
space:
mode:
authorShoyu Vanilla (Flint) <modulo641@gmail.com>2025-07-25 04:16:22 +0000
committerGitHub <noreply@github.com>2025-07-25 04:16:22 +0000
commite8757ce62962ea4daee4529a249b64077923211d (patch)
treecf6b2da0b0f054d1627d001577466ad796d7f422
parentd976ef8ae494cc8fd6672d43c49377e43c2876b5 (diff)
parent71269047bee4c8678fa9aec34f0b3ad765fc8033 (diff)
Merge pull request #19938 from A4-Tacks/gen-impl-trait
Add ide-assist: generate_impl_trait for generate_impl
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs325
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/lib.rs1
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs23
3 files changed, 346 insertions, 3 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
index 14601ca0207..168eba22b3b 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs
@@ -1,12 +1,17 @@
 use syntax::{
-    ast::{self, AstNode, HasName, edit_in_place::Indent, make},
+    ast::{self, AstNode, HasGenericParams, HasName, edit_in_place::Indent, make},
     syntax_editor::{Position, SyntaxEditor},
 };
 
-use crate::{AssistContext, AssistId, Assists, utils};
+use crate::{
+    AssistContext, AssistId, Assists,
+    utils::{self, DefaultMethods, IgnoreAssocItems},
+};
 
-fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &ast::Adt) {
+fn insert_impl(editor: &mut SyntaxEditor, impl_: &ast::Impl, nominal: &impl Indent) {
     let indent = nominal.indent_level();
+
+    impl_.indent(indent);
     editor.insert_all(
         Position::after(nominal.syntax()),
         vec![
@@ -120,6 +125,115 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
     )
 }
 
+// Assist: generate_impl_trait
+//
+// Adds this trait impl for a type.
+//
+// ```
+// trait $0Foo {
+//     fn foo(&self) -> i32;
+// }
+// ```
+// ->
+// ```
+// trait Foo {
+//     fn foo(&self) -> i32;
+// }
+//
+// impl Foo for ${1:_} {
+//     fn foo(&self) -> i32 {
+//         $0todo!()
+//     }
+// }
+// ```
+pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+    let name = ctx.find_node_at_offset::<ast::Name>()?;
+    let trait_ = ast::Trait::cast(name.syntax().parent()?)?;
+    let target_scope = ctx.sema.scope(trait_.syntax())?;
+    let hir_trait = ctx.sema.to_def(&trait_)?;
+
+    let target = trait_.syntax().text_range();
+    acc.add(
+        AssistId::generate("generate_impl_trait"),
+        format!("Generate `{name}` impl for type"),
+        target,
+        |edit| {
+            let mut editor = edit.make_editor(trait_.syntax());
+
+            let holder_arg = ast::GenericArg::TypeArg(make::type_arg(make::ty_placeholder()));
+            let missing_items = utils::filter_assoc_items(
+                &ctx.sema,
+                &hir_trait.items(ctx.db()),
+                DefaultMethods::No,
+                IgnoreAssocItems::DocHiddenAttrPresent,
+            );
+            let impl_ = make::impl_trait(
+                trait_.unsafe_token().is_some(),
+                None,
+                trait_.generic_param_list().map(|list| {
+                    make::generic_arg_list(list.generic_params().map(|_| holder_arg.clone()))
+                }),
+                None,
+                None,
+                false,
+                make::ty(&name.text()),
+                make::ty_placeholder(),
+                None,
+                None,
+                None,
+            )
+            .clone_for_update();
+
+            if !missing_items.is_empty() {
+                utils::add_trait_assoc_items_to_impl(
+                    &ctx.sema,
+                    ctx.config,
+                    &missing_items,
+                    hir_trait,
+                    &impl_,
+                    &target_scope,
+                );
+            }
+
+            if let Some(cap) = ctx.config.snippet_cap {
+                if let Some(generics) = impl_.trait_().and_then(|it| it.generic_arg_list()) {
+                    for generic in generics.generic_args() {
+                        let placeholder = edit.make_placeholder_snippet(cap);
+                        editor.add_annotation(generic.syntax(), placeholder);
+                    }
+                }
+
+                if let Some(ty) = impl_.self_ty() {
+                    let placeholder = edit.make_placeholder_snippet(cap);
+                    editor.add_annotation(ty.syntax(), placeholder);
+                }
+
+                if let Some(expr) =
+                    impl_.assoc_item_list().and_then(|it| it.assoc_items().find_map(extract_expr))
+                {
+                    let tabstop = edit.make_tabstop_before(cap);
+                    editor.add_annotation(expr.syntax(), tabstop);
+                } else if let Some(l_curly) =
+                    impl_.assoc_item_list().and_then(|it| it.l_curly_token())
+                {
+                    let tabstop = edit.make_tabstop_after(cap);
+                    editor.add_annotation(l_curly, tabstop);
+                }
+            }
+
+            insert_impl(&mut editor, &impl_, &trait_);
+            edit.add_file_edits(ctx.vfs_file_id(), editor);
+        },
+    )
+}
+
+fn extract_expr(item: ast::AssocItem) -> Option<ast::Expr> {
+    let ast::AssocItem::Fn(f) = item else {
+        return None;
+    };
+    f.body()?.tail_expr()
+}
+
 #[cfg(test)]
 mod tests {
     use crate::tests::{check_assist, check_assist_target};
@@ -492,4 +606,209 @@ mod tests {
             "#,
         );
     }
+
+    #[test]
+    fn test_add_impl_trait() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo {
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                trait Foo {
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+
+                impl Foo for ${1:_} {
+                    fn foo(&self) -> i32 {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_use_generic() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo<T> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                trait Foo<T> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+
+                impl Foo<${1:_}> for ${2:_} {
+                    fn foo(&self) -> _ {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo<T, U> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                trait Foo<T, U> {
+                    fn foo(&self) -> T;
+
+                    fn bar(&self) -> T {
+                        self.foo()
+                    }
+                }
+
+                impl Foo<${1:_}, ${2:_}> for ${3:_} {
+                    fn foo(&self) -> _ {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_docs() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                /// foo
+                trait $0Foo {
+                    /// foo method
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+            "#,
+            r#"
+                /// foo
+                trait Foo {
+                    /// foo method
+                    fn foo(&self) -> i32;
+
+                    fn bar(&self) -> i32 {
+                        self.foo()
+                    }
+                }
+
+                impl Foo for ${1:_} {
+                    fn foo(&self) -> i32 {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_assoc_types() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo {
+                    type Output;
+
+                    fn foo(&self) -> Self::Output;
+                }
+            "#,
+            r#"
+                trait Foo {
+                    type Output;
+
+                    fn foo(&self) -> Self::Output;
+                }
+
+                impl Foo for ${1:_} {
+                    type Output;
+
+                    fn foo(&self) -> Self::Output {
+                        $0todo!()
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_indent() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                mod foo {
+                    mod bar {
+                        trait $0Foo {
+                            type Output;
+
+                            fn foo(&self) -> Self::Output;
+                        }
+                    }
+                }
+            "#,
+            r#"
+                mod foo {
+                    mod bar {
+                        trait Foo {
+                            type Output;
+
+                            fn foo(&self) -> Self::Output;
+                        }
+
+                        impl Foo for ${1:_} {
+                            type Output;
+
+                            fn foo(&self) -> Self::Output {
+                                $0todo!()
+                            }
+                        }
+                    }
+                }
+            "#,
+        );
+    }
+
+    #[test]
+    fn test_add_impl_trait_empty() {
+        check_assist(
+            generate_impl_trait,
+            r#"
+                trait $0Foo {}
+            "#,
+            r#"
+                trait Foo {}
+
+                impl Foo for ${1:_} {$0}
+            "#,
+        );
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
index cde0d875e0d..4682c047323 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs
@@ -302,6 +302,7 @@ mod handlers {
             generate_function::generate_function,
             generate_impl::generate_impl,
             generate_impl::generate_trait_impl,
+            generate_impl::generate_impl_trait,
             generate_is_empty_from_len::generate_is_empty_from_len,
             generate_mut_trait_impl::generate_mut_trait_impl,
             generate_new::generate_new,
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
index fc1c6928ff3..91348be97eb 100644
--- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs
@@ -1881,6 +1881,29 @@ impl<T: Clone> Ctx<T> {$0}
 }
 
 #[test]
+fn doctest_generate_impl_trait() {
+    check_doc_test(
+        "generate_impl_trait",
+        r#####"
+trait $0Foo {
+    fn foo(&self) -> i32;
+}
+"#####,
+        r#####"
+trait Foo {
+    fn foo(&self) -> i32;
+}
+
+impl Foo for ${1:_} {
+    fn foo(&self) -> i32 {
+        $0todo!()
+    }
+}
+"#####,
+    )
+}
+
+#[test]
 fn doctest_generate_is_empty_from_len() {
     check_doc_test(
         "generate_is_empty_from_len",