From 2bf3802f21de48b1c284cdd5e0e9f7f168787d9b Mon Sep 17 00:00:00 2001 From: Chetan Khilosiya Date: Fri, 12 Mar 2021 00:25:22 +0530 Subject: 7709: Added the assist to generate is_empty function the assist will be shown when the len function is implemented. is_empty internally uses len function. --- .../src/handlers/generate_is_empty_from_len.rs | 225 +++++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + crates/ide_assists/src/tests/generated.rs | 25 +++ 3 files changed, 252 insertions(+) create mode 100644 crates/ide_assists/src/handlers/generate_is_empty_from_len.rs (limited to 'crates') diff --git a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs new file mode 100644 index 000000000..bd29dddb3 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs @@ -0,0 +1,225 @@ +use hir::{AssocItem, HasSource, Impl}; +use syntax::{ + ast::{self, NameOwner}, + AstNode, TextRange, +}; + +use crate::{ + assist_context::{AssistContext, Assists}, + AssistId, AssistKind, +}; + +// Assist: generate_is_empty_from_len +// +// Generates is_empty implementation from the len method. +// +// ``` +// impl MyStruct { +// p$0ub fn len(&self) -> usize { +// self.data.len() +// } +// } +// ``` +// -> +// ``` +// impl MyStruct { +// pub fn len(&self) -> usize { +// self.data.len() +// } +// +// pub fn is_empty(&self) -> bool { +// self.len() == 0 +// } +// } +// ``` +pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let fn_node = ctx.find_node_at_offset::()?; + let fn_name = fn_node.name()?; + + if fn_name.text() != "len" { + cov_mark::hit!(len_function_not_present); + return None; + } + + if fn_node.param_list()?.params().next().is_some() { + cov_mark::hit!(len_function_with_parameters); + return None; + } + + let impl_ = fn_node.syntax().ancestors().into_iter().find_map(ast::Impl::cast)?; + let impl_def = ctx.sema.to_def(&impl_)?; + if is_empty_implemented(ctx, &impl_def) { + cov_mark::hit!(is_empty_already_implemented); + return None; + } + + let range = get_text_range_of_len_function(ctx, &impl_def)?; + + acc.add( + AssistId("generate_is_empty_from_len", AssistKind::Generate), + "Generate a is_empty impl from a len function", + range, + |builder| { + let code = get_is_empty_code(); + builder.insert(range.end(), code) + }, + ) +} + +fn get_function_from_impl(ctx: &AssistContext, impl_def: &Impl, name: &str) -> Option { + let db = ctx.sema.db; + impl_def.items(db).into_iter().filter(|item| matches!(item, AssocItem::Function(_value))).find( + |func| match func.name(db) { + Some(fn_name) => fn_name.to_string() == name, + None => false, + }, + ) +} + +fn is_empty_implemented(ctx: &AssistContext, impl_def: &Impl) -> bool { + get_function_from_impl(ctx, impl_def, "is_empty").is_some() +} + +fn get_text_range_of_len_function(ctx: &AssistContext, impl_def: &Impl) -> Option { + let db = ctx.sema.db; + let len_fn = get_function_from_impl(ctx, impl_def, "len")?; + + let mut range = None; + if let AssocItem::Function(node) = len_fn { + let node = node.source(db)?; + range = Some(node.syntax().value.text_range()); + } + + range +} + +fn get_is_empty_code() -> String { + r#" + + pub fn is_empty(&self) -> bool { + self.len() == 0 + }"# + .to_string() +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn len_function_not_present() { + cov_mark::check!(len_function_not_present); + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +impl MyStruct { + p$0ub fn test(&self) -> usize { + self.data.len() + } + } +"#, + ); + } + + #[test] + fn len_function_with_parameters() { + cov_mark::check!(len_function_with_parameters); + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +impl MyStruct { + p$0ub fn len(&self, _i: bool) -> usize { + self.data.len() + } +} +"#, + ); + } + + #[test] + fn is_empty_already_implemented() { + cov_mark::check!(is_empty_already_implemented); + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +impl MyStruct { + p$0ub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} +"#, + ); + } + + #[test] + fn generate_is_empty() { + check_assist( + generate_is_empty_from_len, + r#" +impl MyStruct { + p$0ub fn len(&self) -> usize { + self.data.len() + } +} +"#, + r#" +impl MyStruct { + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} +"#, + ); + } + + #[test] + fn multiple_functions_in_impl() { + check_assist( + generate_is_empty_from_len, + r#" +impl MyStruct { + pub fn new() -> Self { + Self { data: 0 } + } + + p$0ub fn len(&self) -> usize { + self.data.len() + } + + pub fn work(&self) -> Option { + // do some work + } +} +"#, + r#" +impl MyStruct { + pub fn new() -> Self { + Self { data: 0 } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn work(&self) -> Option { + // do some work + } +} +"#, + ); + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index f1aab74d4..8c068a6c0 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -129,6 +129,7 @@ mod handlers { mod flip_trait_bound; mod generate_default_from_enum_variant; mod generate_default_from_new; + mod generate_is_empty_from_len; mod generate_derive; mod generate_enum_is_method; mod generate_enum_projection_method; @@ -193,6 +194,7 @@ mod handlers { flip_trait_bound::flip_trait_bound, generate_default_from_enum_variant::generate_default_from_enum_variant, generate_default_from_new::generate_default_from_new, + generate_is_empty_from_len::generate_is_empty_from_len, generate_derive::generate_derive, generate_enum_is_method::generate_enum_is_method, generate_enum_projection_method::generate_enum_as_method, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 3f77edd8d..66fbcc968 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -721,6 +721,31 @@ impl Ctx { ) } +#[test] +fn doctest_generate_is_empty_from_len() { + check_doc_test( + "generate_is_empty_from_len", + r#####" +impl MyStruct { + p$0ub fn len(&self) -> usize { + self.data.len() + } +} +"#####, + r#####" +impl MyStruct { + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} +"#####, + ) +} + #[test] fn doctest_generate_new() { check_doc_test( -- cgit v1.2.3 From 0c2d4a8a7758793765d33790e22c70b79b8bea56 Mon Sep 17 00:00:00 2001 From: Chetan Khilosiya Date: Mon, 15 Mar 2021 22:48:50 +0530 Subject: 7709: Updated the implementation. The get function from impl method is updated. and now same method used to get len and is_empty function. --- crates/hir_expand/src/name.rs | 2 + .../src/handlers/generate_is_empty_from_len.rs | 104 +++++++++++++-------- crates/ide_assists/src/tests/generated.rs | 4 + 3 files changed, 73 insertions(+), 37 deletions(-) (limited to 'crates') diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs index e833e032c..43de9edd6 100644 --- a/crates/hir_expand/src/name.rs +++ b/crates/hir_expand/src/name.rs @@ -191,6 +191,8 @@ pub mod known { filter_map, next, iter_mut, + len, + is_empty, // Builtin macros file, column, diff --git a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs index bd29dddb3..aa7072f25 100644 --- a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs +++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs @@ -1,4 +1,4 @@ -use hir::{AssocItem, HasSource, Impl}; +use hir::{known, HasSource, Name}; use syntax::{ ast::{self, NameOwner}, AstNode, TextRange, @@ -14,6 +14,8 @@ use crate::{ // Generates is_empty implementation from the len method. // // ``` +// struct MyStruct { data: Vec } +// // impl MyStruct { // p$0ub fn len(&self) -> usize { // self.data.len() @@ -22,6 +24,8 @@ use crate::{ // ``` // -> // ``` +// struct MyStruct { data: Vec } +// // impl MyStruct { // pub fn len(&self) -> usize { // self.data.len() @@ -46,60 +50,50 @@ pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext) return None; } - let impl_ = fn_node.syntax().ancestors().into_iter().find_map(ast::Impl::cast)?; - let impl_def = ctx.sema.to_def(&impl_)?; - if is_empty_implemented(ctx, &impl_def) { + let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?; + if get_impl_method(ctx, &impl_, &known::is_empty).is_some() { cov_mark::hit!(is_empty_already_implemented); return None; } - let range = get_text_range_of_len_function(ctx, &impl_def)?; + let range = get_text_range_of_len_function(ctx, &impl_)?; acc.add( AssistId("generate_is_empty_from_len", AssistKind::Generate), "Generate a is_empty impl from a len function", range, |builder| { - let code = get_is_empty_code(); + let code = r#" + + pub fn is_empty(&self) -> bool { + self.len() == 0 + }"# + .to_string(); builder.insert(range.end(), code) }, ) } -fn get_function_from_impl(ctx: &AssistContext, impl_def: &Impl, name: &str) -> Option { +fn get_impl_method( + ctx: &AssistContext, + impl_: &ast::Impl, + fn_name: &Name, +) -> Option { let db = ctx.sema.db; - impl_def.items(db).into_iter().filter(|item| matches!(item, AssocItem::Function(_value))).find( - |func| match func.name(db) { - Some(fn_name) => fn_name.to_string() == name, - None => false, - }, - ) -} + let impl_def: hir::Impl = ctx.sema.to_def(impl_)?; -fn is_empty_implemented(ctx: &AssistContext, impl_def: &Impl) -> bool { - get_function_from_impl(ctx, impl_def, "is_empty").is_some() + let scope = ctx.sema.scope(impl_.syntax()); + let krate = impl_def.module(db).krate(); + let ty = impl_def.target_ty(db); + let traits_in_scope = scope.traits_in_scope(); + ty.iterate_method_candidates(db, krate, &traits_in_scope, Some(fn_name), |_, func| Some(func)) } -fn get_text_range_of_len_function(ctx: &AssistContext, impl_def: &Impl) -> Option { +fn get_text_range_of_len_function(ctx: &AssistContext, impl_: &ast::Impl) -> Option { let db = ctx.sema.db; - let len_fn = get_function_from_impl(ctx, impl_def, "len")?; - - let mut range = None; - if let AssocItem::Function(node) = len_fn { - let node = node.source(db)?; - range = Some(node.syntax().value.text_range()); - } - - range -} - -fn get_is_empty_code() -> String { - r#" - - pub fn is_empty(&self) -> bool { - self.len() == 0 - }"# - .to_string() + let func = get_impl_method(ctx, impl_, &known::len)?; + let node = func.source(db)?; + Some(node.syntax().value.text_range()) } #[cfg(test)] @@ -114,6 +108,8 @@ mod tests { check_assist_not_applicable( generate_is_empty_from_len, r#" +struct MyStruct { data: Vec } + impl MyStruct { p$0ub fn test(&self) -> usize { self.data.len() @@ -129,6 +125,8 @@ impl MyStruct { check_assist_not_applicable( generate_is_empty_from_len, r#" +struct MyStruct { data: Vec } + impl MyStruct { p$0ub fn len(&self, _i: bool) -> usize { self.data.len() @@ -144,6 +142,8 @@ impl MyStruct { check_assist_not_applicable( generate_is_empty_from_len, r#" +struct MyStruct { data: Vec } + impl MyStruct { p$0ub fn len(&self) -> usize { self.data.len() @@ -162,6 +162,8 @@ impl MyStruct { check_assist( generate_is_empty_from_len, r#" +struct MyStruct { data: Vec } + impl MyStruct { p$0ub fn len(&self) -> usize { self.data.len() @@ -169,6 +171,8 @@ impl MyStruct { } "#, r#" +struct MyStruct { data: Vec } + impl MyStruct { pub fn len(&self) -> usize { self.data.len() @@ -187,6 +191,8 @@ impl MyStruct { check_assist( generate_is_empty_from_len, r#" +struct MyStruct { data: Vec } + impl MyStruct { pub fn new() -> Self { Self { data: 0 } @@ -197,11 +203,13 @@ impl MyStruct { } pub fn work(&self) -> Option { - // do some work + } } "#, r#" +struct MyStruct { data: Vec } + impl MyStruct { pub fn new() -> Self { Self { data: 0 } @@ -216,7 +224,29 @@ impl MyStruct { } pub fn work(&self) -> Option { - // do some work + + } +} +"#, + ); + } + + #[test] + fn multiple_impls() { + check_assist_not_applicable( + generate_is_empty_from_len, + r#" +struct MyStruct { data: Vec } + +impl MyStruct { + p$0ub fn len(&self) -> usize { + self.data.len() + } +} + +impl MyStruct { + pub fn is_empty(&self) -> bool { + self.len() == 0 } } "#, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 66fbcc968..736027ff0 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -726,6 +726,8 @@ fn doctest_generate_is_empty_from_len() { check_doc_test( "generate_is_empty_from_len", r#####" +struct MyStruct { data: Vec } + impl MyStruct { p$0ub fn len(&self) -> usize { self.data.len() @@ -733,6 +735,8 @@ impl MyStruct { } "#####, r#####" +struct MyStruct { data: Vec } + impl MyStruct { pub fn len(&self) -> usize { self.data.len() -- cgit v1.2.3