diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs index 1ef6b5f48f1..d9fe1348552 100644 --- a/crates/completion/src/completions.rs +++ b/crates/completion/src/completions.rs @@ -19,9 +19,14 @@ use hir::{ModPath, ScopeDef, Type}; 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, }; @@ -105,6 +110,28 @@ impl Completions { self.add(item) } + pub(crate) fn add_variant_pat( + &mut self, + ctx: &CompletionContext, + variant: hir::Variant, + local_name: Option, + ) { + 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, + ) { + 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); diff --git a/crates/completion/src/completions/pattern.rs b/crates/completion/src/completions/pattern.rs index 4d56731ec61..eee31098d13 100644 --- a/crates/completion/src/completions/pattern.rs +++ b/crates/completion/src/completions/pattern.rs @@ -2,9 +2,9 @@ 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() { @@ -15,20 +15,21 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { // 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 => { + 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, }; @@ -42,13 +43,21 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { mod tests { 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) { let actual = completion_list(ra_fixture, CompletionKind::Reference); 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( @@ -69,7 +78,7 @@ fn foo() { en E ct Z st Bar - ev X () + ev X 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) => (), + } +} +"#, + ); + } } diff --git a/crates/completion/src/context.rs b/crates/completion/src/context.rs index 5cd11cf775c..41de324d8d9 100644 --- a/crates/completion/src/context.rs +++ b/crates/completion/src/context.rs @@ -51,7 +51,7 @@ pub(crate) struct CompletionContext<'a> { /// 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). @@ -147,7 +147,7 @@ impl<'a> CompletionContext<'a> { 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, @@ -327,14 +327,19 @@ impl<'a> CompletionContext<'a> { 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::(name.syntax()) { diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs index 1092a482561..1ba7201a138 100644 --- a/crates/completion/src/render.rs +++ b/crates/completion/src/render.rs @@ -5,6 +5,7 @@ pub(crate) mod macro_; 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; @@ -159,6 +160,12 @@ impl<'a> Render<'a> { let item = render_fn(self.ctx, import_to_add, Some(local_name), *func); 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)) => { let item = render_variant(self.ctx, import_to_add, Some(local_name), *var, None); return Some(item); diff --git a/crates/completion/src/render/builder_ext.rs b/crates/completion/src/render/builder_ext.rs index ce8718bd542..d053a988bc8 100644 --- a/crates/completion/src/render/builder_ext.rs +++ b/crates/completion/src/render/builder_ext.rs @@ -34,7 +34,6 @@ impl Builder { return false; } if ctx.is_pattern_call { - mark::hit!(dont_duplicate_pattern_parens); return false; } if ctx.is_call { diff --git a/crates/completion/src/render/enum_variant.rs b/crates/completion/src/render/enum_variant.rs index 7176fd9b3d1..732e139eca2 100644 --- a/crates/completion/src/render/enum_variant.rs +++ b/crates/completion/src/render/enum_variant.rs @@ -124,51 +124,6 @@ use Option::*; fn main() -> Option { Some($0) } -"#, - ); - check_edit( - "Some", - r#" -enum Option { Some(T), None } -use Option::*; -fn main(value: Option) { - match value { - Som<|> - } -} -"#, - r#" -enum Option { Some(T), None } -use Option::*; -fn main(value: Option) { - 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) => (), - } -} "#, ); } diff --git a/crates/completion/src/render/pattern.rs b/crates/completion/src/render/pattern.rs new file mode 100644 index 00000000000..a3b6a3cac7a --- /dev/null +++ b/crates/completion/src/render/pattern.rs @@ -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, 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::>(); + + 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, +) -> Option { + 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, +) -> Option { + 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 { + 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, + 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 + ) +} diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index 73ca6ba9f48..1d7e5ddd7f6 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs @@ -511,6 +511,10 @@ impl Struct { 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 { db.struct_data(self.id).variant_data.clone() }