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.rs345
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}
+ "#,
+ );
+ }
}