6964: Add full pattern completions for Struct and Variant patterns r=matklad a=Veykril


Just gonna call it full pattern completion as pattern completion is already implemented in a sense by showing idents in pattern position. What this does is basically complete struct and variant patterns where applicable(function params, let statements and refutable pattern locations).

This does not replace just completing the corresponding idents of the structs and variants, instead two completions are shown for these, a completion for the ident itself and a completion for the pattern(if the pattern make sense to be used that is). I figured in some cases one would rather type out the pattern manually if it has a lot of fields but you only care about one since this completion would cause one more work in the end since you would have to delete all the extra matched fields again.

These completions are tagged as `CompletionKind::Snippet`, not sure if that is the right one here.
<details>
  <summary>some gifs</summary>

![dx2lxgzhj3](https://user-images.githubusercontent.com/3757771/102719967-6987ef80-42f1-11eb-8ae0-8aff53777860.gif)
![EP2E7sJLkB](https://user-images.githubusercontent.com/3757771/102785777-c7264580-439e-11eb-8a64-f142e19fb65b.gif)
![JMNHHWknr9](https://user-images.githubusercontent.com/3757771/102785796-d1e0da80-439e-11eb-934b-218ada31b51c.gif)
</details>

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
bors[bot] 2020-12-22 18:03:51 +00:00 committed by GitHub
commit 4a2f60cb7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 363 additions and 74 deletions

View file

@ -19,9 +19,14 @@ use hir::{ModPath, ScopeDef, Type};
use crate::{ use crate::{
item::Builder, item::Builder,
render::{ render::{
const_::render_const, enum_variant::render_variant, function::render_fn, const_::render_const,
macro_::render_macro, render_field, render_resolution, render_tuple_field, enum_variant::render_variant,
type_alias::render_type_alias, RenderContext, function::render_fn,
macro_::render_macro,
pattern::{render_struct_pat, render_variant_pat},
render_field, render_resolution, render_tuple_field,
type_alias::render_type_alias,
RenderContext,
}, },
CompletionContext, CompletionItem, CompletionContext, CompletionItem,
}; };
@ -105,6 +110,28 @@ impl Completions {
self.add(item) self.add(item)
} }
pub(crate) fn add_variant_pat(
&mut self,
ctx: &CompletionContext,
variant: hir::Variant,
local_name: Option<hir::Name>,
) {
if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, local_name) {
self.add(item);
}
}
pub(crate) fn add_struct_pat(
&mut self,
ctx: &CompletionContext,
strukt: hir::Struct,
local_name: Option<hir::Name>,
) {
if let Some(item) = render_struct_pat(RenderContext::new(ctx), strukt, local_name) {
self.add(item);
}
}
pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
if let Some(item) = render_const(RenderContext::new(ctx), constant) { if let Some(item) = render_const(RenderContext::new(ctx), constant) {
self.add(item); self.add(item);

View file

@ -2,9 +2,9 @@
use crate::{CompletionContext, Completions}; use crate::{CompletionContext, Completions};
/// Completes constats and paths in patterns. /// Completes constants and paths in patterns.
pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_let_pat_binding) { if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_pat_binding) {
return; return;
} }
if ctx.record_pat_syntax.is_some() { if ctx.record_pat_syntax.is_some() {
@ -15,20 +15,21 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
// suggest variants + auto-imports // suggest variants + auto-imports
ctx.scope.process_all_names(&mut |name, res| { ctx.scope.process_all_names(&mut |name, res| {
let add_resolution = match &res { let add_resolution = match &res {
hir::ScopeDef::ModuleDef(def) => { hir::ScopeDef::ModuleDef(def) => match def {
if ctx.is_irrefutable_let_pat_binding { hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
matches!(def, hir::ModuleDef::Adt(hir::Adt::Struct(_))) acc.add_struct_pat(ctx, strukt.clone(), Some(name.clone()));
} else { true
matches!( }
def, hir::ModuleDef::Variant(variant) if !ctx.is_irrefutable_pat_binding => {
acc.add_variant_pat(ctx, variant.clone(), Some(name.clone()));
true
}
hir::ModuleDef::Adt(hir::Adt::Enum(..)) hir::ModuleDef::Adt(hir::Adt::Enum(..))
| hir::ModuleDef::Adt(hir::Adt::Struct(..))
| hir::ModuleDef::Variant(..) | hir::ModuleDef::Variant(..)
| hir::ModuleDef::Const(..) | hir::ModuleDef::Const(..)
| hir::ModuleDef::Module(..) | hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding,
) _ => false,
} },
}
hir::ScopeDef::MacroDef(_) => true, hir::ScopeDef::MacroDef(_) => true,
_ => false, _ => false,
}; };
@ -42,13 +43,21 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
mod tests { mod tests {
use expect_test::{expect, Expect}; use expect_test::{expect, Expect};
use crate::{test_utils::completion_list, CompletionKind}; use crate::{
test_utils::{check_edit, completion_list},
CompletionKind,
};
fn check(ra_fixture: &str, expect: Expect) { fn check(ra_fixture: &str, expect: Expect) {
let actual = completion_list(ra_fixture, CompletionKind::Reference); let actual = completion_list(ra_fixture, CompletionKind::Reference);
expect.assert_eq(&actual) expect.assert_eq(&actual)
} }
fn check_snippet(ra_fixture: &str, expect: Expect) {
let actual = completion_list(ra_fixture, CompletionKind::Snippet);
expect.assert_eq(&actual)
}
#[test] #[test]
fn completes_enum_variants_and_modules() { fn completes_enum_variants_and_modules() {
check( check(
@ -69,7 +78,7 @@ fn foo() {
en E en E
ct Z ct Z
st Bar st Bar
ev X () ev X
md m md m
"#]], "#]],
); );
@ -114,4 +123,139 @@ fn foo() {
"#]], "#]],
); );
} }
#[test]
fn completes_in_param() {
check(
r#"
enum E { X }
static FOO: E = E::X;
struct Bar { f: u32 }
fn foo(<|>) {
}
"#,
expect![[r#"
st Bar
"#]],
);
}
#[test]
fn completes_pat_in_let() {
check_snippet(
r#"
struct Bar { f: u32 }
fn foo() {
let <|>
}
"#,
expect![[r#"
bn Bar Bar { f$1 }$0
"#]],
);
}
#[test]
fn completes_param_pattern() {
check_snippet(
r#"
struct Foo { bar: String, baz: String }
struct Bar(String, String);
struct Baz;
fn outer(<|>) {}
"#,
expect![[r#"
bn Foo Foo { bar$1, baz$2 }: Foo$0
bn Bar Bar($1, $2): Bar$0
"#]],
)
}
#[test]
fn completes_let_pattern() {
check_snippet(
r#"
struct Foo { bar: String, baz: String }
struct Bar(String, String);
struct Baz;
fn outer() {
let <|>
}
"#,
expect![[r#"
bn Foo Foo { bar$1, baz$2 }$0
bn Bar Bar($1, $2)$0
"#]],
)
}
#[test]
fn completes_refutable_pattern() {
check_snippet(
r#"
struct Foo { bar: i32, baz: i32 }
struct Bar(String, String);
struct Baz;
fn outer() {
match () {
<|>
}
}
"#,
expect![[r#"
bn Foo Foo { bar$1, baz$2 }$0
bn Bar Bar($1, $2)$0
"#]],
)
}
#[test]
fn omits_private_fields_pat() {
check_snippet(
r#"
mod foo {
pub struct Foo { pub bar: i32, baz: i32 }
pub struct Bar(pub String, String);
pub struct Invisible(String, String);
}
use foo::*;
fn outer() {
match () {
<|>
}
}
"#,
expect![[r#"
bn Foo Foo { bar$1, .. }$0
bn Bar Bar($1, ..)$0
"#]],
)
}
#[test]
fn only_shows_ident_completion() {
check_edit(
"Foo",
r#"
struct Foo(i32);
fn main() {
match Foo(92) {
<|>(92) => (),
}
}
"#,
r#"
struct Foo(i32);
fn main() {
match Foo(92) {
Foo(92) => (),
}
}
"#,
);
}
} }

View file

@ -51,7 +51,7 @@ pub(crate) struct CompletionContext<'a> {
/// If a name-binding or reference to a const in a pattern. /// If a name-binding or reference to a const in a pattern.
/// Irrefutable patterns (like let) are excluded. /// Irrefutable patterns (like let) are excluded.
pub(super) is_pat_binding_or_const: bool, pub(super) is_pat_binding_or_const: bool,
pub(super) is_irrefutable_let_pat_binding: bool, pub(super) is_irrefutable_pat_binding: bool,
/// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
pub(super) is_trivial_path: bool, pub(super) is_trivial_path: bool,
/// If not a trivial path, the prefix (qualifier). /// If not a trivial path, the prefix (qualifier).
@ -147,7 +147,7 @@ impl<'a> CompletionContext<'a> {
active_parameter: ActiveParameter::at(db, position), active_parameter: ActiveParameter::at(db, position),
is_param: false, is_param: false,
is_pat_binding_or_const: false, is_pat_binding_or_const: false,
is_irrefutable_let_pat_binding: false, is_irrefutable_pat_binding: false,
is_trivial_path: false, is_trivial_path: false,
path_qual: None, path_qual: None,
after_if: false, after_if: false,
@ -327,14 +327,19 @@ impl<'a> CompletionContext<'a> {
if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
self.is_pat_binding_or_const = false; self.is_pat_binding_or_const = false;
} }
if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) { if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| {
if let Some(pat) = let_stmt.pat() { match_ast! {
if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) match node {
{ ast::LetStmt(it) => Some(it.pat()),
self.is_pat_binding_or_const = false; ast::Param(it) => Some(it.pat()),
self.is_irrefutable_let_pat_binding = true; _ => None,
} }
} }
}) {
if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) {
self.is_pat_binding_or_const = false;
self.is_irrefutable_pat_binding = true;
}
} }
} }
if is_node::<ast::Param>(name.syntax()) { if is_node::<ast::Param>(name.syntax()) {

View file

@ -5,6 +5,7 @@ pub(crate) mod macro_;
pub(crate) mod function; pub(crate) mod function;
pub(crate) mod enum_variant; pub(crate) mod enum_variant;
pub(crate) mod const_; pub(crate) mod const_;
pub(crate) mod pattern;
pub(crate) mod type_alias; pub(crate) mod type_alias;
mod builder_ext; mod builder_ext;
@ -159,6 +160,12 @@ impl<'a> Render<'a> {
let item = render_fn(self.ctx, import_to_add, Some(local_name), *func); let item = render_fn(self.ctx, import_to_add, Some(local_name), *func);
return Some(item); return Some(item);
} }
ScopeDef::ModuleDef(Variant(_))
if self.ctx.completion.is_pat_binding_or_const
| self.ctx.completion.is_irrefutable_pat_binding =>
{
CompletionItemKind::EnumVariant
}
ScopeDef::ModuleDef(Variant(var)) => { ScopeDef::ModuleDef(Variant(var)) => {
let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None); let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None);
return Some(item); return Some(item);

View file

@ -34,7 +34,6 @@ impl Builder {
return false; return false;
} }
if ctx.is_pattern_call { if ctx.is_pattern_call {
mark::hit!(dont_duplicate_pattern_parens);
return false; return false;
} }
if ctx.is_call { if ctx.is_call {

View file

@ -124,51 +124,6 @@ use Option::*;
fn main() -> Option<i32> { fn main() -> Option<i32> {
Some($0) Some($0)
} }
"#,
);
check_edit(
"Some",
r#"
enum Option<T> { Some(T), None }
use Option::*;
fn main(value: Option<i32>) {
match value {
Som<|>
}
}
"#,
r#"
enum Option<T> { Some(T), None }
use Option::*;
fn main(value: Option<i32>) {
match value {
Some($0)
}
}
"#,
);
}
#[test]
fn dont_duplicate_pattern_parens() {
mark::check!(dont_duplicate_pattern_parens);
check_edit(
"Var",
r#"
enum E { Var(i32) }
fn main() {
match E::Var(92) {
E::<|>(92) => (),
}
}
"#,
r#"
enum E { Var(i32) }
fn main() {
match E::Var(92) {
E::Var(92) => (),
}
}
"#, "#,
); );
} }

View file

@ -0,0 +1,148 @@
//! Renderer for patterns.
use hir::{db::HirDatabase, HasAttrs, HasVisibility, Name, StructKind};
use itertools::Itertools;
use crate::{
config::SnippetCap, 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,
local_name: Option<Name>,
) -> Option<CompletionItem> {
let _p = profile::span("render_struct_pat");
let fields = strukt.fields(ctx.db());
let (visible_fields, fields_omitted) = visible_fields(&ctx, &fields, strukt)?;
if visible_fields.is_empty() {
// Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
return None;
}
let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
let pat = render_pat(&ctx, &name, strukt.kind(ctx.db()), &visible_fields, fields_omitted)?;
Some(build_completion(ctx, name, pat, strukt))
}
pub(crate) fn render_variant_pat(
ctx: RenderContext<'_>,
variant: hir::Variant,
local_name: Option<Name>,
) -> 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 pat = render_pat(&ctx, &name, variant.kind(ctx.db()), &visible_fields, fields_omitted)?;
Some(build_completion(ctx, name, pat, variant))
}
fn build_completion(
ctx: RenderContext<'_>,
name: String,
pat: String,
item: impl HasAttrs + Copy,
) -> CompletionItem {
let completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
.kind(CompletionItemKind::Binding)
.set_documentation(ctx.docs(item))
.set_deprecated(ctx.is_deprecated(item))
.detail(&pat);
let completion = if let Some(snippet_cap) = ctx.snippet_cap() {
completion.insert_snippet(snippet_cap, pat)
} else {
completion.insert_text(pat)
};
completion.build()
}
fn render_pat(
ctx: &RenderContext<'_>,
name: &str,
kind: StructKind,
fields: &[hir::Field],
fields_omitted: bool,
) -> Option<String> {
let mut pat = match kind {
StructKind::Tuple if ctx.snippet_cap().is_some() => {
render_tuple_as_pat(&fields, &name, fields_omitted)
}
StructKind::Record => {
render_record_as_pat(ctx.db(), ctx.snippet_cap(), &fields, &name, fields_omitted)
}
_ => return None,
};
if ctx.completion.is_param {
pat.push(':');
pat.push(' ');
pat.push_str(&name);
}
if ctx.snippet_cap().is_some() {
pat.push_str("$0");
}
Some(pat)
}
fn render_record_as_pat(
db: &dyn HirDatabase,
snippet_cap: Option<SnippetCap>,
fields: &[hir::Field],
name: &str,
fields_omitted: bool,
) -> String {
let fields = fields.iter();
if snippet_cap.is_some() {
format!(
"{name} {{ {}{} }}",
fields
.enumerate()
.map(|(idx, field)| format!("{}${}", field.name(db), idx + 1))
.format(", "),
if fields_omitted { ", .." } else { "" },
name = name
)
} else {
format!(
"{name} {{ {}{} }}",
fields.map(|field| field.name(db)).format(", "),
if fields_omitted { ", .." } else { "" },
name = name
)
}
}
fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String {
format!(
"{name}({}{})",
fields.iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
if fields_omitted { ", .." } else { "" },
name = name
)
}

View file

@ -511,6 +511,10 @@ impl Struct {
db.struct_data(self.id).repr.clone() db.struct_data(self.id).repr.clone()
} }
pub fn kind(self, db: &dyn HirDatabase) -> StructKind {
self.variant_data(db).kind()
}
fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> { fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
db.struct_data(self.id).variant_data.clone() db.struct_data(self.id).variant_data.clone()
} }