Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/generate_delegate_methods.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/generate_delegate_methods.rs | 130 |
1 files changed, 112 insertions, 18 deletions
diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs index ed1b8f4e28..3667fc375b 100644 --- a/crates/ide-assists/src/handlers/generate_delegate_methods.rs +++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use hir::{self, HasCrate, HasSource, HasVisibility}; use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility as _}; @@ -63,25 +65,36 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' }; let sema_field_ty = ctx.sema.resolve_type(&field_ty)?; - let krate = sema_field_ty.krate(ctx.db()); let mut methods = vec![]; - sema_field_ty.iterate_assoc_items(ctx.db(), krate, |item| { - if let hir::AssocItem::Function(f) = item { - if f.self_param(ctx.db()).is_some() && f.is_visible_from(ctx.db(), current_module) { - methods.push(f) + let mut seen_names = HashSet::new(); + + for ty in sema_field_ty.autoderef(ctx.db()) { + let krate = ty.krate(ctx.db()); + ty.iterate_assoc_items(ctx.db(), krate, |item| { + if let hir::AssocItem::Function(f) = item { + if f.self_param(ctx.db()).is_some() + && f.is_visible_from(ctx.db(), current_module) + && seen_names.insert(f.name(ctx.db())) + { + methods.push(f) + } } - } - Option::<()>::None - }); + Option::<()>::None + }); + } for method in methods { let adt = ast::Adt::Struct(strukt.clone()); - let name = method.name(ctx.db()).to_string(); - let impl_def = find_struct_impl(ctx, &adt, &[name]).flatten(); + let name = method.name(ctx.db()).display(ctx.db()).to_string(); + // if `find_struct_impl` returns None, that means that a function named `name` already exists. + let Some(impl_def) = find_struct_impl(ctx, &adt, &[name]) else { continue; }; acc.add_group( &GroupLabel("Generate delegate methods…".to_owned()), AssistId("generate_delegate_methods", AssistKind::Generate), - format!("Generate delegate for `{field_name}.{}()`", method.name(ctx.db())), + format!( + "Generate delegate for `{field_name}.{}()`", + method.name(ctx.db()).display(ctx.db()) + ), target, |builder| { // Create the function @@ -91,7 +104,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' }; let method_name = method.name(ctx.db()); let vis = method_source.visibility(); - let name = make::name(&method.name(ctx.db()).to_string()); + let name = make::name(&method.name(ctx.db()).display(ctx.db()).to_string()); let params = method_source.param_list().unwrap_or_else(|| make::param_list(None, [])); let type_params = method_source.generic_param_list(); @@ -101,17 +114,30 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' }; let tail_expr = make::expr_method_call( make::ext::field_from_idents(["self", &field_name]).unwrap(), // This unwrap is ok because we have at least 1 arg in the list - make::name_ref(&method_name.to_string()), + make::name_ref(&method_name.display(ctx.db()).to_string()), arg_list, ); let ret_type = method_source.ret_type(); let is_async = method_source.async_token().is_some(); + let is_const = method_source.const_token().is_some(); + let is_unsafe = method_source.unsafe_token().is_some(); let tail_expr_finished = if is_async { make::expr_await(tail_expr) } else { tail_expr }; let body = make::block_expr([], Some(tail_expr_finished)); - let f = make::fn_(vis, name, type_params, None, params, body, ret_type, is_async) - .indent(ast::edit::IndentLevel(1)) - .clone_for_update(); + let f = make::fn_( + vis, + name, + type_params, + None, + params, + body, + ret_type, + is_async, + is_const, + is_unsafe, + ) + .indent(ast::edit::IndentLevel(1)) + .clone_for_update(); let cursor = Cursor::Before(f.syntax()); @@ -143,8 +169,16 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<' let name = &strukt_name.to_string(); let params = strukt.generic_param_list(); let ty_params = params.clone(); - let impl_def = make::impl_(make::ext::ident_path(name), params, ty_params) - .clone_for_update(); + let where_clause = strukt.where_clause(); + + let impl_def = make::impl_( + ty_params, + None, + make::ty_path(make::ext::ident_path(name)), + where_clause, + None, + ) + .clone_for_update(); let assoc_items = impl_def.get_or_create_assoc_item_list(); assoc_items.add_item(f.clone().into()); @@ -315,6 +349,44 @@ impl<T> Person<T> { } #[test] + fn test_generates_delegate_autoderef() { + check_assist( + generate_delegate_methods, + r#" +//- minicore: deref +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} +struct AgeDeref(Age); +impl core::ops::Deref for AgeDeref { type Target = Age; } +struct Person { + ag$0e: AgeDeref, +} +impl Person {}"#, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} +struct AgeDeref(Age); +impl core::ops::Deref for AgeDeref { type Target = Age; } +struct Person { + age: AgeDeref, +} +impl Person { + $0fn age(&self) -> u8 { + self.age.age() + } +}"#, + ); + } + + #[test] fn test_generate_delegate_visibility() { check_assist_not_applicable( generate_delegate_methods, @@ -333,4 +405,26 @@ struct Person { }"#, ) } + + #[test] + fn test_generate_not_eligible_if_fn_exists() { + check_assist_not_applicable( + generate_delegate_methods, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} + +struct Person { + ag$0e: Age, +} +impl Person { + fn age(&self) -> u8 { 0 } +} +"#, + ); + } } |