From 0ff89deb6973be35848380be28c4a23063af8768 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Wed, 13 Oct 2021 15:08:40 +0200 Subject: [PATCH 1/9] Add basic support for delegation --- crates/hir/src/lib.rs | 6 + .../src/handlers/generate_delegate.rs | 149 ++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + crates/syntax/src/ast/make.rs | 4 + 4 files changed, 161 insertions(+) create mode 100644 crates/ide_assists/src/handlers/generate_delegate.rs diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c356a71ed6..de59cb9613 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -3007,3 +3007,9 @@ impl HasCrate for Function { self.module(db).krate() } } + +impl HasCrate for Type { + fn krate(&self, _db: &dyn HirDatabase) -> Crate { + self.krate.into() + } +} diff --git a/crates/ide_assists/src/handlers/generate_delegate.rs b/crates/ide_assists/src/handlers/generate_delegate.rs new file mode 100644 index 0000000000..396c709237 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_delegate.rs @@ -0,0 +1,149 @@ +use hir::{self, HasCrate, HirDisplay}; +use stdx::format_to; +use syntax::ast::{self, AstNode, HasName, HasVisibility}; + +use crate::{ + utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, + AssistContext, AssistId, AssistKind, Assists, GroupLabel, +}; + +// Assist: generate_setter +// +// Generate a setter method. +// +// ``` +// struct Person { +// nam$0e: String, +// } +// ``` +// -> +// ``` +// struct Person { +// name: String, +// } +// +// impl Person { +// /// Set the person's name. +// fn set_name(&mut self, name: String) { +// self.name = name; +// } +// } +// ``` +pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let strukt = ctx.find_node_at_offset::()?; + let field = ctx.find_node_at_offset::()?; + + let field_name = field.name()?; + let field_ty = field.ty()?; + + 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() { + methods.push(f) + } + } + Some(()) + }); + + let target = field_ty.syntax().text_range(); + for method in methods { + let impl_def = find_struct_impl( + ctx, + &ast::Adt::Struct(strukt.clone()), + &method.name(ctx.db()).to_string(), + )?; + acc.add_group( + &GroupLabel("Generate delegate".to_owned()), + AssistId("generate_delegate", AssistKind::Generate), + format!("Generate a delegate method for '{}'", method.name(ctx.db())), + target, + |builder| { + let mut buf = String::with_capacity(512); + + let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); + let return_type = method.ret_type(ctx.db()); + let return_type = if return_type.is_unit() || return_type.is_unknown() { + String::new() + } else { + let module = match ctx.sema.scope(strukt.syntax()).module() { + Some(m) => m, + None => return, + }; + match return_type.display_source_code(ctx.db(), module.into()) { + Ok(rt) => format!("-> {}", rt), + Err(_) => return, + } + }; + + format_to!( + buf, + "{}fn {}(&self) {} {{ + self.{}.{}() + }}", + vis, + method.name(ctx.db()), + return_type, + field_name, + method.name(ctx.db()) + ); + + let start_offset = impl_def + .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) + .unwrap_or_else(|| { + buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf); + strukt.syntax().text_range().end() + }); + + builder.insert(start_offset, buf); + }, + )?; + } + Some(()) +} + +#[cfg(test)] +mod tests { + use crate::tests::check_assist; + + use super::*; + + #[test] + fn test_generate_setter_from_field() { + check_assist( + generate_delegate, + 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 { + fn age(&self) -> u8 { + self.age.age() + } +}"#, + ); + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index a0629afb1d..e6a85e68d3 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -144,6 +144,7 @@ mod handlers { mod generate_is_empty_from_len; mod generate_new; mod generate_setter; + mod generate_delegate; mod add_return_type; mod inline_call; mod inline_local_variable; @@ -210,6 +211,7 @@ mod handlers { generate_constant::generate_constant, generate_default_from_enum_variant::generate_default_from_enum_variant, generate_default_from_new::generate_default_from_new, + generate_delegate::generate_delegate, generate_deref::generate_deref, generate_derive::generate_derive, generate_enum_is_method::generate_enum_is_method, diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index f16dcd56e9..d428044450 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -124,6 +124,10 @@ pub fn assoc_item_list() -> ast::AssocItemList { ast_from_text("impl C for D {}") } +pub fn impl_(ty: ast::Path) -> ast::Impl { + ast_from_text(&format!("impl {} {{}}", ty)) +} + pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { ast_from_text(&format!("impl {} for {} {{}}", trait_, ty)) } From c14a12edd75ea37f357d81ee73585669ad5c3e5e Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Wed, 13 Oct 2021 20:13:36 +0200 Subject: [PATCH 2/9] create function --- .../src/handlers/generate_delegate.rs | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/crates/ide_assists/src/handlers/generate_delegate.rs b/crates/ide_assists/src/handlers/generate_delegate.rs index 396c709237..bdc277e5f4 100644 --- a/crates/ide_assists/src/handlers/generate_delegate.rs +++ b/crates/ide_assists/src/handlers/generate_delegate.rs @@ -1,9 +1,9 @@ use hir::{self, HasCrate, HirDisplay}; use stdx::format_to; -use syntax::ast::{self, AstNode, HasName, HasVisibility}; +use syntax::ast::{self, make, AstNode, HasName, HasVisibility}; use crate::{ - utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, + utils::{find_impl_block_end, find_struct_impl, generate_impl_text, render_snippet, Cursor}, AssistContext, AssistId, AssistKind, Assists, GroupLabel, }; @@ -78,17 +78,16 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio } }; - format_to!( - buf, - "{}fn {}(&self) {} {{ - self.{}.{}() - }}", - vis, - method.name(ctx.db()), - return_type, - field_name, - method.name(ctx.db()) - ); + // make function + let vis = strukt.visibility(); + let name = make::name(&method.name(ctx.db()).to_string()); + let type_params = None; + let params = make::param_list(None, []); + let body = make::block_expr([], None); + let ret_type = &method.ret_type(ctx.db()).display(ctx.db()).to_string(); + let ret_type = Some(make::ret_type(make::ty(ret_type))); + let is_async = false; + let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async); let start_offset = impl_def .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) @@ -97,7 +96,14 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio strukt.syntax().text_range().end() }); - builder.insert(start_offset, buf); + let cap = ctx.config.snippet_cap.unwrap(); // FIXME. + let cursor = Cursor::Before(f.syntax()); + + builder.insert_snippet( + cap, + start_offset, + format!("\n\n{}", render_snippet(cap, f.syntax(), cursor)), + ); }, )?; } From efb4d45ebc77a2e28f843fb22507e63339db829c Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Wed, 13 Oct 2021 23:59:23 +0200 Subject: [PATCH 3/9] Update generate_delegate.rs --- .../src/handlers/generate_delegate.rs | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/crates/ide_assists/src/handlers/generate_delegate.rs b/crates/ide_assists/src/handlers/generate_delegate.rs index bdc277e5f4..2b68920f22 100644 --- a/crates/ide_assists/src/handlers/generate_delegate.rs +++ b/crates/ide_assists/src/handlers/generate_delegate.rs @@ -1,9 +1,8 @@ use hir::{self, HasCrate, HirDisplay}; -use stdx::format_to; use syntax::ast::{self, make, AstNode, HasName, HasVisibility}; use crate::{ - utils::{find_impl_block_end, find_struct_impl, generate_impl_text, render_snippet, Cursor}, + utils::{find_struct_impl, render_snippet, Cursor}, AssistContext, AssistId, AssistKind, Assists, GroupLabel, }; @@ -31,9 +30,9 @@ use crate::{ // ``` pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let strukt = ctx.find_node_at_offset::()?; - let field = ctx.find_node_at_offset::()?; + let strukt_name = strukt.name()?; - let field_name = field.name()?; + let field = ctx.find_node_at_offset::()?; let field_ty = field.ty()?; let sema_field_ty = ctx.sema.resolve_type(&field_ty)?; @@ -61,23 +60,6 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio format!("Generate a delegate method for '{}'", method.name(ctx.db())), target, |builder| { - let mut buf = String::with_capacity(512); - - let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); - let return_type = method.ret_type(ctx.db()); - let return_type = if return_type.is_unit() || return_type.is_unknown() { - String::new() - } else { - let module = match ctx.sema.scope(strukt.syntax()).module() { - Some(m) => m, - None => return, - }; - match return_type.display_source_code(ctx.db(), module.into()) { - Ok(rt) => format!("-> {}", rt), - Err(_) => return, - } - }; - // make function let vis = strukt.visibility(); let name = make::name(&method.name(ctx.db()).to_string()); @@ -89,21 +71,28 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio let is_async = false; let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async); - let start_offset = impl_def - .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) - .unwrap_or_else(|| { - buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf); - strukt.syntax().text_range().end() - }); - - let cap = ctx.config.snippet_cap.unwrap(); // FIXME. let cursor = Cursor::Before(f.syntax()); + let cap = ctx.config.snippet_cap.unwrap(); // FIXME. - builder.insert_snippet( - cap, - start_offset, - format!("\n\n{}", render_snippet(cap, f.syntax(), cursor)), - ); + match impl_def { + Some(impl_def) => { + let impl_def = impl_def.clone_for_update(); + let old_range = impl_def.syntax().text_range(); + let assoc_items = impl_def.get_or_create_assoc_item_list(); + assoc_items.add_item(f.clone().into()); + let snippet = render_snippet(cap, impl_def.syntax(), cursor); + builder.replace_snippet(cap, old_range, snippet); + } + None => { + let name = &strukt_name.to_string(); + let impl_def = make::impl_(make::ext::ident_path(name)); + let assoc_items = impl_def.get_or_create_assoc_item_list(); + assoc_items.add_item(f.clone().into()); + let start_offset = strukt.syntax().text_range().end(); + let snippet = render_snippet(cap, impl_def.syntax(), cursor); + builder.insert_snippet(cap, start_offset, snippet); + } + } }, )?; } @@ -132,11 +121,13 @@ impl Age { struct Person { ag$0e: Age, } + +impl Person {} "#, r#" struct Age(u8); impl Age { - fn age(&self) -> u8 { + $0fn age(&self) -> u8 { self.0 } } From c9882c80020d2d93728f6b52ae58b1de5902146b Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 14 Oct 2021 12:34:31 +0200 Subject: [PATCH 4/9] Get a make version working! --- .../src/handlers/generate_delegate.rs | 105 +++++++++++++++--- 1 file changed, 90 insertions(+), 15 deletions(-) diff --git a/crates/ide_assists/src/handlers/generate_delegate.rs b/crates/ide_assists/src/handlers/generate_delegate.rs index 2b68920f22..b5fdb72d35 100644 --- a/crates/ide_assists/src/handlers/generate_delegate.rs +++ b/crates/ide_assists/src/handlers/generate_delegate.rs @@ -1,10 +1,11 @@ -use hir::{self, HasCrate, HirDisplay}; +use hir::{self, HasCrate, HasSource, HirDisplay}; use syntax::ast::{self, make, AstNode, HasName, HasVisibility}; use crate::{ utils::{find_struct_impl, render_snippet, Cursor}, AssistContext, AssistId, AssistKind, Assists, GroupLabel, }; +use syntax::ast::edit::AstNodeEdit; // Assist: generate_setter // @@ -33,6 +34,7 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio let strukt_name = strukt.name()?; let field = ctx.find_node_at_offset::()?; + let field_name = field.name()?; let field_ty = field.ty()?; let sema_field_ty = ctx.sema.resolve_type(&field_ty)?; @@ -61,36 +63,62 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio target, |builder| { // make function - let vis = strukt.visibility(); + 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 type_params = None; - let params = make::param_list(None, []); - let body = make::block_expr([], None); + let self_ty = method + .self_param(ctx.db()) + .map(|s| s.source(ctx.db()).map(|s| s.value)) + .flatten(); + let params = make::param_list(self_ty, []); + let tail_expr = make::expr_method_call( + field_from_idents(["self", &field_name.to_string()]).unwrap(), + make::name_ref(&method_name.to_string()), + make::arg_list([]), + ); + let body = make::block_expr([], Some(tail_expr)); let ret_type = &method.ret_type(ctx.db()).display(ctx.db()).to_string(); let ret_type = Some(make::ret_type(make::ty(ret_type))); let is_async = false; - let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async); + 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()); let cap = ctx.config.snippet_cap.unwrap(); // FIXME. + // Create or update an impl block, and attach the function to it. 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. let snippet = render_snippet(cap, impl_def.syntax(), cursor); builder.replace_snippet(cap, old_range, snippet); } None => { + // Attach the function to the impl block let name = &strukt_name.to_string(); - let impl_def = make::impl_(make::ext::ident_path(name)); + let impl_def = make::impl_(make::ext::ident_path(name)).clone_for_update(); let assoc_items = impl_def.get_or_create_assoc_item_list(); assoc_items.add_item(f.clone().into()); - let start_offset = strukt.syntax().text_range().end(); + + // Insert the impl block. + let offset = strukt.syntax().text_range().end(); let snippet = render_snippet(cap, impl_def.syntax(), cursor); - builder.insert_snippet(cap, start_offset, snippet); + let snippet = format!("\n\n{}", snippet); + builder.insert_snippet(cap, offset, snippet); } } }, @@ -99,6 +127,15 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio Some(()) } +pub fn field_from_idents<'a>( + parts: impl std::iter::IntoIterator, +) -> Option { + let mut iter = parts.into_iter(); + let base = make::expr_path(make::ext::ident_path(iter.next()?)); + let expr = iter.fold(base, |base, s| make::expr_field(base, s)); + Some(expr) +} + #[cfg(test)] mod tests { use crate::tests::check_assist; @@ -106,7 +143,7 @@ mod tests { use super::*; #[test] - fn test_generate_setter_from_field() { + fn test_generate_delegate_create_impl_block() { check_assist( generate_delegate, r#" @@ -114,20 +151,16 @@ struct Age(u8); impl Age { fn age(&self) -> u8 { self.0 - } } struct Person { ag$0e: Age, -} - -impl Person {} -"#, +}"#, r#" struct Age(u8); impl Age { - $0fn age(&self) -> u8 { + fn age(&self) -> u8 { self.0 } } @@ -137,7 +170,49 @@ struct Person { } impl Person { + $0fn age(&self) -> u8 { + self.age.age() + } +}"#, + ); + } + + #[test] + fn test_generate_delegate_update_impl_block() { + check_assist( + generate_delegate, + 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() + } +}"#, + ); + } self.age.age() } }"#, From 680dd9d95254d6496447085ec828b02bac20d703 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 14 Oct 2021 13:23:46 +0200 Subject: [PATCH 5/9] Enable delegation generation for complex types --- .../src/handlers/generate_delegate.rs | 103 ++++++++++++------ crates/ide_assists/src/tests/generated.rs | 37 +++++++ crates/syntax/src/ast/make.rs | 27 ++++- 3 files changed, 133 insertions(+), 34 deletions(-) diff --git a/crates/ide_assists/src/handlers/generate_delegate.rs b/crates/ide_assists/src/handlers/generate_delegate.rs index b5fdb72d35..2716df0943 100644 --- a/crates/ide_assists/src/handlers/generate_delegate.rs +++ b/crates/ide_assists/src/handlers/generate_delegate.rs @@ -1,5 +1,5 @@ use hir::{self, HasCrate, HasSource, HirDisplay}; -use syntax::ast::{self, make, AstNode, HasName, HasVisibility}; +use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility}; use crate::{ utils::{find_struct_impl, render_snippet, Cursor}, @@ -7,29 +7,44 @@ use crate::{ }; use syntax::ast::edit::AstNodeEdit; -// Assist: generate_setter +// Assist: generate_delegate // -// Generate a setter method. +// Generate a delegate method. // // ``` +// struct Age(u8); +// impl Age { +// fn age(&self) -> u8 { +// self.0 +// } +// } +// // struct Person { -// nam$0e: String, +// ag$0e: Age, // } // ``` // -> // ``` +// struct Age(u8); +// impl Age { +// fn age(&self) -> u8 { +// self.0 +// } +// } +// // struct Person { -// name: String, +// age: Age, // } // // impl Person { -// /// Set the person's name. -// fn set_name(&mut self, name: String) { -// self.name = name; +// $0fn age(&self) -> u8 { +// self.age.age() // } // } // ``` pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let cap = ctx.config.snippet_cap?; + let strukt = ctx.find_node_at_offset::()?; let strukt_name = strukt.name()?; @@ -62,7 +77,7 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio format!("Generate a delegate method for '{}'", method.name(ctx.db())), target, |builder| { - // make function + // Create the function let method_source = match method.source(ctx.db()) { Some(source) => source.value, None => return, @@ -70,29 +85,31 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio let method_name = method.name(ctx.db()); let vis = method_source.visibility(); let name = make::name(&method.name(ctx.db()).to_string()); - let type_params = None; - let self_ty = method - .self_param(ctx.db()) - .map(|s| s.source(ctx.db()).map(|s| s.value)) - .flatten(); - let params = make::param_list(self_ty, []); + let params = + method_source.param_list().unwrap_or_else(|| make::param_list(None, [])); let tail_expr = make::expr_method_call( - field_from_idents(["self", &field_name.to_string()]).unwrap(), + make::ext::field_from_idents(["self", &field_name.to_string()]).unwrap(), // This unwrap is ok because we have at least 1 arg in the list make::name_ref(&method_name.to_string()), make::arg_list([]), ); + let type_params = method_source.generic_param_list(); let body = make::block_expr([], Some(tail_expr)); - let ret_type = &method.ret_type(ctx.db()).display(ctx.db()).to_string(); - let ret_type = Some(make::ret_type(make::ty(ret_type))); - let is_async = false; + let ret_type = method.ret_type(ctx.db()); + let ret_type = if ret_type.is_unknown() { + Some(make::ret_type(make::ty_placeholder())) + } else { + let ret_type = &ret_type.display(ctx.db()).to_string(); + Some(make::ret_type(make::ty(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()); - let cap = ctx.config.snippet_cap.unwrap(); // FIXME. - // Create or update an impl block, and attach the function to it. + // 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. @@ -110,7 +127,10 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio None => { // Attach the function to the impl block let name = &strukt_name.to_string(); - let impl_def = make::impl_(make::ext::ident_path(name)).clone_for_update(); + 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()); @@ -127,15 +147,6 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio Some(()) } -pub fn field_from_idents<'a>( - parts: impl std::iter::IntoIterator, -) -> Option { - let mut iter = parts.into_iter(); - let base = make::expr_path(make::ext::ident_path(iter.next()?)); - let expr = iter.fold(base, |base, s| make::expr_field(base, s)); - Some(expr) -} - #[cfg(test)] mod tests { use crate::tests::check_assist; @@ -213,6 +224,36 @@ impl Person { }"#, ); } + + #[test] + fn test_generate_delegate_enable_all_attributes() { + check_assist( + generate_delegate, + r#" +struct Age(T); +impl Age { + pub(crate) async fn age(&'a mut self, ty: T, arg: J) -> T { + self.0 + } +} + +struct Person { + ag$0e: Age, +}"#, + r#" +struct Age(T); +impl Age { + pub(crate) async fn age(&'a mut self, ty: T, arg: J) -> T { + self.0 + } +} + +struct Person { + age: Age, +} + +impl Person { + $0pub(crate) async fn age(&'a mut self, ty: T, arg: J) -> _ { self.age.age() } }"#, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 62c44d70e4..8729b2eb49 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -728,6 +728,43 @@ impl Default for Example { ) } +#[test] +fn doctest_generate_delegate() { + check_doc_test( + "generate_delegate", + 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 doctest_generate_deref() { check_doc_test( diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index d428044450..e67ac69073 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -44,6 +44,15 @@ pub mod ext { Some(path) } + pub fn field_from_idents<'a>( + parts: impl std::iter::IntoIterator, + ) -> Option { + let mut iter = parts.into_iter(); + let base = expr_path(ext::ident_path(iter.next()?)); + let expr = iter.fold(base, |base, s| expr_field(base, s)); + Some(expr) + } + pub fn expr_unreachable() -> ast::Expr { expr_from_text("unreachable!()") } @@ -124,8 +133,20 @@ pub fn assoc_item_list() -> ast::AssocItemList { ast_from_text("impl C for D {}") } -pub fn impl_(ty: ast::Path) -> ast::Impl { - ast_from_text(&format!("impl {} {{}}", ty)) +pub fn impl_( + ty: ast::Path, + params: Option, + ty_params: Option, +) -> ast::Impl { + let params = match params { + Some(params) => params.to_string(), + None => String::new(), + }; + let ty_params = match ty_params { + Some(params) => params.to_string(), + None => String::new(), + }; + ast_from_text(&format!("impl{} {}{} {{}}", params, ty, ty_params)) } pub fn impl_trait(trait_: ast::Path, ty: ast::Path) -> ast::Impl { @@ -649,7 +670,7 @@ pub fn fn_( is_async: bool, ) -> ast::Fn { let type_params = match type_params { - Some(type_params) => format!("<{}>", type_params), + Some(type_params) => format!("{}", type_params), None => "".into(), }; let ret_type = match ret_type { From 8b6ea8ee868bc6f97b145335d792439449910a09 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 14 Oct 2021 13:52:31 +0200 Subject: [PATCH 6/9] Update label names --- ...elegate.rs => generate_delegate_methods.rs} | 18 +++++++++--------- crates/ide_assists/src/lib.rs | 4 ++-- crates/ide_assists/src/tests/generated.rs | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) rename crates/ide_assists/src/handlers/{generate_delegate.rs => generate_delegate_methods.rs} (92%) diff --git a/crates/ide_assists/src/handlers/generate_delegate.rs b/crates/ide_assists/src/handlers/generate_delegate_methods.rs similarity index 92% rename from crates/ide_assists/src/handlers/generate_delegate.rs rename to crates/ide_assists/src/handlers/generate_delegate_methods.rs index 2716df0943..b6cf353682 100644 --- a/crates/ide_assists/src/handlers/generate_delegate.rs +++ b/crates/ide_assists/src/handlers/generate_delegate_methods.rs @@ -7,9 +7,9 @@ use crate::{ }; use syntax::ast::edit::AstNodeEdit; -// Assist: generate_delegate +// Assist: generate_delegate_methods // -// Generate a delegate method. +// Generate delegate methods. // // ``` // struct Age(u8); @@ -42,7 +42,7 @@ use syntax::ast::edit::AstNodeEdit; // } // } // ``` -pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { +pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let cap = ctx.config.snippet_cap?; let strukt = ctx.find_node_at_offset::()?; @@ -72,9 +72,9 @@ pub(crate) fn generate_delegate(acc: &mut Assists, ctx: &AssistContext) -> Optio &method.name(ctx.db()).to_string(), )?; acc.add_group( - &GroupLabel("Generate delegate".to_owned()), - AssistId("generate_delegate", AssistKind::Generate), - format!("Generate a delegate method for '{}'", method.name(ctx.db())), + &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 @@ -156,7 +156,7 @@ mod tests { #[test] fn test_generate_delegate_create_impl_block() { check_assist( - generate_delegate, + generate_delegate_methods, r#" struct Age(u8); impl Age { @@ -191,7 +191,7 @@ impl Person { #[test] fn test_generate_delegate_update_impl_block() { check_assist( - generate_delegate, + generate_delegate_methods, r#" struct Age(u8); impl Age { @@ -228,7 +228,7 @@ impl Person { #[test] fn test_generate_delegate_enable_all_attributes() { check_assist( - generate_delegate, + generate_delegate_methods, r#" struct Age(T); impl Age { diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index e6a85e68d3..bd543ff3e4 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -144,7 +144,7 @@ mod handlers { mod generate_is_empty_from_len; mod generate_new; mod generate_setter; - mod generate_delegate; + mod generate_delegate_methods; mod add_return_type; mod inline_call; mod inline_local_variable; @@ -211,7 +211,7 @@ mod handlers { generate_constant::generate_constant, generate_default_from_enum_variant::generate_default_from_enum_variant, generate_default_from_new::generate_default_from_new, - generate_delegate::generate_delegate, + generate_delegate_methods::generate_delegate_methods, generate_deref::generate_deref, generate_derive::generate_derive, generate_enum_is_method::generate_enum_is_method, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 8729b2eb49..fba7736633 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -729,9 +729,9 @@ impl Default for Example { } #[test] -fn doctest_generate_delegate() { +fn doctest_generate_delegate_methods() { check_doc_test( - "generate_delegate", + "generate_delegate_methods", r#####" struct Age(u8); impl Age { From 68ffe91526510a4640d0d0d718a41f607f49dab3 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 14 Oct 2021 14:18:12 +0200 Subject: [PATCH 7/9] Add support for tuple structs --- .../src/handlers/generate_delegate_methods.rs | 51 +++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/crates/ide_assists/src/handlers/generate_delegate_methods.rs b/crates/ide_assists/src/handlers/generate_delegate_methods.rs index b6cf353682..ecebc79251 100644 --- a/crates/ide_assists/src/handlers/generate_delegate_methods.rs +++ b/crates/ide_assists/src/handlers/generate_delegate_methods.rs @@ -48,9 +48,21 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) let strukt = ctx.find_node_at_offset::()?; let strukt_name = strukt.name()?; - let field = ctx.find_node_at_offset::()?; - let field_name = field.name()?; - let field_ty = field.ty()?; + let (field_name, field_ty) = match ctx.find_node_at_offset::() { + Some(field) => { + let field_name = field.name()?; + let field_ty = field.ty()?; + (format!("{}", field_name), field_ty) + } + None => { + let field = ctx.find_node_at_offset::()?; + let field_list = ctx.find_node_at_offset::()?; + let field_list_index = + field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?; + let field_ty = field.ty()?; + (format!("{}", field_list_index), field_ty) + } + }; let sema_field_ty = ctx.sema.resolve_type(&field_ty)?; let krate = sema_field_ty.krate(ctx.db()); @@ -88,7 +100,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) let params = method_source.param_list().unwrap_or_else(|| make::param_list(None, [])); let tail_expr = make::expr_method_call( - make::ext::field_from_idents(["self", &field_name.to_string()]).unwrap(), // This unwrap is ok because we have at least 1 arg in the list + 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::arg_list([]), ); @@ -225,6 +237,37 @@ impl Person { ); } + #[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( From 987ab1feda701581dc0254453fbf80eaf39d169b Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 14 Oct 2021 18:19:20 +0200 Subject: [PATCH 8/9] implement feedback from review --- .../src/handlers/generate_delegate_methods.rs | 58 ++++++++++++------- crates/ide_assists/src/utils.rs | 16 +++++ 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/crates/ide_assists/src/handlers/generate_delegate_methods.rs b/crates/ide_assists/src/handlers/generate_delegate_methods.rs index ecebc79251..1db945a6fc 100644 --- a/crates/ide_assists/src/handlers/generate_delegate_methods.rs +++ b/crates/ide_assists/src/handlers/generate_delegate_methods.rs @@ -2,7 +2,7 @@ use hir::{self, HasCrate, HasSource, HirDisplay}; use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility}; use crate::{ - utils::{find_struct_impl, render_snippet, Cursor}, + utils::{convert_param_list_to_arg_list, find_struct_impl, render_snippet, Cursor}, AssistContext, AssistId, AssistKind, Assists, GroupLabel, }; use syntax::ast::edit::AstNodeEdit; @@ -43,8 +43,6 @@ use syntax::ast::edit::AstNodeEdit; // } // ``` pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let cap = ctx.config.snippet_cap?; - let strukt = ctx.find_node_at_offset::()?; let strukt_name = strukt.name()?; @@ -57,8 +55,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) None => { let field = ctx.find_node_at_offset::()?; let field_list = ctx.find_node_at_offset::()?; - let field_list_index = - field_list.syntax().children().into_iter().position(|s| &s == field.syntax())?; + let field_list_index = field_list.fields().position(|it| it == field)?; let field_ty = field.ty()?; (format!("{}", field_list_index), field_ty) } @@ -73,16 +70,14 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) methods.push(f) } } - Some(()) + Option::<()>::None }); let target = field_ty.syntax().text_range(); for method in methods { - let impl_def = find_struct_impl( - ctx, - &ast::Adt::Struct(strukt.clone()), - &method.name(ctx.db()).to_string(), - )?; + 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), @@ -99,15 +94,22 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) 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()), - make::arg_list([]), + arg_list, ); - let type_params = method_source.generic_param_list(); let body = make::block_expr([], Some(tail_expr)); let ret_type = method.ret_type(ctx.db()); let ret_type = if ret_type.is_unknown() { + // FIXME: we currently can't resolve certain generics, and + // are returning placeholders instead. We should fix our + // type resolution here, so we return fewer placeholders. Some(make::ret_type(make::ty_placeholder())) } else { let ret_type = &ret_type.display(ctx.db()).to_string(); @@ -133,8 +135,15 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) assoc_items.add_item(f.clone().into()); // Update the impl block. - let snippet = render_snippet(cap, impl_def.syntax(), cursor); - builder.replace_snippet(cap, old_range, snippet); + 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 @@ -147,10 +156,19 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) assoc_items.add_item(f.clone().into()); // Insert the impl block. - 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); + 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().to_string()); + builder.insert(offset, snippet); + } + } } } }, @@ -297,7 +315,7 @@ struct Person { impl Person { $0pub(crate) async fn age(&'a mut self, ty: T, arg: J) -> _ { - self.age.age() + self.age.age(ty, arg) } }"#, ); diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 3d4ab968fb..c1092b97c2 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs @@ -525,3 +525,19 @@ pub(crate) fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRa } trimmed_range } + +/// Convert a list of function params to a list of arguments that can be passed +/// into a function call. +pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgList { + let mut args = vec![]; + for param in list.params() { + if let Some(ast::Pat::IdentPat(pat)) = param.pat() { + if let Some(name) = pat.name() { + let name = name.to_string(); + let expr = make::expr_path(make::ext::ident_path(&name)); + args.push(expr); + } + } + } + make::arg_list(args) +} From f84b0b32425b18301cabcecb61a485ad4a5c8bd4 Mon Sep 17 00:00:00 2001 From: Yoshua Wuyts Date: Thu, 14 Oct 2021 18:33:29 +0200 Subject: [PATCH 9/9] fix ret type in generic --- .../src/handlers/generate_delegate_methods.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/crates/ide_assists/src/handlers/generate_delegate_methods.rs b/crates/ide_assists/src/handlers/generate_delegate_methods.rs index 1db945a6fc..233f26ed63 100644 --- a/crates/ide_assists/src/handlers/generate_delegate_methods.rs +++ b/crates/ide_assists/src/handlers/generate_delegate_methods.rs @@ -1,4 +1,4 @@ -use hir::{self, HasCrate, HasSource, HirDisplay}; +use hir::{self, HasCrate, HasSource}; use syntax::ast::{self, make, AstNode, HasGenericParams, HasName, HasVisibility}; use crate::{ @@ -105,16 +105,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext) arg_list, ); let body = make::block_expr([], Some(tail_expr)); - let ret_type = method.ret_type(ctx.db()); - let ret_type = if ret_type.is_unknown() { - // FIXME: we currently can't resolve certain generics, and - // are returning placeholders instead. We should fix our - // type resolution here, so we return fewer placeholders. - Some(make::ret_type(make::ty_placeholder())) - } else { - let ret_type = &ret_type.display(ctx.db()).to_string(); - Some(make::ret_type(make::ty(ret_type))) - }; + 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)) @@ -314,7 +305,7 @@ struct Person { } impl Person { - $0pub(crate) async fn age(&'a mut self, ty: T, arg: J) -> _ { + $0pub(crate) async fn age(&'a mut self, ty: T, arg: J) -> T { self.age.age(ty, arg) } }"#,