Repository: rust-analyzer/rust-analyzer
Pull request #8037 (2021-03-15 17:21:19)

Assist is empty 7709
Updated the implementation to get the function from implementation


Patch:
diff --git a/crates/hir_expand/src/name.rs b/crates/hir_expand/src/name.rs
index e833e032c0..43de9edd65 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
new file mode 100644
index 0000000000..aa7072f25c
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs
@@ -0,0 +1,255 @@
+use hir::{known, HasSource, Name};
+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.
+//
+// ```
+// struct MyStruct { data: Vec<String> }
+//
+// impl MyStruct {
+//     p$0ub fn len(&self) -> usize {
+//         self.data.len()
+//     }
+// }
+// ```
+// ->
+// ```
+// struct MyStruct { data: Vec<String> }
+//
+// 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::<ast::Fn>()?;
+    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().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_)?;
+
+    acc.add(
+        AssistId("generate_is_empty_from_len", AssistKind::Generate),
+        "Generate a is_empty impl from a len function",
+        range,
+        |builder| {
+            let code = r#"
+
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }"#
+            .to_string();
+            builder.insert(range.end(), code)
+        },
+    )
+}
+
+fn get_impl_method(
+    ctx: &AssistContext,
+    impl_: &ast::Impl,
+    fn_name: &Name,
+) -> Option<hir::Function> {
+    let db = ctx.sema.db;
+    let impl_def: hir::Impl = ctx.sema.to_def(impl_)?;
+
+    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_: &ast::Impl) -> Option<TextRange> {
+    let db = ctx.sema.db;
+    let func = get_impl_method(ctx, impl_, &known::len)?;
+    let node = func.source(db)?;
+    Some(node.syntax().value.text_range())
+}
+
+#[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#"
+struct MyStruct { data: Vec<String> }
+
+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#"
+struct MyStruct { data: Vec<String> }
+
+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#"
+struct MyStruct { data: Vec<String> }
+
+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#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+    p$0ub fn len(&self) -> usize {
+        self.data.len()
+    }
+}
+"#,
+            r#"
+struct MyStruct { data: Vec<String> }
+
+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#"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+    pub fn new() -> Self {
+        Self { data: 0 }
+    }
+
+    p$0ub fn len(&self) -> usize {
+        self.data.len()
+    }
+
+    pub fn work(&self) -> Option<usize> {
+
+    }
+}
+"#,
+            r#"
+struct MyStruct { data: Vec<String> }
+
+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<usize> {
+
+    }
+}
+"#,
+        );
+    }
+
+    #[test]
+    fn multiple_impls() {
+        check_assist_not_applicable(
+            generate_is_empty_from_len,
+            r#"
+struct MyStruct { data: Vec<String> }
+
+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/lib.rs b/crates/ide_assists/src/lib.rs
index f1aab74d47..8c068a6c0e 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_completion/src/completions/pattern.rs b/crates/ide_completion/src/completions/pattern.rs
index 46cef58f01..9282c38279 100644
--- a/crates/ide_completion/src/completions/pattern.rs
+++ b/crates/ide_completion/src/completions/pattern.rs
@@ -11,13 +11,10 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
         return;
     }
 
-    if !ctx.is_irrefutable_pat_binding {
-        if let Some(ty) = ctx.expected_type.as_ref() {
-            super::complete_enum_variants(acc, ctx, ty, |acc, ctx, variant, path| {
-                acc.add_qualified_variant_pat(ctx, variant, path.clone());
-                acc.add_qualified_enum_variant(ctx, variant, path);
-            });
-        }
+    if let Some(ty) = &ctx.expected_type {
+        super::complete_enum_variants(acc, ctx, ty, |acc, ctx, variant, path| {
+            acc.add_qualified_variant_pat(ctx, variant, path)
+        });
     }
 
     // FIXME: ideally, we should look at the type we are matching against and
@@ -88,7 +85,7 @@ static FOO: E = E::X;
 struct Bar { f: u32 }
 
 fn foo() {
-   match E::X { a$0 }
+   match E::X { $0 }
 }
 "#,
             expect![[r#"
@@ -109,11 +106,10 @@ macro_rules! m { ($e:expr) => { $e } }
 enum E { X }
 
 fn foo() {
-   m!(match E::X { a$0 })
+   m!(match E::X { $0 })
 }
 "#,
             expect![[r#"
-                ev E::X  ()
                 en E
                 ma m!(…) macro_rules! m
             "#]],
@@ -133,7 +129,7 @@ static FOO: E = E::X;
 struct Bar { f: u32 }
 
 fn foo() {
-   let a$0
+   let $0
 }
 "#,
             expect![[r#"
@@ -151,7 +147,7 @@ enum E { X }
 static FOO: E = E::X;
 struct Bar { f: u32 }
 
-fn foo(a$0) {
+fn foo($0) {
 }
 "#,
             expect![[r#"
@@ -167,7 +163,7 @@ fn foo(a$0) {
 struct Bar { f: u32 }
 
 fn foo() {
-   let a$0
+   let $0
 }
 "#,
             expect![[r#"
@@ -183,7 +179,7 @@ fn foo() {
 struct Foo { bar: String, baz: String }
 struct Bar(String, String);
 struct Baz;
-fn outer(a$0) {}
+fn outer($0) {}
 "#,
             expect![[r#"
                 bn Foo Foo { bar$1, baz$2 }: Foo$0
@@ -200,7 +196,7 @@ struct Foo { bar: String, baz: String }
 struct Bar(String, String);
 struct Baz;
 fn outer() {
-    let a$0
+    let $0
 }
 "#,
             expect![[r#"
@@ -219,7 +215,7 @@ struct Bar(String, String);
 struct Baz;
 fn outer() {
     match () {
-        a$0
+        $0
     }
 }
 "#,
@@ -243,7 +239,7 @@ use foo::*;
 
 fn outer() {
     match () {
-        a$0
+        $0
     }
 }
 "#,
@@ -262,7 +258,7 @@ fn outer() {
 struct Foo(i32);
 fn main() {
     match Foo(92) {
-        a$0(92) => (),
+        $0(92) => (),
     }
 }
 "#,
@@ -285,7 +281,7 @@ struct Foo(i32);
 impl Foo {
     fn foo() {
         match () {
-            a$0
+            $0
         }
     }
 }
@@ -318,86 +314,4 @@ impl Foo {
             "#]],
         )
     }
-
-    #[test]
-    fn completes_enum_variant_matcharm() {
-        check(
-            r#"
-enum Foo { Bar, Baz, Quux }
-
-fn main() {
-    let foo = Foo::Quux;
-    match foo { Qu$0 }
-}
-"#,
-            expect![[r#"
-                ev Foo::Bar  ()
-                ev Foo::Baz  ()
-                ev Foo::Quux ()
-                en Foo
-            "#]],
-        )
-    }
-
-    #[test]
-    fn completes_enum_variant_matcharm_ref() {
-        check(
-            r#"
-enum Foo { Bar, Baz, Quux }
-
-fn main() {
-    let foo = Foo::Quux;
-    match &foo { Qu$0 }
-}
-"#,
-            expect![[r#"
-                ev Foo::Bar  ()
-                ev Foo::Baz  ()
-                ev Foo::Quux ()
-                en Foo
-            "#]],
-        )
-    }
-
-    #[test]
-    fn completes_enum_variant_iflet() {
-        check(
-            r#"
-enum Foo { Bar, Baz, Quux }
-
-fn main() {
-    let foo = Foo::Quux;
-    if let Qu$0 = foo { }
-}
-"#,
-            expect![[r#"
-                ev Foo::Bar  ()
-                ev Foo::Baz  ()
-                ev Foo::Quux ()
-                en Foo
-            "#]],
-        )
-    }
-
-    #[test]
-    fn completes_enum_variant_impl() {
-        check(
-            r#"
-enum Foo { Bar, Baz, Quux }
-impl Foo {
-    fn foo() { match Foo::Bar { Q$0 } }
-}
-"#,
-            expect![[r#"
-                ev Self::Bar  ()
-                ev Self::Baz  ()
-                ev Self::Quux ()
-                ev Foo::Bar   ()
-                ev Foo::Baz   ()
-                ev Foo::Quux  ()
-                sp Self
-                en Foo
-            "#]],
-        )
-    }
 }
diff --git a/crates/ide_completion/src/completions/unqualified_path.rs b/crates/ide_completion/src/completions/unqualified_path.rs
index 5ef80f6a72..044dfd1602 100644
--- a/crates/ide_completion/src/completions/unqualified_path.rs
+++ b/crates/ide_completion/src/completions/unqualified_path.rs
@@ -6,7 +6,7 @@ use syntax::AstNode;
 use crate::{CompletionContext, Completions};
 
 pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
-    if !ctx.is_trivial_path {
+    if !(ctx.is_trivial_path || ctx.is_pat_binding_or_const) {
         return;
     }
     if ctx.record_lit_syntax.is_some()
@@ -23,6 +23,10 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
         });
     }
 
+    if ctx.is_pat_binding_or_const {
+        return;
+    }
+
     ctx.scope.process_all_names(&mut |name, res| {
         if let ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) = res {
             cov_mark::hit!(skip_lifetime_completion);
@@ -604,6 +608,66 @@ fn main() { $0 }
         );
     }
 
+    #[test]
+    fn completes_enum_variant_matcharm() {
+        check(
+            r#"
+enum Foo { Bar, Baz, Quux }
+
+fn main() {
+    let foo = Foo::Quux;
+    match foo { Qu$0 }
+}
+"#,
+            expect![[r#"
+                ev Foo::Bar  ()
+                ev Foo::Baz  ()
+                ev Foo::Quux ()
+                en Foo
+            "#]],
+        )
+    }
+
+    #[test]
+    fn completes_enum_variant_matcharm_ref() {
+        check(
+            r#"
+enum Foo { Bar, Baz, Quux }
+
+fn main() {
+    let foo = Foo::Quux;
+    match &foo { Qu$0 }
+}
+"#,
+            expect![[r#"
+                ev Foo::Bar  ()
+                ev Foo::Baz  ()
+                ev Foo::Quux ()
+                en Foo
+            "#]],
+        )
+    }
+
+    #[test]
+    fn completes_enum_variant_iflet() {
+        check(
+            r#"
+enum Foo { Bar, Baz, Quux }
+
+fn main() {
+    let foo = Foo::Quux;
+    if let Qu$0 = foo { }
+}
+"#,
+            expect![[r#"
+                ev Foo::Bar  ()
+                ev Foo::Baz  ()
+                ev Foo::Quux ()
+                en Foo
+            "#]],
+        )
+    }
+
     #[test]
     fn completes_enum_variant_basic_expr() {
         check(
@@ -636,6 +700,28 @@ fn f() -> m::E { V$0 }
         )
     }
 
+    #[test]
+    fn completes_enum_variant_impl() {
+        check(
+            r#"
+enum Foo { Bar, Baz, Quux }
+impl Foo {
+    fn foo() { match Foo::Bar { Q$0 } }
+}
+"#,
+            expect![[r#"
+                ev Self::Bar  ()
+                ev Self::Baz  ()
+                ev Self::Quux ()
+                ev Foo::Bar   ()
+                ev Foo::Baz   ()
+                ev Foo::Quux  ()
+                sp Self
+                en Foo
+            "#]],
+        )
+    }
+
     #[test]
     fn dont_complete_attr() {
         check(
diff --git a/crates/ide_completion/src/render/pattern.rs b/crates/ide_completion/src/render/pattern.rs
index b4e80f424a..ca29261254 100644
--- a/crates/ide_completion/src/render/pattern.rs
+++ b/crates/ide_completion/src/render/pattern.rs
@@ -6,6 +6,24 @@ use itertools::Itertools;
 
 use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
 
+fn visible_fields(
+    ctx: &RenderContext<'_>,
+    fields: &[hir::Field],
+    item: impl HasAttrs,
+) -> Option<(Vec<hir::Field>, bool)> {
+    let module = ctx.completion.scope.module()?;
+    let n_fields = fields.len();
+    let fields = fields
+        .into_iter()
+        .filter(|field| field.is_visible_from(ctx.db(), module))
+        .copied()
+        .collect::<Vec<_>>();
+
+    let fields_omitted =
+        n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
+    Some((fields, fields_omitted))
+}
+
 pub(crate) fn render_struct_pat(
     ctx: RenderContext<'_>,
     strukt: hir::Struct,
@@ -130,21 +148,3 @@ fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool)
         name = name
     )
 }
-
-fn visible_fields(
-    ctx: &RenderContext<'_>,
-    fields: &[hir::Field],
-    item: impl HasAttrs,
-) -> Option<(Vec<hir::Field>, bool)> {
-    let module = ctx.completion.scope.module()?;
-    let n_fields = fields.len();
-    let fields = fields
-        .into_iter()
-        .filter(|field| field.is_visible_from(ctx.db(), module))
-        .copied()
-        .collect::<Vec<_>>();
-
-    let fields_omitted =
-        n_fields - fields.len() > 0 || item.attrs(ctx.db()).by_key("non_exhaustive").exists();
-    Some((fields, fields_omitted))
-}


Test patch:
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 3f77edd8d6..736027ff00 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -721,6 +721,35 @@ impl<T: Clone> Ctx<T> {
     )
 }
 
+#[test]
+fn doctest_generate_is_empty_from_len() {
+    check_doc_test(
+        "generate_is_empty_from_len",
+        r#####"
+struct MyStruct { data: Vec<String> }
+
+impl MyStruct {
+    p$0ub fn len(&self) -> usize {
+        self.data.len()
+    }
+}
+"#####,
+        r#####"
+struct MyStruct { data: Vec<String> }
+
+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(