use crate::{
item::Builder,
render::{
- const_::render_const, enum_variant::render_variant, function::render_fn,
- macro_::render_macro, render_field, render_resolution, render_tuple_field,
- type_alias::render_type_alias, RenderContext,
+ const_::render_const,
+ enum_variant::render_variant,
+ 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,
};
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) {
if let Some(item) = render_const(RenderContext::new(ctx), constant) {
self.add(item);
//! Completes constats and paths in patterns.
+use hir::StructKind;
+
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) {
- 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;
}
if ctx.record_pat_syntax.is_some() {
// suggest variants + auto-imports
ctx.scope.process_all_names(&mut |name, res| {
let add_resolution = match &res {
- hir::ScopeDef::ModuleDef(def) => {
- if ctx.is_irrefutable_let_pat_binding {
- matches!(def, hir::ModuleDef::Adt(hir::Adt::Struct(_)))
- } else {
- matches!(
- def,
- hir::ModuleDef::Adt(hir::Adt::Enum(..))
- | hir::ModuleDef::Adt(hir::Adt::Struct(..))
- | hir::ModuleDef::Variant(..)
- | hir::ModuleDef::Const(..)
- | hir::ModuleDef::Module(..)
- )
+ hir::ScopeDef::ModuleDef(def) => match def {
+ hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
+ acc.add_struct_pat(ctx, strukt.clone(), Some(name.clone()));
+ true
+ }
+ hir::ModuleDef::Variant(variant)
+ if !ctx.is_irrefutable_pat_binding
+ // render_resolution already does some pattern completion tricks for tuple variants
+ && variant.kind(ctx.db) == StructKind::Record =>
+ {
+ acc.add_variant_pat(ctx, variant.clone(), Some(name.clone()));
+ true
}
- }
+ hir::ModuleDef::Adt(hir::Adt::Enum(..))
+ | hir::ModuleDef::Variant(..)
+ | hir::ModuleDef::Const(..)
+ | hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding,
+ _ => false,
+ },
hir::ScopeDef::MacroDef(_) => true,
_ => false,
};
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]
fn completes_enum_variants_and_modules() {
check(
"#]],
);
}
+
+ #[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 }$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, baz }: 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, baz }$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, baz }$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, .. }$0
+ bn Bar Bar($1, ..)$0
+ "#]],
+ )
+ }
}
/// If a name-binding or reference to a const in a pattern.
/// Irrefutable patterns (like let) are excluded.
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.
pub(super) is_trivial_path: bool,
/// If not a trivial path, the prefix (qualifier).
active_parameter: ActiveParameter::at(db, position),
is_param: false,
is_pat_binding_or_const: false,
- is_irrefutable_let_pat_binding: false,
+ is_irrefutable_pat_binding: false,
is_trivial_path: false,
path_qual: None,
after_if: false,
if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
self.is_pat_binding_or_const = false;
}
- if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) {
- if let Some(pat) = let_stmt.pat() {
- if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range())
- {
- self.is_pat_binding_or_const = false;
- self.is_irrefutable_let_pat_binding = true;
+ if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| {
+ match_ast! {
+ match node {
+ ast::LetStmt(it) => Some(it.pat()),
+ ast::Param(it) => Some(it.pat()),
+ _ => 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()) {
pub(crate) mod function;
pub(crate) mod enum_variant;
pub(crate) mod const_;
+pub(crate) mod pattern;
pub(crate) mod type_alias;
mod builder_ext;
--- /dev/null
+//! Renderer for patterns.
+
+use hir::{db::HirDatabase, HasVisibility, Name, StructKind};
+use itertools::Itertools;
+
+use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
+
+pub(crate) fn render_struct_pat<'a>(
+ ctx: RenderContext<'a>,
+ strukt: hir::Struct,
+ local_name: Option<Name>,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_struct_pat");
+
+ let module = ctx.completion.scope.module()?;
+ let fields = strukt.fields(ctx.db());
+ let n_fields = fields.len();
+ let fields = fields
+ .into_iter()
+ .filter(|field| field.is_visible_from(ctx.db(), module))
+ .collect::<Vec<_>>();
+
+ if fields.is_empty() {
+ // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
+ return None;
+ }
+ let fields_omitted = n_fields - fields.len() > 0;
+
+ let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
+ let mut pat = match strukt.kind(ctx.db()) {
+ StructKind::Tuple if ctx.snippet_cap().is_some() => {
+ render_tuple_as_pat(&fields, &name, fields_omitted)
+ }
+ StructKind::Record => render_record_as_pat(ctx.db(), &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");
+ }
+
+ let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
+ .kind(CompletionItemKind::Binding)
+ .set_documentation(ctx.docs(strukt))
+ .set_deprecated(ctx.is_deprecated(strukt))
+ .detail(&pat);
+ if let Some(snippet_cap) = ctx.snippet_cap() {
+ completion = completion.insert_snippet(snippet_cap, pat);
+ } else {
+ completion = completion.insert_text(pat);
+ }
+ Some(completion.build())
+}
+
+pub(crate) fn render_variant_pat<'a>(
+ ctx: RenderContext<'a>,
+ variant: hir::Variant,
+ local_name: Option<Name>,
+) -> Option<CompletionItem> {
+ let _p = profile::span("render_variant_pat");
+
+ let module = ctx.completion.scope.module()?;
+ let fields = variant.fields(ctx.db());
+ let n_fields = fields.len();
+ let fields = fields
+ .into_iter()
+ .filter(|field| field.is_visible_from(ctx.db(), module))
+ .collect::<Vec<_>>();
+
+ let fields_omitted = n_fields - fields.len() > 0;
+
+ let name = local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string();
+ let mut pat = match variant.kind(ctx.db()) {
+ StructKind::Tuple if ctx.snippet_cap().is_some() => {
+ render_tuple_as_pat(&fields, &name, fields_omitted)
+ }
+ StructKind::Record => render_record_as_pat(ctx.db(), &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");
+ }
+ let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
+ .kind(CompletionItemKind::Binding)
+ .set_documentation(ctx.docs(variant))
+ .set_deprecated(ctx.is_deprecated(variant))
+ .detail(&pat);
+ if let Some(snippet_cap) = ctx.snippet_cap() {
+ completion = completion.insert_snippet(snippet_cap, pat);
+ } else {
+ completion = completion.insert_text(pat);
+ }
+ Some(completion.build())
+}
+
+fn render_record_as_pat(
+ db: &dyn HirDatabase,
+ fields: &[hir::Field],
+ name: &str,
+ fields_omitted: bool,
+) -> String {
+ format!(
+ "{name} {{ {}{} }}",
+ fields.into_iter().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.into_iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
+ if fields_omitted { ", .." } else { "" },
+ name = name
+ )
+}
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> {
db.struct_data(self.id).variant_data.clone()
}