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 | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/generate_delegate_methods.rs b/crates/ide-assists/src/handlers/generate_delegate_methods.rs new file mode 100644 index 0000000000..120acde8e5 --- /dev/null +++ b/crates/ide-assists/src/handlers/generate_delegate_methods.rs @@ -0,0 +1,334 @@ +use hir::{self, HasCrate, HasSource, HasVisibility}; +use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility as _}; + +use crate::{ + utils::{convert_param_list_to_arg_list, find_struct_impl, render_snippet, Cursor}, + AssistContext, AssistId, AssistKind, Assists, GroupLabel, +}; +use syntax::ast::edit::AstNodeEdit; + +// Assist: generate_delegate_methods +// +// Generate delegate methods. +// +// ``` +// struct Age(u8); +// impl Age { +// fn age(&self) -> u8 { +// self.0 +// } +// } +// +// struct Person { +// ag$0e: Age, +// } +// ``` +// -> +// ``` +// struct Age(u8); +// impl Age { +// fn age(&self) -> u8 { +// self.0 +// } +// } +// +// struct Person { +// age: Age, +// } +// +// impl Person { +// $0fn age(&self) -> u8 { +// self.age.age() +// } +// } +// ``` +pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let strukt = ctx.find_node_at_offset::<ast::Struct>()?; + let strukt_name = strukt.name()?; + let current_module = ctx.sema.scope(strukt.syntax())?.module(); + + let (field_name, field_ty, target) = match ctx.find_node_at_offset::<ast::RecordField>() { + Some(field) => { + let field_name = field.name()?; + let field_ty = field.ty()?; + (format!("{}", field_name), field_ty, field.syntax().text_range()) + } + None => { + let field = ctx.find_node_at_offset::<ast::TupleField>()?; + let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?; + let field_list_index = field_list.fields().position(|it| it == field)?; + let field_ty = field.ty()?; + (format!("{}", field_list_index), field_ty, field.syntax().text_range()) + } + }; + + 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) + } + } + 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(); + 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())), + target, + |builder| { + // Create the function + let method_source = match method.source(ctx.db()) { + Some(source) => source.value, + None => return, + }; + let method_name = method.name(ctx.db()); + let vis = method_source.visibility(); + let name = make::name(&method.name(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(); + let arg_list = match method_source.param_list() { + Some(list) => convert_param_list_to_arg_list(list), + None => make::arg_list([]), + }; + 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()), + arg_list, + ); + let body = make::block_expr([], Some(tail_expr)); + let ret_type = method_source.ret_type(); + let is_async = method_source.async_token().is_some(); + let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async) + .indent(ast::edit::IndentLevel(1)) + .clone_for_update(); + + let cursor = Cursor::Before(f.syntax()); + + // Create or update an impl block, attach the function to it, + // then insert into our code. + match impl_def { + Some(impl_def) => { + // Remember where in our source our `impl` block lives. + let impl_def = impl_def.clone_for_update(); + let old_range = impl_def.syntax().text_range(); + + // Attach the function to the impl block + let assoc_items = impl_def.get_or_create_assoc_item_list(); + assoc_items.add_item(f.clone().into()); + + // Update the impl block. + match ctx.config.snippet_cap { + Some(cap) => { + let snippet = render_snippet(cap, impl_def.syntax(), cursor); + builder.replace_snippet(cap, old_range, snippet); + } + None => { + builder.replace(old_range, impl_def.syntax().to_string()); + } + } + } + None => { + // Attach the function to the impl block + 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 assoc_items = impl_def.get_or_create_assoc_item_list(); + assoc_items.add_item(f.clone().into()); + + // Insert the impl block. + match ctx.config.snippet_cap { + Some(cap) => { + let offset = strukt.syntax().text_range().end(); + let snippet = render_snippet(cap, impl_def.syntax(), cursor); + let snippet = format!("\n\n{}", snippet); + builder.insert_snippet(cap, offset, snippet); + } + None => { + let offset = strukt.syntax().text_range().end(); + let snippet = format!("\n\n{}", impl_def.syntax()); + builder.insert(offset, snippet); + } + } + } + } + }, + )?; + } + Some(()) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_generate_delegate_create_impl_block() { + check_assist( + generate_delegate_methods, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} + +struct Person { + ag$0e: Age, +}"#, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} + +struct Person { + age: Age, +} + +impl Person { + $0fn age(&self) -> u8 { + self.age.age() + } +}"#, + ); + } + + #[test] + fn test_generate_delegate_update_impl_block() { + check_assist( + generate_delegate_methods, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} + +struct Person { + ag$0e: Age, +} + +impl Person {}"#, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} + +struct Person { + age: Age, +} + +impl Person { + $0fn age(&self) -> u8 { + self.age.age() + } +}"#, + ); + } + + #[test] + fn test_generate_delegate_tuple_struct() { + check_assist( + generate_delegate_methods, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} + +struct Person(A$0ge);"#, + r#" +struct Age(u8); +impl Age { + fn age(&self) -> u8 { + self.0 + } +} + +struct Person(Age); + +impl Person { + $0fn age(&self) -> u8 { + self.0.age() + } +}"#, + ); + } + + #[test] + fn test_generate_delegate_enable_all_attributes() { + check_assist( + generate_delegate_methods, + r#" +struct Age<T>(T); +impl<T> Age<T> { + pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T { + self.0 + } +} + +struct Person<T> { + ag$0e: Age<T>, +}"#, + r#" +struct Age<T>(T); +impl<T> Age<T> { + pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T { + self.0 + } +} + +struct Person<T> { + age: Age<T>, +} + +impl<T> Person<T> { + $0pub(crate) async fn age<J, 'a>(&'a mut self, ty: T, arg: J) -> T { + self.age.age(ty, arg) + } +}"#, + ); + } + + #[test] + fn test_generate_delegate_visibility() { + check_assist_not_applicable( + generate_delegate_methods, + r#" +mod m { + pub struct Age(u8); + impl Age { + fn age(&self) -> u8 { + self.0 + } + } +} + +struct Person { + ag$0e: m::Age, +}"#, + ) + } +} |