From 0b9114fdfc226316eecec9f680004e0abeb7588d Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 6 Jun 2025 16:09:45 +0800 Subject: [PATCH 1/3] Add generate_impl_trait for generate_impl --- .../ide-assists/src/handlers/generate_impl.rs | 272 +++++++++++++++++- crates/ide-assists/src/lib.rs | 1 + crates/ide-assists/src/tests/generated.rs | 23 ++ 3 files changed, 294 insertions(+), 2 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs index 14601ca02076..ba95fe5d92a3 100644 --- a/crates/ide-assists/src/handlers/generate_impl.rs +++ b/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) { let indent = nominal.indent_level(); + + impl_.indent(indent); editor.insert_all( Position::after(nominal.syntax()), vec![ @@ -120,6 +125,101 @@ 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:_} { +// $0fn foo(&self) -> i32 { +// todo!() +// } +// } +// ``` +pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let name = ctx.find_node_at_offset::()?; + 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 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(); + + let trait_ = edit.make_mut(trait_); + + 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() { + edit.add_placeholder_snippet(cap, generic); + } + } + + if let Some(ty) = impl_.self_ty() { + edit.add_placeholder_snippet(cap, ty); + } + + if let Some(item) = impl_.assoc_item_list().and_then(|it| it.assoc_items().next()) { + edit.add_tabstop_before(cap, item); + } else if let Some(l_curly) = + impl_.assoc_item_list().and_then(|it| it.l_curly_token()) + { + edit.add_tabstop_after_token(cap, l_curly); + } + } + + insert_impl(impl_, &trait_); + }, + ) +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_target}; @@ -492,4 +592,172 @@ 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:_} { + $0fn foo(&self) -> i32 { + todo!() + } + } + "#, + ); + } + + #[test] + fn test_add_impl_trait_use_generic() { + check_assist( + generate_impl_trait, + r#" + trait $0Foo { + fn foo(&self) -> T; + + fn bar(&self) -> T { + self.foo() + } + } + "#, + r#" + trait Foo { + fn foo(&self) -> T; + + fn bar(&self) -> T { + self.foo() + } + } + + impl Foo<${1:_}> for ${2:_} { + $0fn foo(&self) -> _ { + todo!() + } + } + "#, + ); + check_assist( + generate_impl_trait, + r#" + trait $0Foo { + fn foo(&self) -> T; + + fn bar(&self) -> T { + self.foo() + } + } + "#, + r#" + trait Foo { + fn foo(&self) -> T; + + fn bar(&self) -> T { + self.foo() + } + } + + impl Foo<${1:_}, ${2:_}> for ${3:_} { + $0fn foo(&self) -> _ { + todo!() + } + } + "#, + ); + } + + #[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:_} { + $0fn foo(&self) -> i32 { + todo!() + } + } + "#, + ); + } + + #[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:_} { + $0type Output; + + fn foo(&self) -> Self::Output { + todo!() + } + } + "#, + ); + } + + #[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/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index c2604432032d..46a4c85f41ad 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -301,6 +301,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/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index fe74694d8a44..7c88677be1a1 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1880,6 +1880,29 @@ impl Ctx {$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:_} { + $0fn foo(&self) -> i32 { + todo!() + } +} +"#####, + ) +} + #[test] fn doctest_generate_is_empty_from_len() { check_doc_test( From 6b8e9276545f5735a19ff6fca53377e9f36c2d14 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 28 Jun 2025 15:43:04 +0800 Subject: [PATCH 2/3] Change tabstop to method tail_expr --- .../ide-assists/src/handlers/generate_impl.rs | 37 ++++++++++++------- crates/ide-assists/src/tests/generated.rs | 4 +- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs index ba95fe5d92a3..e72062bf9953 100644 --- a/crates/ide-assists/src/handlers/generate_impl.rs +++ b/crates/ide-assists/src/handlers/generate_impl.rs @@ -141,8 +141,8 @@ pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> // } // // impl Foo for ${1:_} { -// $0fn foo(&self) -> i32 { -// todo!() +// fn foo(&self) -> i32 { +// $0todo!() // } // } // ``` @@ -206,8 +206,10 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> edit.add_placeholder_snippet(cap, ty); } - if let Some(item) = impl_.assoc_item_list().and_then(|it| it.assoc_items().next()) { - edit.add_tabstop_before(cap, item); + if let Some(expr) = + impl_.assoc_item_list().and_then(|it| it.assoc_items().find_map(extract_expr)) + { + edit.add_tabstop_before(cap, expr); } else if let Some(l_curly) = impl_.assoc_item_list().and_then(|it| it.l_curly_token()) { @@ -220,6 +222,13 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> ) } +fn extract_expr(item: ast::AssocItem) -> Option { + 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}; @@ -616,8 +625,8 @@ mod tests { } impl Foo for ${1:_} { - $0fn foo(&self) -> i32 { - todo!() + fn foo(&self) -> i32 { + $0todo!() } } "#, @@ -647,8 +656,8 @@ mod tests { } impl Foo<${1:_}> for ${2:_} { - $0fn foo(&self) -> _ { - todo!() + fn foo(&self) -> _ { + $0todo!() } } "#, @@ -674,8 +683,8 @@ mod tests { } impl Foo<${1:_}, ${2:_}> for ${3:_} { - $0fn foo(&self) -> _ { - todo!() + fn foo(&self) -> _ { + $0todo!() } } "#, @@ -709,8 +718,8 @@ mod tests { } impl Foo for ${1:_} { - $0fn foo(&self) -> i32 { - todo!() + fn foo(&self) -> i32 { + $0todo!() } } "#, @@ -736,10 +745,10 @@ mod tests { } impl Foo for ${1:_} { - $0type Output; + type Output; fn foo(&self) -> Self::Output { - todo!() + $0todo!() } } "#, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 7c88677be1a1..b63571ca6a12 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1895,8 +1895,8 @@ trait Foo { } impl Foo for ${1:_} { - $0fn foo(&self) -> i32 { - todo!() + fn foo(&self) -> i32 { + $0todo!() } } "#####, From 1816d2244e6ea9ef2dbde30f764f5fe4241b0b81 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 18 Jul 2025 06:39:51 +0800 Subject: [PATCH 3/3] Use SyntaxEditor --- .../ide-assists/src/handlers/generate_impl.rs | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs index e72062bf9953..168eba22b3ba 100644 --- a/crates/ide-assists/src/handlers/generate_impl.rs +++ b/crates/ide-assists/src/handlers/generate_impl.rs @@ -8,7 +8,7 @@ use crate::{ 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); @@ -158,6 +158,8 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> 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, @@ -182,8 +184,6 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> ) .clone_for_update(); - let trait_ = edit.make_mut(trait_); - if !missing_items.is_empty() { utils::add_trait_assoc_items_to_impl( &ctx.sema, @@ -198,26 +198,31 @@ pub(crate) fn generate_impl_trait(acc: &mut Assists, ctx: &AssistContext<'_>) -> 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() { - edit.add_placeholder_snippet(cap, generic); + let placeholder = edit.make_placeholder_snippet(cap); + editor.add_annotation(generic.syntax(), placeholder); } } if let Some(ty) = impl_.self_ty() { - edit.add_placeholder_snippet(cap, 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)) { - edit.add_tabstop_before(cap, 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()) { - edit.add_tabstop_after_token(cap, l_curly); + let tabstop = edit.make_tabstop_after(cap); + editor.add_annotation(l_curly, tabstop); } } - insert_impl(impl_, &trait_); + insert_impl(&mut editor, &impl_, &trait_); + edit.add_file_edits(ctx.vfs_file_id(), editor); }, ) } @@ -755,6 +760,43 @@ mod tests { ); } + #[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(