From 0ff89deb6973be35848380be28c4a23063af8768 Mon Sep 17 00:00:00 2001 From: Ryan Levick Date: Wed, 13 Oct 2021 15:08:40 +0200 Subject: [PATCH] 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)) }