4065: Complete unqualified enum names in patterns and expressions r=matklad a=nathanwhit

This PR implements the completion described in #4014.
The result looks like so for patterns:
<img width="542" alt="Screen Shot 2020-04-20 at 3 53 55 PM" src="https://user-images.githubusercontent.com/17734409/79794010-8f529400-831f-11ea-9673-f838aa9bc962.png">

and for `expr`s:
<img width="620" alt="Screen Shot 2020-04-21 at 3 51 24 PM" src="https://user-images.githubusercontent.com/17734409/79908784-d73ded80-83e9-11ea-991d-921f0cb27e6f.png">


I'm not confident that the completion text itself is very robust, as it will unconditionally add completions for enum variants with the form `Enum::Variant`. This means (I believe) it would still suggest `Enum::Variant` even if the local name is changed i.e. `use Enum as Foo` or the variants are brought into scope such as through `use Enum::*`.

Co-authored-by: nathanwhit <nathan.whitaker01@gmail.com>
This commit is contained in:
bors[bot] 2020-04-23 15:35:31 +00:00 committed by GitHub
commit 278bf351e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 217 additions and 7 deletions

View file

@ -4,20 +4,23 @@ use hir::ScopeDef;
use test_utils::tested_by;
use crate::completion::{CompletionContext, Completions};
use hir::{Adt, ModuleDef};
use ra_syntax::AstNode;
pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
if !ctx.is_trivial_path {
return;
}
if ctx.is_pat_binding_or_const
if (!ctx.is_trivial_path && !ctx.is_pat_binding_or_const)
|| ctx.record_lit_syntax.is_some()
|| ctx.record_pat_syntax.is_some()
{
return;
}
complete_enum_variants(acc, ctx);
if ctx.is_pat_binding_or_const {
return;
}
ctx.scope().process_all_names(&mut |name, res| {
if ctx.use_item_syntax.is_some() {
if let (ScopeDef::Unknown, Some(name_ref)) = (&res, &ctx.name_ref_syntax) {
@ -31,6 +34,24 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
});
}
fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext) {
if let Some(ty) = ctx.expected_type_of(&ctx.token.parent()) {
if let Some(Adt::Enum(enum_data)) = ty.as_adt() {
let variants = enum_data.variants(ctx.db);
let module = enum_data.module(ctx.db);
for variant in variants {
if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
// Variants with trivial paths are already added by the existing completion logic,
// so we should avoid adding these twice
if path.segments.len() > 1 {
acc.add_enum_variant(ctx, variant, Some(path.to_string()));
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot;
@ -82,7 +103,7 @@ mod tests {
}
"
),
@r###"[]"###
@"[]"
);
}
@ -1109,4 +1130,182 @@ mod tests {
"###
);
}
#[test]
fn completes_enum_variant_matcharm() {
assert_debug_snapshot!(
do_reference_completion(
r"
enum Foo {
Bar,
Baz,
Quux
}
fn main() {
let foo = Foo::Quux;
match foo {
Qu<|>
}
}
"
),
@r###"
[
CompletionItem {
label: "Foo",
source_range: [248; 250),
delete: [248; 250),
insert: "Foo",
kind: Enum,
},
CompletionItem {
label: "Foo::Bar",
source_range: [248; 250),
delete: [248; 250),
insert: "Foo::Bar",
kind: EnumVariant,
detail: "()",
},
CompletionItem {
label: "Foo::Baz",
source_range: [248; 250),
delete: [248; 250),
insert: "Foo::Baz",
kind: EnumVariant,
detail: "()",
},
CompletionItem {
label: "Foo::Quux",
source_range: [248; 250),
delete: [248; 250),
insert: "Foo::Quux",
kind: EnumVariant,
detail: "()",
},
]
"###
)
}
#[test]
fn completes_enum_variant_iflet() {
assert_debug_snapshot!(
do_reference_completion(
r"
enum Foo {
Bar,
Baz,
Quux
}
fn main() {
let foo = Foo::Quux;
if let Qu<|> = foo {
}
}
"
),
@r###"
[
CompletionItem {
label: "Foo",
source_range: [219; 221),
delete: [219; 221),
insert: "Foo",
kind: Enum,
},
CompletionItem {
label: "Foo::Bar",
source_range: [219; 221),
delete: [219; 221),
insert: "Foo::Bar",
kind: EnumVariant,
detail: "()",
},
CompletionItem {
label: "Foo::Baz",
source_range: [219; 221),
delete: [219; 221),
insert: "Foo::Baz",
kind: EnumVariant,
detail: "()",
},
CompletionItem {
label: "Foo::Quux",
source_range: [219; 221),
delete: [219; 221),
insert: "Foo::Quux",
kind: EnumVariant,
detail: "()",
},
]
"###
)
}
#[test]
fn completes_enum_variant_basic_expr() {
assert_debug_snapshot!(
do_reference_completion(
r"
enum Foo {
Bar,
Baz,
Quux
}
fn main() {
let foo: Foo = Q<|>
}
"
),
@r###"
[
CompletionItem {
label: "Foo",
source_range: [185; 186),
delete: [185; 186),
insert: "Foo",
kind: Enum,
},
CompletionItem {
label: "Foo::Bar",
source_range: [185; 186),
delete: [185; 186),
insert: "Foo::Bar",
kind: EnumVariant,
detail: "()",
},
CompletionItem {
label: "Foo::Baz",
source_range: [185; 186),
delete: [185; 186),
insert: "Foo::Baz",
kind: EnumVariant,
detail: "()",
},
CompletionItem {
label: "Foo::Quux",
source_range: [185; 186),
delete: [185; 186),
insert: "Foo::Quux",
kind: EnumVariant,
detail: "()",
},
CompletionItem {
label: "main()",
source_range: [185; 186),
delete: [185; 186),
insert: "main()$0",
kind: Function,
lookup: "main",
detail: "fn main()",
},
]
"###
)
}
}

View file

@ -1,6 +1,6 @@
//! FIXME: write short doc here
use hir::{Semantics, SemanticsScope};
use hir::{Semantics, SemanticsScope, Type};
use ra_db::SourceDatabase;
use ra_ide_db::RootDatabase;
use ra_syntax::{
@ -168,6 +168,17 @@ impl<'a> CompletionContext<'a> {
self.sema.scope_at_offset(&self.token.parent(), self.offset)
}
pub(crate) fn expected_type_of(&self, node: &SyntaxNode) -> Option<Type> {
for ancestor in node.ancestors() {
if let Some(pat) = ast::Pat::cast(ancestor.clone()) {
return self.sema.type_of_pat(&pat);
} else if let Some(expr) = ast::Expr::cast(ancestor) {
return self.sema.type_of_expr(&expr);
}
}
None
}
fn fill(
&mut self,
original_file: &SyntaxNode,