Show qualified variant pattern completions

This commit is contained in:
Lukas Wirth 2021-02-09 21:32:05 +01:00
parent 5d4ae1c8e3
commit 5454559c0a
4 changed files with 95 additions and 45 deletions

View file

@ -15,7 +15,9 @@ pub(crate) mod trait_impl;
pub(crate) mod mod_;
pub(crate) mod flyimport;
use hir::{ModPath, ScopeDef, Type};
use std::iter;
use hir::{known, ModPath, ScopeDef, Type};
use crate::{
item::Builder,
@ -118,7 +120,18 @@ impl Completions {
variant: hir::Variant,
local_name: Option<hir::Name>,
) {
if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, local_name) {
if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, local_name, None) {
self.add(item);
}
}
pub(crate) fn add_qualified_variant_pat(
&mut self,
ctx: &CompletionContext,
variant: hir::Variant,
path: ModPath,
) {
if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, None, Some(path)) {
self.add(item);
}
}
@ -166,3 +179,46 @@ impl Completions {
self.add(item);
}
}
fn complete_enum_variants(
acc: &mut Completions,
ctx: &CompletionContext,
ty: &hir::Type,
cb: impl Fn(&mut Completions, &CompletionContext, hir::Variant, hir::ModPath),
) {
if let Some(hir::Adt::Enum(enum_data)) =
iter::successors(Some(ty.clone()), |ty| ty.remove_ref()).last().and_then(|ty| ty.as_adt())
{
let variants = enum_data.variants(ctx.db);
let module = if let Some(module) = ctx.scope.module() {
// Compute path from the completion site if available.
module
} else {
// Otherwise fall back to the enum's definition site.
enum_data.module(ctx.db)
};
if let Some(impl_) = ctx.impl_def.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) {
if impl_.target_ty(ctx.db) == *ty {
for &variant in &variants {
let self_path = hir::ModPath::from_segments(
hir::PathKind::Plain,
iter::once(known::SELF_TYPE).chain(iter::once(variant.name(ctx.db))),
);
cb(acc, ctx, variant, self_path);
}
}
}
for variant in variants {
if let Some(path) = module.find_use_path(ctx.db, hir::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 {
cb(acc, ctx, variant, path);
}
}
}
}
}

View file

@ -11,6 +11,12 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
return;
}
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
// suggest variants + auto-imports
ctx.scope.process_all_names(&mut |name, res| {
@ -286,4 +292,26 @@ impl Foo {
"#]],
)
}
#[test]
fn completes_qualified_variant() {
check_snippet(
r#"
enum Foo {
Bar { baz: i32 }
}
impl Foo {
fn foo() {
match {Foo::Bar { baz: 0 }} {
B$0
}
}
}
"#,
expect![[r#"
bn Self::Bar Self::Bar { baz$1 }$0
bn Foo::Bar Foo::Bar { baz$1 }$0
"#]],
)
}
}

View file

@ -1,8 +1,6 @@
//! Completion of names from the current scope, e.g. locals and imported items.
use std::iter;
use hir::{known, Adt, ModuleDef, ScopeDef, Type};
use hir::ScopeDef;
use syntax::AstNode;
use test_utils::mark;
@ -21,7 +19,9 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
}
if let Some(ty) = &ctx.expected_type {
complete_enum_variants(acc, ctx, ty);
super::complete_enum_variants(acc, ctx, ty, |acc, ctx, variant, path| {
acc.add_qualified_enum_variant(ctx, variant, path)
});
}
if ctx.is_pat_binding_or_const {
@ -45,44 +45,6 @@ pub(crate) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
});
}
fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
if let Some(Adt::Enum(enum_data)) =
iter::successors(Some(ty.clone()), |ty| ty.remove_ref()).last().and_then(|ty| ty.as_adt())
{
let variants = enum_data.variants(ctx.db);
let module = if let Some(module) = ctx.scope.module() {
// Compute path from the completion site if available.
module
} else {
// Otherwise fall back to the enum's definition site.
enum_data.module(ctx.db)
};
if let Some(impl_) = ctx.impl_def.as_ref().and_then(|impl_| ctx.sema.to_def(impl_)) {
if impl_.target_ty(ctx.db) == *ty {
for &variant in &variants {
let self_path = hir::ModPath::from_segments(
hir::PathKind::Plain,
iter::once(known::SELF_TYPE).chain(iter::once(variant.name(ctx.db))),
);
acc.add_qualified_enum_variant(ctx, variant, self_path.clone());
}
}
}
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_qualified_enum_variant(ctx, variant, path);
}
}
}
}
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};

View file

@ -49,13 +49,17 @@ pub(crate) fn render_variant_pat(
ctx: RenderContext<'_>,
variant: hir::Variant,
local_name: Option<Name>,
path: Option<hir::ModPath>,
) -> Option<CompletionItem> {
let _p = profile::span("render_variant_pat");
let fields = variant.fields(ctx.db());
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, variant)?;
let name = local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string();
let name = match &path {
Some(path) => path.to_string(),
None => local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string(),
};
let pat = render_pat(&ctx, &name, variant.kind(ctx.db()), &visible_fields, fields_omitted)?;
Some(build_completion(ctx, name, pat, variant))