Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/generate_impl.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_impl.rs | 345 |
1 files changed, 337 insertions, 8 deletions
diff --git a/crates/ide-assists/src/handlers/generate_impl.rs b/crates/ide-assists/src/handlers/generate_impl.rs index 14601ca020..b38ee6f7dc 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) { +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![ @@ -53,11 +58,11 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio let mut editor = edit.make_editor(nominal.syntax()); // Add a tabstop after the left curly brace - if let Some(cap) = ctx.config.snippet_cap { - 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); - } + if let Some(cap) = ctx.config.snippet_cap + && 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_, &nominal); @@ -120,6 +125,125 @@ 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 trait_gen_args = trait_.generic_param_list().map(|list| { + make::generic_arg_list(list.generic_params().map(|_| holder_arg.clone())) + }); + + let make_impl_ = |body| { + make::impl_trait( + trait_.unsafe_token().is_some(), + None, + trait_gen_args.clone(), + None, + None, + false, + make::ty(&name.text()), + make::ty_placeholder(), + None, + None, + body, + ) + .clone_for_update() + }; + + let impl_ = if missing_items.is_empty() { + make_impl_(None) + } else { + let impl_ = make_impl_(None); + let assoc_items = utils::add_trait_assoc_items_to_impl( + &ctx.sema, + ctx.config, + &missing_items, + hir_trait, + &impl_, + &target_scope, + ); + let assoc_item_list = make::assoc_item_list(Some(assoc_items)); + make_impl_(Some(assoc_item_list)) + }; + + 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 +616,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} + "#, + ); + } } |