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..32ea37c37f39 --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_blanket_trait_impl.rs @@ -0,0 +1,1510 @@ +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, edit_in_place::Indent, make, + }, + syntax_editor::Position, +}; + +// 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 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().and_then(exlucde_sized); + let is_unsafe = traitd.unsafe_token().is_some(); + 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(), + apply_sized(has_sized(&traitd), 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(); + 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_.indent(indent); + + 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); + }, + ); + + 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_( + 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_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( + 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) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } +} + "#, + r#" +mod foo { + trait Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0impl Foo for T { + fn foo(&self) -> i32 { + todo!() + } + } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + mod bar { + trait $0Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + } +} + "#, + r#" +mod foo { + mod bar { + trait Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + $0impl Foo for T { + fn foo(&self) -> i32 { + todo!() + } + } + } +} + "#, + ); + check_assist( + generate_blanket_trait_impl, + r#" +mod foo { + mod bar { + #[cfg(test)] + trait $0Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + } +} + "#, + r#" +mod foo { + mod bar { + #[cfg(test)] + trait Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } + } + + #[cfg(test)] + $0impl Foo for T { + fn foo(&self) -> i32 { + 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 T { + 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()); + } +} + +#[cfg(test)] +$0impl Foo for T { + 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()); + } +} + +#[cfg(test)] +$0impl Foo for T { + #[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 T {} +"#, + ); + } + + #[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 T {} +"#, + ); + } + + #[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 T +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 T +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 T +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 T +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 T +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 T +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 T +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 T {} +"#, + ); + } + + #[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 T {} +"#, + ); + } + + #[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 T { + 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: i32) -> i32; + fn bar(&self, value: i32) -> i32 { todo!() } +} +"#, + r#" +trait Foo { + fn foo(&self, value: i32) -> i32; + fn bar(&self, value: i32) -> i32 { todo!() } +} + +$0impl Foo for T { + fn foo(&self, value: i32) -> i32 { + 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, T: ?Sized> Foo<'a> for T { + 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, T: ?Sized> Foo<'a> for T { + 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, T: 'a + ?Sized> Foo<'a> for T { + 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, T: ?Sized> Foo<'a, 'b> for T { + 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, T: ?Sized> Foo<'a> for T +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 T { + 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 T {} +"#, + r#" +trait Foo { + fn foo(&self) -> i32; + + fn print_foo(&self) { + println!("{}", self.foo()); + } +} + +$0impl Foo for T { + fn foo(&self) -> i32 { + todo!() + } +} + +trait Bar {} +impl Bar for T {} +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 2395091b6f2e..d27e5f2d27a6 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -152,6 +152,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; @@ -288,6 +289,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 76134acb36eb..fa205d20430a 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1270,6 +1270,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( diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs index ef6914fda1d5..e4ae4d183596 100644 --- a/crates/ide-assists/src/utils.rs +++ b/crates/ide-assists/src/utils.rs @@ -747,16 +747,23 @@ fn generate_impl_inner( .clone_for_update(); // Copy any cfg attrs from the original adt - let cfg_attrs = adt - .attrs() - .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)); - for attr in cfg_attrs { - impl_.add_attr(attr.clone_for_update()); - } + add_cfg_attrs_to(adt, &impl_); impl_ } +pub(crate) fn add_cfg_attrs_to(from: &T, to: &U) +where + T: HasAttrs, + U: AttrsOwnerEdit, +{ + let cfg_attrs = + from.attrs().filter(|attr| attr.as_simple_call().is_some_and(|(name, _arg)| name == "cfg")); + for attr in cfg_attrs { + to.add_attr(attr.clone_for_update()); + } +} + pub(crate) fn add_method_to_adt( builder: &mut SourceChangeBuilder, adt: &ast::Adt,