From 22fe2d3699bc45bc3c5862e9f0ffa3cda86db377 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 9 May 2025 21:17:32 +0800 Subject: [PATCH 1/8] feat: code actions, generate blanket trait impl --- .../handlers/generate_blanket_trait_impl.rs | 1244 +++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 40 + 3 files changed, 1286 insertions(+) create mode 100644 crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs new file mode 100644 index 000000000000..3100809fc62f --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -0,0 +1,1244 @@ +use crate::{ + AssistConfig, + assist_context::{AssistContext, Assists}, + utils::add_cfg_attrs_to, +}; +use ide_db::assists::{AssistId, AssistKind, ExprFillDefaultMode}; +use syntax::{ + AstNode, + ast::{ + self, AssocItem, BlockExpr, GenericParam, HasGenericParams, HasName, HasTypeBounds, + HasVisibility as astHasVisibility, + edit_in_place::{AttrsOwnerEdit, Indent}, + make, + }, +}; + +// Assist: generate_blanket_trait_impl +// +// Generate blanket trait implementation. +// +// ``` +// trait $0Foo: ToOwned +// where +// Self::Owned: Default, +// { +// fn foo(&self) -> T; +// +// fn print_foo(&self) { +// println!("{}", self.foo()); +// } +// } +// ``` +// -> +// ``` +// trait Foo: ToOwned +// where +// Self::Owned: Default, +// { +// fn foo(&self) -> T; +// +// fn print_foo(&self) { +// println!("{}", self.foo()); +// } +// } +// +// $0impl Foo for This +// where +// Self::Owned: Default, +// { +// fn foo(&self) -> T { +// todo!() +// } +// } +// ``` +pub(crate) fn generate_blanket_trait_impl( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let name = ctx.find_node_at_offset::()?; + let traitd = ast::Trait::cast(name.syntax().parent()?)?; + + acc.add( + AssistId("generate_blanket_trait_impl", AssistKind::Generate, None), + "Generate blanket trait implementation", + name.syntax().text_range(), + |builder| { + let namety = make::ty_path(make::path_from_text(&name.text())); + let trait_where_clause = traitd.where_clause().map(|it| it.clone_for_update()); + let bounds = traitd.type_bound_list(); + let is_unsafe = traitd.unsafe_token().is_some(); + let thisname = make::name("This"); + let thisty = make::ty_path(make::path_from_text(&thisname.text())); + let indent = traitd.indent_level(); + + let gendecl = make::generic_param_list([GenericParam::TypeParam(make::type_param( + thisname.clone(), + bounds, + ))]); + + let trait_gen_args = + traitd.generic_param_list().map(|param_list| param_list.to_generic_args()); + + if let Some(ref where_clause) = trait_where_clause { + where_clause.reindent_to(0.into()); + } + + let impl_ = make::impl_trait( + is_unsafe, + traitd.generic_param_list(), + trait_gen_args, + Some(gendecl), + None, + false, + namety, + thisty.clone(), + trait_where_clause, + None, + None, + ) + .clone_for_update(); + + if let Some(trait_assoc_list) = traitd.assoc_item_list() { + let assoc_item_list = impl_.get_or_create_assoc_item_list(); + for method in trait_assoc_list.assoc_items() { + let AssocItem::Fn(method) = method else { + continue; + }; + if method.body().is_some() { + continue; + } + let f = todo_fn(&method, ctx.config).clone_for_update(); + f.remove_attrs_and_docs(); + add_cfg_attrs_to(&method, &f); + f.indent(1.into()); + assoc_item_list.add_item(AssocItem::Fn(f)); + } + } + + add_cfg_attrs_to(&traitd, &impl_); + + impl_.reindent_to(indent); + + // FIXME: $0 after the cfg attributes + builder.insert(traitd.syntax().text_range().end(), format!("\n\n{indent}$0{impl_}")); + }, + ); + + Some(()) +} + +fn todo_fn(f: &ast::Fn, config: &AssistConfig) -> ast::Fn { + let params = f.param_list().unwrap_or_else(|| make::param_list(None, None)); + make::fn_( + f.visibility(), + f.name().unwrap_or_else(|| make::name("unnamed")), + f.generic_param_list(), + f.where_clause(), + params, + default_block(config), + f.ret_type(), + f.async_token().is_some(), + f.const_token().is_some(), + f.unsafe_token().is_some(), + f.gen_token().is_some(), + ) +} + +fn default_block(config: &AssistConfig) -> BlockExpr { + let expr = match config.expr_fill_default { + ExprFillDefaultMode::Todo => make::ext::expr_todo(), + ExprFillDefaultMode::Underscore => make::ext::expr_underscore(), + ExprFillDefaultMode::Default => make::ext::expr_todo(), + }; + make::block_expr(None, Some(expr)) +} + +#[cfg(test)] +mod test { + + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_gen_blanket_works() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: ToOwned +where + Self::Owned: Default, +{ + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +trait Foo: ToOwned +where + Self::Owned: Default, +{ + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This +where + Self::Owned: Default, +{ + fn foo(&self) -> T { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_indent() { + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + trait $0Foo: ToOwned + where + Self::Owned: Default, + { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } +} + "#, + r#" +mod foo { + trait Foo: ToOwned + where + Self::Owned: Default, + { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0impl Foo for This + where + Self::Owned: Default, + { + fn foo(&self) -> T { + todo!() + } + } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + trait $0Foo: ToOwned + where + Self::Owned: Default, + Self: Send, + { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } +} + "#, + r#" +mod foo { + trait Foo: ToOwned + where + Self::Owned: Default, + Self: Send, + { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0impl Foo for This + where + Self::Owned: Default, + Self: Send, + { + fn foo(&self) -> T { + todo!() + } + } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + mod bar { + trait $0Foo: ToOwned + where + Self::Owned: Default, + Self: Send, + { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + } +} + "#, + r#" +mod foo { + mod bar { + trait Foo: ToOwned + where + Self::Owned: Default, + Self: Send, + { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0impl Foo for This + where + Self::Owned: Default, + Self: Send, + { + fn foo(&self) -> T { + todo!() + } + } + } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + trait $0Foo { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } +} + "#, + r#" +mod foo { + trait Foo { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0impl Foo for This { + fn foo(&self) -> T { + todo!() + } + } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + mod bar { + trait $0Foo { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + } +} + "#, + r#" +mod foo { + mod bar { + trait Foo { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0impl Foo for This { + fn foo(&self) -> T { + todo!() + } + } + } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + mod bar { + #[cfg(test)] + trait $0Foo { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + } +} + "#, + r#" +mod foo { + mod bar { + #[cfg(test)] + trait Foo { + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0#[cfg(test)] + impl Foo for This { + fn foo(&self) -> T { + todo!() + } + } + } +} + "#, + ); + } + + #[test] + fn test_gen_blanket_remove_attribute() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: ToOwned +where + Self::Owned: Default, +{ + #[doc(hidden)] + fn foo(&self) -> T; + + /// foo + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +trait Foo: ToOwned +where + Self::Owned: Default, +{ + #[doc(hidden)] + fn foo(&self) -> T; + + /// foo + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This +where + Self::Owned: Default, +{ + fn foo(&self) -> T { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_not_gen_type_alias() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: ToOwned +where + Self::Owned: Default, +{ + type X: Sync; + + fn foo(&self, x: Self::X) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +trait Foo: ToOwned +where + Self::Owned: Default, +{ + type X: Sync; + + fn foo(&self, x: Self::X) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This +where + Self::Owned: Default, +{ + fn foo(&self, x: Self::X) -> T { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_no_quick_bound() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo +where + Self: ToOwned, + Self::Owned: Default, +{ + type X: Sync; + + fn foo(&self, x: Self::X) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +trait Foo +where + Self: ToOwned, + Self::Owned: Default, +{ + type X: Sync; + + fn foo(&self, x: Self::X) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This +where + Self: ToOwned, + Self::Owned: Default, +{ + fn foo(&self, x: Self::X) -> T { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_no_where_clause() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + type X: Sync; + + fn foo(&self, x: Self::X) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +trait Foo { + type X: Sync; + + fn foo(&self, x: Self::X) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This { + fn foo(&self, x: Self::X) -> T { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_basic() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + type X: Sync; + + fn foo(&self, x: Self::X) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +trait Foo { + type X: Sync; + + fn foo(&self, x: Self::X) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This { + fn foo(&self, x: Self::X) -> i32 { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_cfg_attrs() { + check_assist( + generate_blanket_trait_impl, + r#" +#[cfg(test)] +trait $0Foo { + fn foo(&self, x: i32) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +#[cfg(test)] +trait Foo { + fn foo(&self, x: i32) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0#[cfg(test)] +impl Foo for This { + fn foo(&self, x: i32) -> i32 { + todo!() + } +} +"#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +#[cfg(test)] +trait $0Foo { + /// ... + #[cfg(test)] + fn foo(&self, x: i32) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + r#" +#[cfg(test)] +trait Foo { + /// ... + #[cfg(test)] + fn foo(&self, x: i32) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0#[cfg(test)] +impl Foo for This { + #[cfg(test)] + fn foo(&self, x: i32) -> i32 { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo {} +"#, + r#" +trait Foo {} + +$0impl Foo for This {} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_quick_bounds() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: Copy {} +"#, + r#" +trait Foo: Copy {} + +$0impl Foo for This {} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_where_clause() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo where Self: Copy {} +"#, + r#" +trait Foo where Self: Copy {} + +$0impl Foo for This +where Self: Copy +{ +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_where_clause_comma() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo where Self: Copy, {} +"#, + r#" +trait Foo where Self: Copy, {} + +$0impl Foo for This +where Self: Copy, +{ +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_where_clause_newline() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo +where Self: Copy +{} +"#, + r#" +trait Foo +where Self: Copy +{} + +$0impl Foo for This +where Self: Copy +{ +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_where_clause_newline_newline() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo +where + Self: Copy +{} +"#, + r#" +trait Foo +where + Self: Copy +{} + +$0impl Foo for This +where + Self: Copy +{ +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_where_clause_newline_newline_comma() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo +where + Self: Copy, +{} +"#, + r#" +trait Foo +where + Self: Copy, +{} + +$0impl Foo for This +where + Self: Copy, +{ +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_multiple_where_clause() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo +where + Self: Copy, + (): Into, +{} +"#, + r#" +trait Foo +where + Self: Copy, + (): Into, +{} + +$0impl Foo for This +where + Self: Copy, + (): Into, +{ +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_trait_with_multiple_bounds_where_clause() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo +where + Self: Copy + Sync, + (): Into, +{} +"#, + r#" +trait Foo +where + Self: Copy + Sync, + (): Into, +{} + +$0impl Foo for This +where + Self: Copy + Sync, + (): Into, +{ +} +"#, + ); + } + + #[test] + fn test_gen_blanket_empty_generate() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + fn foo(&self) {} +} +"#, + r#" +trait Foo { + fn foo(&self) {} +} + +$0impl Foo for This {} +"#, + ); + } + + #[test] + fn test_gen_blanket_trait_with_doc() { + check_assist( + generate_blanket_trait_impl, + r#" +/// some docs +trait $0Foo {} +"#, + r#" +/// some docs +trait Foo {} + +$0impl Foo for This {} +"#, + ); + } + + #[test] + fn test_gen_blanket_multiple_method() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + fn foo(&self); + fn bar(&self); +} +"#, + r#" +trait Foo { + fn foo(&self); + fn bar(&self); +} + +$0impl Foo for This { + fn foo(&self) { + todo!() + } + + fn bar(&self) { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_method_with_generic() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + fn foo(&self, value: T) -> T; + fn bar(&self, value: T) -> T { todo!() } +} +"#, + r#" +trait Foo { + fn foo(&self, value: T) -> T; + fn bar(&self, value: T) -> T { todo!() } +} + +$0impl Foo for This { + fn foo(&self, value: T) -> T { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_method_with_lifetimes() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo<'a> { + fn foo(&self) -> &'a str; +} +"#, + r#" +trait Foo<'a> { + fn foo(&self) -> &'a str; +} + +$0impl<'a, This> Foo<'a> for This { + fn foo(&self) -> &'a str { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_method_with_lifetime_bounds() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo<'a: 'static> { + fn foo(&self) -> &'a str; +} +"#, + r#" +trait Foo<'a: 'static> { + fn foo(&self) -> &'a str; +} + +$0impl<'a: 'static, This> Foo<'a> for This { + fn foo(&self) -> &'a str { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_method_with_lifetime_quick_bounds() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo<'a>: 'a { + fn foo(&self) -> &'a str; +} +"#, + r#" +trait Foo<'a>: 'a { + fn foo(&self) -> &'a str; +} + +$0impl<'a, This: 'a> Foo<'a> for This { + fn foo(&self) -> &'a str { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_method_with_multiple_lifetimes() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo<'a, 'b> { + fn foo(&self) -> &'a &'b str; +} +"#, + r#" +trait Foo<'a, 'b> { + fn foo(&self) -> &'a &'b str; +} + +$0impl<'a, 'b, This> Foo<'a, 'b> for This { + fn foo(&self) -> &'a &'b str { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_method_with_lifetime_bounds_at_where_clause() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo<'a> +where 'a: 'static, +{ + fn foo(&self) -> &'a str; +} +"#, + r#" +trait Foo<'a> +where 'a: 'static, +{ + fn foo(&self) -> &'a str; +} + +$0impl<'a, This> Foo<'a> for This +where 'a: 'static, +{ + fn foo(&self) -> &'a str { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_not_on_name() { + check_assist_not_applicable( + generate_blanket_trait_impl, + r#" +trait Foo: $0ToOwned +where + Self::Owned: Default, +{ + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + ); + + check_assist_not_applicable( + generate_blanket_trait_impl, + r#" +trait Foo: ToOwned +where + Self::Owned: Default, +{ + $0fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + ); + + check_assist_not_applicable( + generate_blanket_trait_impl, + r#" +trait Foo: ToOwned +where + Self::Owned: Default, +{ + fn $0foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_apply_on_other_impl_block() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +trait Bar {} +impl Bar for i32 {} +"#, + r#" +trait Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This { + fn foo(&self) -> i32 { + todo!() + } +} + +trait Bar {} +impl Bar for i32 {} +"#, + ); + } + + #[test] + fn test_gen_blanket_apply_on_other_blanket_impl_block() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +trait Bar {} +impl Bar for This {} +"#, + r#" +trait Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This { + fn foo(&self) -> i32 { + todo!() + } +} + +trait Bar {} +impl Bar for This {} +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index cde0d875e0d6..c951d7e14944 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -153,6 +153,7 @@ mod handlers { mod flip_comma; mod flip_or_pattern; mod flip_trait_bound; + mod generate_blanket_trait_impl; mod generate_constant; mod generate_default_from_enum_variant; mod generate_default_from_new; @@ -290,6 +291,7 @@ mod handlers { generate_default_from_enum_variant::generate_default_from_enum_variant, generate_default_from_new::generate_default_from_new, generate_delegate_trait::generate_delegate_trait, + generate_blanket_trait_impl::generate_blanket_trait_impl, generate_derive::generate_derive, generate_documentation_template::generate_doc_example, generate_documentation_template::generate_documentation_template, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index fc1c6928ff31..1845cb8f3c93 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1311,6 +1311,46 @@ fn foo() { } ) } +#[test] +fn doctest_generate_blanket_trait_impl() { + check_doc_test( + "generate_blanket_trait_impl", + r#####" +trait $0Foo: ToOwned +where + Self::Owned: Default, +{ + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} +"#####, + r#####" +trait Foo: ToOwned +where + Self::Owned: Default, +{ + fn foo(&self) -> T; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for This +where + Self::Owned: Default, +{ + fn foo(&self) -> T { + todo!() + } +} +"#####, + ) +} + #[test] fn doctest_generate_constant() { check_doc_test( From bf63082debf535bf271fa55bf2514eacacd7b8c4 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Thu, 5 Jun 2025 11:04:35 +0800 Subject: [PATCH 2/8] Use editor and fix tabstop on attrs --- .../handlers/generate_blanket_trait_impl.rs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index 3100809fc62f..52bfaedaa4e5 100644 --- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -8,10 +8,9 @@ use syntax::{ AstNode, ast::{ self, AssocItem, BlockExpr, GenericParam, HasGenericParams, HasName, HasTypeBounds, - HasVisibility as astHasVisibility, - edit_in_place::{AttrsOwnerEdit, Indent}, - make, + HasVisibility as astHasVisibility, edit_in_place::Indent, make, }, + syntax_editor::Position, }; // Assist: generate_blanket_trait_impl @@ -64,6 +63,7 @@ pub(crate) fn generate_blanket_trait_impl( "Generate blanket trait implementation", name.syntax().text_range(), |builder| { + let mut edit = builder.make_editor(traitd.syntax()); let namety = make::ty_path(make::path_from_text(&name.text())); let trait_where_clause = traitd.where_clause().map(|it| it.clone_for_update()); let bounds = traitd.type_bound_list(); @@ -109,7 +109,6 @@ pub(crate) fn generate_blanket_trait_impl( continue; } let f = todo_fn(&method, ctx.config).clone_for_update(); - f.remove_attrs_and_docs(); add_cfg_attrs_to(&method, &f); f.indent(1.into()); assoc_item_list.add_item(AssocItem::Fn(f)); @@ -118,10 +117,21 @@ pub(crate) fn generate_blanket_trait_impl( add_cfg_attrs_to(&traitd, &impl_); - impl_.reindent_to(indent); + impl_.indent(indent); - // FIXME: $0 after the cfg attributes - builder.insert(traitd.syntax().text_range().end(), format!("\n\n{indent}$0{impl_}")); + edit.insert_all( + Position::after(traitd.syntax()), + vec![ + make::tokens::whitespace(&format!("\n\n{indent}")).into(), + impl_.syntax().clone().into(), + ], + ); + + if let Some(cap) = ctx.config.snippet_cap { + builder.add_tabstop_before_token(cap, impl_.impl_token().unwrap()); + } + + builder.add_file_edits(ctx.vfs_file_id(), edit); }, ); @@ -426,8 +436,8 @@ mod foo { } } - $0#[cfg(test)] - impl Foo for This { + #[cfg(test)] + $0impl Foo for This { fn foo(&self) -> T { todo!() } @@ -667,8 +677,8 @@ trait Foo { } } -$0#[cfg(test)] -impl Foo for This { +#[cfg(test)] +$0impl Foo for This { fn foo(&self, x: i32) -> i32 { todo!() } @@ -701,8 +711,8 @@ trait Foo { } } -$0#[cfg(test)] -impl Foo for This { +#[cfg(test)] +$0impl Foo for This { #[cfg(test)] fn foo(&self, x: i32) -> i32 { todo!() From 7aff856fa455ac2de0cdf59c42745231ed001ecf Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 6 Jun 2025 11:25:59 +0800 Subject: [PATCH 3/8] Add smart name and auto unsized --- .../handlers/generate_blanket_trait_impl.rs | 366 +++++++++++++++--- crates/ide-assists/src/tests/generated.rs | 2 +- 2 files changed, 312 insertions(+), 56 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index 52bfaedaa4e5..32ea37c37f39 100644 --- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -8,7 +8,7 @@ use syntax::{ AstNode, ast::{ self, AssocItem, BlockExpr, GenericParam, HasGenericParams, HasName, HasTypeBounds, - HasVisibility as astHasVisibility, edit_in_place::Indent, make, + HasVisibility, edit_in_place::Indent, make, }, syntax_editor::Position, }; @@ -42,7 +42,7 @@ use syntax::{ // } // } // -// $0impl Foo for This +// $0impl Foo for This // where // Self::Owned: Default, // { @@ -66,15 +66,15 @@ pub(crate) fn generate_blanket_trait_impl( let mut edit = builder.make_editor(traitd.syntax()); let namety = make::ty_path(make::path_from_text(&name.text())); let trait_where_clause = traitd.where_clause().map(|it| it.clone_for_update()); - let bounds = traitd.type_bound_list(); + let bounds = traitd.type_bound_list().and_then(exlucde_sized); let is_unsafe = traitd.unsafe_token().is_some(); - let thisname = make::name("This"); + let thisname = this_name(&traitd); let thisty = make::ty_path(make::path_from_text(&thisname.text())); let indent = traitd.indent_level(); let gendecl = make::generic_param_list([GenericParam::TypeParam(make::type_param( thisname.clone(), - bounds, + apply_sized(has_sized(&traitd), bounds), ))]); let trait_gen_args = @@ -138,6 +138,122 @@ pub(crate) fn generate_blanket_trait_impl( Some(()) } +fn has_sized(traitd: &ast::Trait) -> bool { + if let Some(sized) = find_bound("Sized", traitd.type_bound_list()) { + sized.question_mark_token().is_none() + } else if let Some(is_sized) = where_clause_sized(traitd.where_clause()) { + is_sized + } else { + contained_owned_self_method(traitd.assoc_item_list()) + } +} + +fn contained_owned_self_method(item_list: Option) -> bool { + item_list.into_iter().flat_map(|assoc_item_list| assoc_item_list.assoc_items()).any(|item| { + match item { + AssocItem::Fn(f) => { + has_owned_self(&f) && where_clause_sized(f.where_clause()).is_none() + } + _ => false, + } + }) +} + +fn has_owned_self(f: &ast::Fn) -> bool { + has_owned_self_param(f) || has_ret_owned_self(f) +} + +fn has_owned_self_param(f: &ast::Fn) -> bool { + f.param_list() + .and_then(|param_list| param_list.self_param()) + .is_some_and(|sp| sp.amp_token().is_none() && sp.colon_token().is_none()) +} + +fn has_ret_owned_self(f: &ast::Fn) -> bool { + f.ret_type() + .and_then(|ret| match ret.ty() { + Some(ast::Type::PathType(ty)) => ty.path(), + _ => None, + }) + .is_some_and(|path| { + path.segment() + .and_then(|seg| seg.name_ref()) + .is_some_and(|name| path.qualifier().is_none() && name.text() == "Self") + }) +} + +fn where_clause_sized(where_clause: Option) -> Option { + where_clause?.predicates().find_map(|pred| { + find_bound("Sized", pred.type_bound_list()) + .map(|bound| bound.question_mark_token().is_none()) + }) +} + +fn apply_sized(has_sized: bool, bounds: Option) -> Option { + if has_sized { + return bounds; + } + let bounds = bounds + .into_iter() + .flat_map(|bounds| bounds.bounds()) + .chain([make::type_bound_text("?Sized")]); + make::type_bound_list(bounds) +} + +fn exlucde_sized(bounds: ast::TypeBoundList) -> Option { + make::type_bound_list(bounds.bounds().filter(|bound| !ty_bound_is(bound, "Sized"))) +} + +fn this_name(traitd: &ast::Trait) -> ast::Name { + let mut use_t = false; + let mut use_i = false; + let mut use_this = false; + + let has_iter = find_bound("Iterator", traitd.type_bound_list()).is_some(); + + traitd + .generic_param_list() + .into_iter() + .flat_map(|param_list| param_list.generic_params()) + .filter_map(|param| match param { + GenericParam::LifetimeParam(_) => None, + GenericParam::ConstParam(cp) => cp.name(), + GenericParam::TypeParam(tp) => tp.name(), + }) + .for_each(|name| match &*name.text() { + "T" => use_t = true, + "I" => use_i = true, + "This" => use_this = true, + _ => (), + }); + + make::name(if has_iter { + if !use_i { + "I" + } else if !use_t { + "T" + } else { + "This" + } + } else if !use_t { + "T" + } else { + "This" + }) +} + +fn find_bound(s: &str, bounds: Option) -> Option { + bounds.into_iter().flat_map(|bounds| bounds.bounds()).find(|bound| ty_bound_is(bound, s)) +} + +fn ty_bound_is(bound: &ast::TypeBound, s: &str) -> bool { + matches!(bound.ty(), + Some(ast::Type::PathType(ty)) if ty.path() + .and_then(|path| path.segment()) + .and_then(|segment| segment.name_ref()) + .is_some_and(|name| name.text() == s)) +} + fn todo_fn(f: &ast::Fn, config: &AssistConfig) -> ast::Fn { let params = f.param_list().unwrap_or_else(|| make::param_list(None, None)); make::fn_( @@ -198,7 +314,7 @@ where } } -$0impl Foo for This +$0impl Foo for This where Self::Owned: Default, { @@ -210,6 +326,146 @@ where ); } + #[test] + fn test_gen_blanket_sized() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: Iterator + Sized { + fn foo(mut self) -> Self::Item { + self.next().unwrap() + } +} +"#, + r#" +trait Foo: Iterator + Sized { + fn foo(mut self) -> Self::Item { + self.next().unwrap() + } +} + +$0impl Foo for I {} +"#, + ); + + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: Iterator { + fn foo(self) -> Self::Item; +} +"#, + r#" +trait Foo: Iterator { + fn foo(self) -> Self::Item; +} + +$0impl Foo for I { + fn foo(self) -> Self::Item { + todo!() + } +} +"#, + ); + + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo { + fn foo(&self) -> Self; +} +"#, + r#" +trait Foo { + fn foo(&self) -> Self; +} + +$0impl Foo for T { + fn foo(&self) -> Self { + todo!() + } +} +"#, + ); + } + + #[test] + fn test_gen_blanket_non_sized() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: Iterator { + fn foo(&self) -> Self::Item; +} +"#, + r#" +trait Foo: Iterator { + fn foo(&self) -> Self::Item; +} + +$0impl Foo for I { + fn foo(&self) -> Self::Item { + todo!() + } +} +"#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: Iterator { + fn foo(&self) -> Self::Item; + + fn each(self) where Self: Sized; +} +"#, + r#" +trait Foo: Iterator { + fn foo(&self) -> Self::Item; + + fn each(self) where Self: Sized; +} + +$0impl Foo for I { + fn foo(&self) -> Self::Item { + todo!() + } + + fn each(self) where Self: Sized { + todo!() + } +} +"#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: Iterator { + fn foo(&self) -> Self::Item; + + fn each(&self) -> Self where Self: Sized; +} +"#, + r#" +trait Foo: Iterator { + fn foo(&self) -> Self::Item; + + fn each(&self) -> Self where Self: Sized; +} + +$0impl Foo for I { + fn foo(&self) -> Self::Item { + todo!() + } + + fn each(&self) -> Self where Self: Sized { + todo!() + } +} +"#, + ); + } + #[test] fn test_gen_blanket_indent() { check_assist( @@ -241,7 +497,7 @@ mod foo { } } - $0impl Foo for This + $0impl Foo for This where Self::Owned: Default, { @@ -283,7 +539,7 @@ mod foo { } } - $0impl Foo for This + $0impl Foo for This where Self::Owned: Default, Self: Send, @@ -329,7 +585,7 @@ mod foo { } } - $0impl Foo for This + $0impl Foo for This where Self::Owned: Default, Self: Send, @@ -347,7 +603,7 @@ mod foo { r#" mod foo { trait $0Foo { - fn foo(&self) -> T; + fn foo(&self) -> i32; fn print_foo(&self) { println!("{}", self.foo()); @@ -358,15 +614,15 @@ mod foo { r#" mod foo { trait Foo { - fn foo(&self) -> T; + fn foo(&self) -> i32; fn print_foo(&self) { println!("{}", self.foo()); } } - $0impl Foo for This { - fn foo(&self) -> T { + $0impl Foo for T { + fn foo(&self) -> i32 { todo!() } } @@ -379,7 +635,7 @@ mod foo { mod foo { mod bar { trait $0Foo { - fn foo(&self) -> T; + fn foo(&self) -> i32; fn print_foo(&self) { println!("{}", self.foo()); @@ -392,15 +648,15 @@ mod foo { mod foo { mod bar { trait Foo { - fn foo(&self) -> T; + fn foo(&self) -> i32; fn print_foo(&self) { println!("{}", self.foo()); } } - $0impl Foo for This { - fn foo(&self) -> T { + $0impl Foo for T { + fn foo(&self) -> i32 { todo!() } } @@ -415,7 +671,7 @@ mod foo { mod bar { #[cfg(test)] trait $0Foo { - fn foo(&self) -> T; + fn foo(&self) -> i32; fn print_foo(&self) { println!("{}", self.foo()); @@ -429,7 +685,7 @@ mod foo { mod bar { #[cfg(test)] trait Foo { - fn foo(&self) -> T; + fn foo(&self) -> i32; fn print_foo(&self) { println!("{}", self.foo()); @@ -437,8 +693,8 @@ mod foo { } #[cfg(test)] - $0impl Foo for This { - fn foo(&self) -> T { + $0impl Foo for T { + fn foo(&self) -> i32 { todo!() } } @@ -480,7 +736,7 @@ where } } -$0impl Foo for This +$0impl Foo for This where Self::Owned: Default, { @@ -524,7 +780,7 @@ where } } -$0impl Foo for This +$0impl Foo for This where Self::Owned: Default, { @@ -570,7 +826,7 @@ where } } -$0impl Foo for This +$0impl Foo for This where Self: ToOwned, Self::Owned: Default, @@ -609,7 +865,7 @@ trait Foo { } } -$0impl Foo for This { +$0impl Foo for This { fn foo(&self, x: Self::X) -> T { todo!() } @@ -644,7 +900,7 @@ trait Foo { } } -$0impl Foo for This { +$0impl Foo for T { fn foo(&self, x: Self::X) -> i32 { todo!() } @@ -678,7 +934,7 @@ trait Foo { } #[cfg(test)] -$0impl Foo for This { +$0impl Foo for T { fn foo(&self, x: i32) -> i32 { todo!() } @@ -712,7 +968,7 @@ trait Foo { } #[cfg(test)] -$0impl Foo for This { +$0impl Foo for T { #[cfg(test)] fn foo(&self, x: i32) -> i32 { todo!() @@ -732,7 +988,7 @@ trait $0Foo {} r#" trait Foo {} -$0impl Foo for This {} +$0impl Foo for T {} "#, ); } @@ -747,7 +1003,7 @@ trait $0Foo: Copy {} r#" trait Foo: Copy {} -$0impl Foo for This {} +$0impl Foo for T {} "#, ); } @@ -762,7 +1018,7 @@ trait $0Foo where Self: Copy {} r#" trait Foo where Self: Copy {} -$0impl Foo for This +$0impl Foo for T where Self: Copy { } @@ -780,7 +1036,7 @@ trait $0Foo where Self: Copy, {} r#" trait Foo where Self: Copy, {} -$0impl Foo for This +$0impl Foo for T where Self: Copy, { } @@ -802,7 +1058,7 @@ trait Foo where Self: Copy {} -$0impl Foo for This +$0impl Foo for T where Self: Copy { } @@ -826,7 +1082,7 @@ where Self: Copy {} -$0impl Foo for This +$0impl Foo for T where Self: Copy { @@ -851,7 +1107,7 @@ where Self: Copy, {} -$0impl Foo for This +$0impl Foo for T where Self: Copy, { @@ -878,7 +1134,7 @@ where (): Into, {} -$0impl Foo for This +$0impl Foo for T where Self: Copy, (): Into, @@ -906,7 +1162,7 @@ where (): Into, {} -$0impl Foo for This +$0impl Foo for T where Self: Copy + Sync, (): Into, @@ -930,7 +1186,7 @@ trait Foo { fn foo(&self) {} } -$0impl Foo for This {} +$0impl Foo for T {} "#, ); } @@ -947,7 +1203,7 @@ trait $0Foo {} /// some docs trait Foo {} -$0impl Foo for This {} +$0impl Foo for T {} "#, ); } @@ -968,7 +1224,7 @@ trait Foo { fn bar(&self); } -$0impl Foo for This { +$0impl Foo for T { fn foo(&self) { todo!() } @@ -987,18 +1243,18 @@ $0impl Foo for This { generate_blanket_trait_impl, r#" trait $0Foo { - fn foo(&self, value: T) -> T; - fn bar(&self, value: T) -> T { todo!() } + fn foo(&self, value: i32) -> i32; + fn bar(&self, value: i32) -> i32 { todo!() } } "#, r#" trait Foo { - fn foo(&self, value: T) -> T; - fn bar(&self, value: T) -> T { todo!() } + fn foo(&self, value: i32) -> i32; + fn bar(&self, value: i32) -> i32 { todo!() } } -$0impl Foo for This { - fn foo(&self, value: T) -> T { +$0impl Foo for T { + fn foo(&self, value: i32) -> i32 { todo!() } } @@ -1020,7 +1276,7 @@ trait Foo<'a> { fn foo(&self) -> &'a str; } -$0impl<'a, This> Foo<'a> for This { +$0impl<'a, T: ?Sized> Foo<'a> for T { fn foo(&self) -> &'a str { todo!() } @@ -1043,7 +1299,7 @@ trait Foo<'a: 'static> { fn foo(&self) -> &'a str; } -$0impl<'a: 'static, This> Foo<'a> for This { +$0impl<'a: 'static, T: ?Sized> Foo<'a> for T { fn foo(&self) -> &'a str { todo!() } @@ -1066,7 +1322,7 @@ trait Foo<'a>: 'a { fn foo(&self) -> &'a str; } -$0impl<'a, This: 'a> Foo<'a> for This { +$0impl<'a, T: 'a + ?Sized> Foo<'a> for T { fn foo(&self) -> &'a str { todo!() } @@ -1089,7 +1345,7 @@ trait Foo<'a, 'b> { fn foo(&self) -> &'a &'b str; } -$0impl<'a, 'b, This> Foo<'a, 'b> for This { +$0impl<'a, 'b, T: ?Sized> Foo<'a, 'b> for T { fn foo(&self) -> &'a &'b str { todo!() } @@ -1116,7 +1372,7 @@ where 'a: 'static, fn foo(&self) -> &'a str; } -$0impl<'a, This> Foo<'a> for This +$0impl<'a, T: ?Sized> Foo<'a> for T where 'a: 'static, { fn foo(&self) -> &'a str { @@ -1203,7 +1459,7 @@ trait Foo { } } -$0impl Foo for This { +$0impl Foo for T { fn foo(&self) -> i32 { todo!() } @@ -1229,7 +1485,7 @@ trait $0Foo { } trait Bar {} -impl Bar for This {} +impl Bar for T {} "#, r#" trait Foo { @@ -1240,14 +1496,14 @@ trait Foo { } } -$0impl Foo for This { +$0impl Foo for T { fn foo(&self) -> i32 { todo!() } } trait Bar {} -impl Bar for This {} +impl Bar for T {} "#, ); } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 1845cb8f3c93..03676e3628e7 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1339,7 +1339,7 @@ where } } -$0impl Foo for This +$0impl Foo for This where Self::Owned: Default, { From d7a40834becff003657e7039f424c96d162bc7f5 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 28 Jun 2025 14:54:59 +0800 Subject: [PATCH 4/8] Change tabstop to self_ty --- .../handlers/generate_blanket_trait_impl.rs | 86 ++++++++++--------- crates/ide-assists/src/tests/generated.rs | 2 +- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index 32ea37c37f39..7904170d37c0 100644 --- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -42,7 +42,7 @@ use syntax::{ // } // } // -// $0impl Foo for This +// impl Foo for $0This // where // Self::Owned: Default, // { @@ -128,7 +128,9 @@ pub(crate) fn generate_blanket_trait_impl( ); if let Some(cap) = ctx.config.snippet_cap { - builder.add_tabstop_before_token(cap, impl_.impl_token().unwrap()); + if let Some(self_ty) = impl_.self_ty() { + builder.add_tabstop_before(cap, self_ty); + } } builder.add_file_edits(ctx.vfs_file_id(), edit); @@ -314,7 +316,7 @@ where } } -$0impl Foo for This +impl Foo for $0This where Self::Owned: Default, { @@ -344,7 +346,7 @@ trait Foo: Iterator + Sized { } } -$0impl Foo for I {} +impl Foo for $0I {} "#, ); @@ -360,7 +362,7 @@ trait Foo: Iterator { fn foo(self) -> Self::Item; } -$0impl Foo for I { +impl Foo for $0I { fn foo(self) -> Self::Item { todo!() } @@ -380,7 +382,7 @@ trait Foo { fn foo(&self) -> Self; } -$0impl Foo for T { +impl Foo for $0T { fn foo(&self) -> Self { todo!() } @@ -403,7 +405,7 @@ trait Foo: Iterator { fn foo(&self) -> Self::Item; } -$0impl Foo for I { +impl Foo for $0I { fn foo(&self) -> Self::Item { todo!() } @@ -426,7 +428,7 @@ trait Foo: Iterator { fn each(self) where Self: Sized; } -$0impl Foo for I { +impl Foo for $0I { fn foo(&self) -> Self::Item { todo!() } @@ -453,7 +455,7 @@ trait Foo: Iterator { fn each(&self) -> Self where Self: Sized; } -$0impl Foo for I { +impl Foo for $0I { fn foo(&self) -> Self::Item { todo!() } @@ -497,7 +499,7 @@ mod foo { } } - $0impl Foo for This + impl Foo for $0This where Self::Owned: Default, { @@ -539,7 +541,7 @@ mod foo { } } - $0impl Foo for This + impl Foo for $0This where Self::Owned: Default, Self: Send, @@ -585,7 +587,7 @@ mod foo { } } - $0impl Foo for This + impl Foo for $0This where Self::Owned: Default, Self: Send, @@ -621,7 +623,7 @@ mod foo { } } - $0impl Foo for T { + impl Foo for $0T { fn foo(&self) -> i32 { todo!() } @@ -655,7 +657,7 @@ mod foo { } } - $0impl Foo for T { + impl Foo for $0T { fn foo(&self) -> i32 { todo!() } @@ -693,7 +695,7 @@ mod foo { } #[cfg(test)] - $0impl Foo for T { + impl Foo for $0T { fn foo(&self) -> i32 { todo!() } @@ -736,7 +738,7 @@ where } } -$0impl Foo for This +impl Foo for $0This where Self::Owned: Default, { @@ -780,7 +782,7 @@ where } } -$0impl Foo for This +impl Foo for $0This where Self::Owned: Default, { @@ -826,7 +828,7 @@ where } } -$0impl Foo for This +impl Foo for $0This where Self: ToOwned, Self::Owned: Default, @@ -865,7 +867,7 @@ trait Foo { } } -$0impl Foo for This { +impl Foo for $0This { fn foo(&self, x: Self::X) -> T { todo!() } @@ -900,7 +902,7 @@ trait Foo { } } -$0impl Foo for T { +impl Foo for $0T { fn foo(&self, x: Self::X) -> i32 { todo!() } @@ -934,7 +936,7 @@ trait Foo { } #[cfg(test)] -$0impl Foo for T { +impl Foo for $0T { fn foo(&self, x: i32) -> i32 { todo!() } @@ -968,7 +970,7 @@ trait Foo { } #[cfg(test)] -$0impl Foo for T { +impl Foo for $0T { #[cfg(test)] fn foo(&self, x: i32) -> i32 { todo!() @@ -988,7 +990,7 @@ trait $0Foo {} r#" trait Foo {} -$0impl Foo for T {} +impl Foo for $0T {} "#, ); } @@ -1003,7 +1005,7 @@ trait $0Foo: Copy {} r#" trait Foo: Copy {} -$0impl Foo for T {} +impl Foo for $0T {} "#, ); } @@ -1018,7 +1020,7 @@ trait $0Foo where Self: Copy {} r#" trait Foo where Self: Copy {} -$0impl Foo for T +impl Foo for $0T where Self: Copy { } @@ -1036,7 +1038,7 @@ trait $0Foo where Self: Copy, {} r#" trait Foo where Self: Copy, {} -$0impl Foo for T +impl Foo for $0T where Self: Copy, { } @@ -1058,7 +1060,7 @@ trait Foo where Self: Copy {} -$0impl Foo for T +impl Foo for $0T where Self: Copy { } @@ -1082,7 +1084,7 @@ where Self: Copy {} -$0impl Foo for T +impl Foo for $0T where Self: Copy { @@ -1107,7 +1109,7 @@ where Self: Copy, {} -$0impl Foo for T +impl Foo for $0T where Self: Copy, { @@ -1134,7 +1136,7 @@ where (): Into, {} -$0impl Foo for T +impl Foo for $0T where Self: Copy, (): Into, @@ -1162,7 +1164,7 @@ where (): Into, {} -$0impl Foo for T +impl Foo for $0T where Self: Copy + Sync, (): Into, @@ -1186,7 +1188,7 @@ trait Foo { fn foo(&self) {} } -$0impl Foo for T {} +impl Foo for $0T {} "#, ); } @@ -1203,7 +1205,7 @@ trait $0Foo {} /// some docs trait Foo {} -$0impl Foo for T {} +impl Foo for $0T {} "#, ); } @@ -1224,7 +1226,7 @@ trait Foo { fn bar(&self); } -$0impl Foo for T { +impl Foo for $0T { fn foo(&self) { todo!() } @@ -1253,7 +1255,7 @@ trait Foo { fn bar(&self, value: i32) -> i32 { todo!() } } -$0impl Foo for T { +impl Foo for $0T { fn foo(&self, value: i32) -> i32 { todo!() } @@ -1276,7 +1278,7 @@ trait Foo<'a> { fn foo(&self) -> &'a str; } -$0impl<'a, T: ?Sized> Foo<'a> for T { +impl<'a, T: ?Sized> Foo<'a> for $0T { fn foo(&self) -> &'a str { todo!() } @@ -1299,7 +1301,7 @@ trait Foo<'a: 'static> { fn foo(&self) -> &'a str; } -$0impl<'a: 'static, T: ?Sized> Foo<'a> for T { +impl<'a: 'static, T: ?Sized> Foo<'a> for $0T { fn foo(&self) -> &'a str { todo!() } @@ -1322,7 +1324,7 @@ trait Foo<'a>: 'a { fn foo(&self) -> &'a str; } -$0impl<'a, T: 'a + ?Sized> Foo<'a> for T { +impl<'a, T: 'a + ?Sized> Foo<'a> for $0T { fn foo(&self) -> &'a str { todo!() } @@ -1345,7 +1347,7 @@ trait Foo<'a, 'b> { fn foo(&self) -> &'a &'b str; } -$0impl<'a, 'b, T: ?Sized> Foo<'a, 'b> for T { +impl<'a, 'b, T: ?Sized> Foo<'a, 'b> for $0T { fn foo(&self) -> &'a &'b str { todo!() } @@ -1372,7 +1374,7 @@ where 'a: 'static, fn foo(&self) -> &'a str; } -$0impl<'a, T: ?Sized> Foo<'a> for T +impl<'a, T: ?Sized> Foo<'a> for $0T where 'a: 'static, { fn foo(&self) -> &'a str { @@ -1459,7 +1461,7 @@ trait Foo { } } -$0impl Foo for T { +impl Foo for $0T { fn foo(&self) -> i32 { todo!() } @@ -1496,7 +1498,7 @@ trait Foo { } } -$0impl Foo for T { +impl Foo for $0T { fn foo(&self) -> i32 { todo!() } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 03676e3628e7..8290cbd521a1 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1339,7 +1339,7 @@ where } } -$0impl Foo for This +impl Foo for $0This where Self::Owned: Default, { From b27de2d5ea40faa2a41911386702eec384d209be Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 28 Jun 2025 15:24:12 +0800 Subject: [PATCH 5/8] Use NameGenerator --- .../handlers/generate_blanket_trait_impl.rs | 56 +++++++------------ crates/ide-assists/src/tests/generated.rs | 2 +- 2 files changed, 22 insertions(+), 36 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index 7904170d37c0..36031f15a043 100644 --- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -3,7 +3,10 @@ use crate::{ assist_context::{AssistContext, Assists}, utils::add_cfg_attrs_to, }; -use ide_db::assists::{AssistId, AssistKind, ExprFillDefaultMode}; +use ide_db::{ + assists::{AssistId, AssistKind, ExprFillDefaultMode}, + syntax_helpers::suggest_name, +}; use syntax::{ AstNode, ast::{ @@ -42,7 +45,7 @@ use syntax::{ // } // } // -// impl Foo for $0This +// impl Foo for $0T1 // where // Self::Owned: Default, // { @@ -207,13 +210,9 @@ fn exlucde_sized(bounds: ast::TypeBoundList) -> Option { } fn this_name(traitd: &ast::Trait) -> ast::Name { - let mut use_t = false; - let mut use_i = false; - let mut use_this = false; - let has_iter = find_bound("Iterator", traitd.type_bound_list()).is_some(); - traitd + let params = traitd .generic_param_list() .into_iter() .flat_map(|param_list| param_list.generic_params()) @@ -222,26 +221,13 @@ fn this_name(traitd: &ast::Trait) -> ast::Name { GenericParam::ConstParam(cp) => cp.name(), GenericParam::TypeParam(tp) => tp.name(), }) - .for_each(|name| match &*name.text() { - "T" => use_t = true, - "I" => use_i = true, - "This" => use_this = true, - _ => (), - }); - - make::name(if has_iter { - if !use_i { - "I" - } else if !use_t { - "T" - } else { - "This" - } - } else if !use_t { - "T" - } else { - "This" - }) + .map(|name| name.to_string()) + .collect::>(); + + let mut name_gen = + suggest_name::NameGenerator::new_with_names(params.iter().map(String::as_str)); + + make::name(&name_gen.suggest_name(if has_iter { "I" } else { "T" })) } fn find_bound(s: &str, bounds: Option) -> Option { @@ -316,7 +302,7 @@ where } } -impl Foo for $0This +impl Foo for $0T1 where Self::Owned: Default, { @@ -499,7 +485,7 @@ mod foo { } } - impl Foo for $0This + impl Foo for $0T1 where Self::Owned: Default, { @@ -541,7 +527,7 @@ mod foo { } } - impl Foo for $0This + impl Foo for $0T1 where Self::Owned: Default, Self: Send, @@ -587,7 +573,7 @@ mod foo { } } - impl Foo for $0This + impl Foo for $0T1 where Self::Owned: Default, Self: Send, @@ -738,7 +724,7 @@ where } } -impl Foo for $0This +impl Foo for $0T1 where Self::Owned: Default, { @@ -782,7 +768,7 @@ where } } -impl Foo for $0This +impl Foo for $0T1 where Self::Owned: Default, { @@ -828,7 +814,7 @@ where } } -impl Foo for $0This +impl Foo for $0T1 where Self: ToOwned, Self::Owned: Default, @@ -867,7 +853,7 @@ trait Foo { } } -impl Foo for $0This { +impl Foo for $0T1 { fn foo(&self, x: Self::X) -> T { todo!() } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 8290cbd521a1..22c2f1e2b76e 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1339,7 +1339,7 @@ where } } -impl Foo for $0This +impl Foo for $0T1 where Self::Owned: Default, { From fc716167120d41140a5e017717cdc7a1d33a4587 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Sat, 28 Jun 2025 16:05:42 +0800 Subject: [PATCH 6/8] Move gen_blanket_trait_impl assist after generates --- crates/ide-assists/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index c951d7e14944..968ec3d02181 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -291,7 +291,6 @@ mod handlers { generate_default_from_enum_variant::generate_default_from_enum_variant, generate_default_from_new::generate_default_from_new, generate_delegate_trait::generate_delegate_trait, - generate_blanket_trait_impl::generate_blanket_trait_impl, generate_derive::generate_derive, generate_documentation_template::generate_doc_example, generate_documentation_template::generate_documentation_template, @@ -309,6 +308,7 @@ mod handlers { generate_new::generate_new, generate_trait_from_impl::generate_trait_from_impl, generate_single_field_struct_from::generate_single_field_struct_from, + generate_blanket_trait_impl::generate_blanket_trait_impl, inline_call::inline_call, inline_call::inline_into_callers, inline_const_as_literal::inline_const_as_literal, From 173ff5c3926d094753854374710a124099e4d918 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 25 Jul 2025 13:57:41 +0800 Subject: [PATCH 7/8] Add existing any implement not applicate blanket --- .../handlers/generate_blanket_trait_impl.rs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index 36031f15a043..1ec49f017fa2 100644 --- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -3,7 +3,9 @@ use crate::{ assist_context::{AssistContext, Assists}, utils::add_cfg_attrs_to, }; +use hir::Semantics; use ide_db::{ + RootDatabase, assists::{AssistId, AssistKind, ExprFillDefaultMode}, syntax_helpers::suggest_name, }; @@ -61,6 +63,11 @@ pub(crate) fn generate_blanket_trait_impl( let name = ctx.find_node_at_offset::()?; let traitd = ast::Trait::cast(name.syntax().parent()?)?; + if existing_any_impl(&traitd, &ctx.sema).is_some() { + cov_mark::hit!(existing_any_impl); + return None; + } + acc.add( AssistId("generate_blanket_trait_impl", AssistKind::Generate, None), "Generate blanket trait implementation", @@ -143,6 +150,16 @@ pub(crate) fn generate_blanket_trait_impl( Some(()) } +fn existing_any_impl(traitd: &ast::Trait, sema: &Semantics<'_, RootDatabase>) -> Option { + let db = sema.db; + let traitd = sema.to_def(traitd)?; + traitd + .module(db) + .impl_defs(db) + .into_iter() + .find(|impl_| impl_.trait_(db).is_some_and(|it| it == traitd)) +} + fn has_sized(traitd: &ast::Trait) -> bool { if let Some(sized) = find_bound("Sized", traitd.type_bound_list()) { sized.question_mark_token().is_none() @@ -1422,6 +1439,51 @@ where ); } + #[test] + fn test_gen_blanket_existing_impl() { + cov_mark::check!(existing_any_impl); + check_assist_not_applicable( + generate_blanket_trait_impl, + r#" +trait $0Foo: Default { + fn foo(&self) -> Self; +} +impl Foo for () {} +"#, + ); + } + + #[test] + fn test_gen_blanket_existing_other_impl() { + check_assist( + generate_blanket_trait_impl, + r#" +trait $0Foo: Default { + fn foo(&self) -> Self; +} +trait Bar: Default { + fn bar(&self) -> Self; +} +impl Bar for () {} +"#, + r#" +trait Foo: Default { + fn foo(&self) -> Self; +} + +impl Foo for $0T { + fn foo(&self) -> Self { + todo!() + } +} +trait Bar: Default { + fn bar(&self) -> Self; +} +impl Bar for () {} +"#, + ); + } + #[test] fn test_gen_blanket_apply_on_other_impl_block() { check_assist( From 109da7afd8248b916c2c7ae5dacb2e22f9ec76f3 Mon Sep 17 00:00:00 2001 From: A4-Tacks Date: Fri, 25 Jul 2025 20:52:56 +0800 Subject: [PATCH 8/8] Improve sized check, from super traits --- .../handlers/generate_blanket_trait_impl.rs | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs index 1ec49f017fa2..dc8056e90339 100644 --- a/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -3,10 +3,11 @@ use crate::{ assist_context::{AssistContext, Assists}, utils::add_cfg_attrs_to, }; -use hir::Semantics; +use hir::{HasCrate, Semantics}; use ide_db::{ RootDatabase, assists::{AssistId, AssistKind, ExprFillDefaultMode}, + famous_defs::FamousDefs, syntax_helpers::suggest_name, }; use syntax::{ @@ -84,7 +85,7 @@ pub(crate) fn generate_blanket_trait_impl( let gendecl = make::generic_param_list([GenericParam::TypeParam(make::type_param( thisname.clone(), - apply_sized(has_sized(&traitd), bounds), + apply_sized(has_sized(&traitd, &ctx.sema), bounds), ))]); let trait_gen_args = @@ -160,16 +161,24 @@ fn existing_any_impl(traitd: &ast::Trait, sema: &Semantics<'_, RootDatabase>) -> .find(|impl_| impl_.trait_(db).is_some_and(|it| it == traitd)) } -fn has_sized(traitd: &ast::Trait) -> bool { +fn has_sized(traitd: &ast::Trait, sema: &Semantics<'_, RootDatabase>) -> bool { if let Some(sized) = find_bound("Sized", traitd.type_bound_list()) { sized.question_mark_token().is_none() } else if let Some(is_sized) = where_clause_sized(traitd.where_clause()) { is_sized } else { contained_owned_self_method(traitd.assoc_item_list()) + || super_traits_has_sized(traitd, sema) == Some(true) } } +fn super_traits_has_sized(traitd: &ast::Trait, sema: &Semantics<'_, RootDatabase>) -> Option { + let traitd = sema.to_def(traitd)?; + let sized = FamousDefs(sema, traitd.krate(sema.db)).core_marker_Sized()?; + + Some(traitd.all_supertraits(sema.db).contains(&sized)) +} + fn contained_owned_self_method(item_list: Option) -> bool { item_list.into_iter().flat_map(|assoc_item_list| assoc_item_list.assoc_items()).any(|item| { match item { @@ -394,6 +403,30 @@ impl Foo for $0T { ); } + #[test] + fn test_gen_blanket_super_sized() { + check_assist( + generate_blanket_trait_impl, + r#" +//- minicore: default +trait $0Foo: Default { + fn foo(&self); +} +"#, + r#" +trait Foo: Default { + fn foo(&self); +} + +impl Foo for $0T { + fn foo(&self) { + todo!() + } +} +"#, + ); + } + #[test] fn test_gen_blanket_non_sized() { check_assist(