aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2020-12-20 17:19:23 +0000
committerLukas Wirth <[email protected]>2020-12-20 17:19:23 +0000
commitb184bfad7a2dc6a9bf6654a7eec6c68a27c49f70 (patch)
treea9d35d8053e13d22561156c760e0996323040169
parentf3125555a8de6fad4529408436800a6b1243a442 (diff)
Add completions for patterns
-rw-r--r--crates/completion/src/completions.rs33
-rw-r--r--crates/completion/src/completions/pattern.rs154
-rw-r--r--crates/completion/src/context.rs21
-rw-r--r--crates/completion/src/render.rs1
-rw-r--r--crates/completion/src/render/pattern.rs128
-rw-r--r--crates/hir/src/code_model.rs4
6 files changed, 315 insertions, 26 deletions
diff --git a/crates/completion/src/completions.rs b/crates/completion/src/completions.rs
index 1ef6b5f48..d9fe13485 100644
--- a/crates/completion/src/completions.rs
+++ b/crates/completion/src/completions.rs
@@ -19,9 +19,14 @@ use hir::{ModPath, ScopeDef, Type};
19use crate::{ 19use crate::{
20 item::Builder, 20 item::Builder,
21 render::{ 21 render::{
22 const_::render_const, enum_variant::render_variant, function::render_fn, 22 const_::render_const,
23 macro_::render_macro, render_field, render_resolution, render_tuple_field, 23 enum_variant::render_variant,
24 type_alias::render_type_alias, RenderContext, 24 function::render_fn,
25 macro_::render_macro,
26 pattern::{render_struct_pat, render_variant_pat},
27 render_field, render_resolution, render_tuple_field,
28 type_alias::render_type_alias,
29 RenderContext,
25 }, 30 },
26 CompletionContext, CompletionItem, 31 CompletionContext, CompletionItem,
27}; 32};
@@ -105,6 +110,28 @@ impl Completions {
105 self.add(item) 110 self.add(item)
106 } 111 }
107 112
113 pub(crate) fn add_variant_pat(
114 &mut self,
115 ctx: &CompletionContext,
116 variant: hir::Variant,
117 local_name: Option<hir::Name>,
118 ) {
119 if let Some(item) = render_variant_pat(RenderContext::new(ctx), variant, local_name) {
120 self.add(item);
121 }
122 }
123
124 pub(crate) fn add_struct_pat(
125 &mut self,
126 ctx: &CompletionContext,
127 strukt: hir::Struct,
128 local_name: Option<hir::Name>,
129 ) {
130 if let Some(item) = render_struct_pat(RenderContext::new(ctx), strukt, local_name) {
131 self.add(item);
132 }
133 }
134
108 pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { 135 pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) {
109 if let Some(item) = render_const(RenderContext::new(ctx), constant) { 136 if let Some(item) = render_const(RenderContext::new(ctx), constant) {
110 self.add(item); 137 self.add(item);
diff --git a/crates/completion/src/completions/pattern.rs b/crates/completion/src/completions/pattern.rs
index 4d56731ec..496f0b040 100644
--- a/crates/completion/src/completions/pattern.rs
+++ b/crates/completion/src/completions/pattern.rs
@@ -1,10 +1,12 @@
1//! Completes constats and paths in patterns. 1//! Completes constats and paths in patterns.
2 2
3use hir::StructKind;
4
3use crate::{CompletionContext, Completions}; 5use crate::{CompletionContext, Completions};
4 6
5/// Completes constats and paths in patterns. 7/// Completes constants and paths in patterns.
6pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { 8pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
7 if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_let_pat_binding) { 9 if !(ctx.is_pat_binding_or_const || ctx.is_irrefutable_pat_binding) {
8 return; 10 return;
9 } 11 }
10 if ctx.record_pat_syntax.is_some() { 12 if ctx.record_pat_syntax.is_some() {
@@ -15,20 +17,25 @@ pub(crate) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) {
15 // suggest variants + auto-imports 17 // suggest variants + auto-imports
16 ctx.scope.process_all_names(&mut |name, res| { 18 ctx.scope.process_all_names(&mut |name, res| {
17 let add_resolution = match &res { 19 let add_resolution = match &res {
18 hir::ScopeDef::ModuleDef(def) => { 20 hir::ScopeDef::ModuleDef(def) => match def {
19 if ctx.is_irrefutable_let_pat_binding { 21 hir::ModuleDef::Adt(hir::Adt::Struct(strukt)) => {
20 matches!(def, hir::ModuleDef::Adt(hir::Adt::Struct(_))) 22 acc.add_struct_pat(ctx, strukt.clone(), Some(name.clone()));
21 } else { 23 true
22 matches!( 24 }
23 def, 25 hir::ModuleDef::Variant(variant)
24 hir::ModuleDef::Adt(hir::Adt::Enum(..)) 26 if !ctx.is_irrefutable_pat_binding
25 | hir::ModuleDef::Adt(hir::Adt::Struct(..)) 27 // render_resolution already does some pattern completion tricks for tuple variants
26 | hir::ModuleDef::Variant(..) 28 && variant.kind(ctx.db) == StructKind::Record =>
27 | hir::ModuleDef::Const(..) 29 {
28 | hir::ModuleDef::Module(..) 30 acc.add_variant_pat(ctx, variant.clone(), Some(name.clone()));
29 ) 31 true
30 } 32 }
31 } 33 hir::ModuleDef::Adt(hir::Adt::Enum(..))
34 | hir::ModuleDef::Variant(..)
35 | hir::ModuleDef::Const(..)
36 | hir::ModuleDef::Module(..) => !ctx.is_irrefutable_pat_binding,
37 _ => false,
38 },
32 hir::ScopeDef::MacroDef(_) => true, 39 hir::ScopeDef::MacroDef(_) => true,
33 _ => false, 40 _ => false,
34 }; 41 };
@@ -49,6 +56,11 @@ mod tests {
49 expect.assert_eq(&actual) 56 expect.assert_eq(&actual)
50 } 57 }
51 58
59 fn check_snippet(ra_fixture: &str, expect: Expect) {
60 let actual = completion_list(ra_fixture, CompletionKind::Snippet);
61 expect.assert_eq(&actual)
62 }
63
52 #[test] 64 #[test]
53 fn completes_enum_variants_and_modules() { 65 fn completes_enum_variants_and_modules() {
54 check( 66 check(
@@ -114,4 +126,116 @@ fn foo() {
114 "#]], 126 "#]],
115 ); 127 );
116 } 128 }
129
130 #[test]
131 fn completes_in_param() {
132 check(
133 r#"
134enum E { X }
135
136static FOO: E = E::X;
137struct Bar { f: u32 }
138
139fn foo(<|>) {
140}
141"#,
142 expect![[r#"
143 st Bar
144 "#]],
145 );
146 }
147
148 #[test]
149 fn completes_pat_in_let() {
150 check_snippet(
151 r#"
152struct Bar { f: u32 }
153
154fn foo() {
155 let <|>
156}
157"#,
158 expect![[r#"
159 bn Bar Bar { f }$0
160 "#]],
161 );
162 }
163
164 #[test]
165 fn completes_param_pattern() {
166 check_snippet(
167 r#"
168struct Foo { bar: String, baz: String }
169struct Bar(String, String);
170struct Baz;
171fn outer(<|>) {}
172"#,
173 expect![[r#"
174 bn Foo Foo { bar, baz }: Foo$0
175 bn Bar Bar($1, $2): Bar$0
176 "#]],
177 )
178 }
179
180 #[test]
181 fn completes_let_pattern() {
182 check_snippet(
183 r#"
184struct Foo { bar: String, baz: String }
185struct Bar(String, String);
186struct Baz;
187fn outer() {
188 let <|>
189}
190"#,
191 expect![[r#"
192 bn Foo Foo { bar, baz }$0
193 bn Bar Bar($1, $2)$0
194 "#]],
195 )
196 }
197
198 #[test]
199 fn completes_refutable_pattern() {
200 check_snippet(
201 r#"
202struct Foo { bar: i32, baz: i32 }
203struct Bar(String, String);
204struct Baz;
205fn outer() {
206 match () {
207 <|>
208 }
209}
210"#,
211 expect![[r#"
212 bn Foo Foo { bar, baz }$0
213 bn Bar Bar($1, $2)$0
214 "#]],
215 )
216 }
217
218 #[test]
219 fn omits_private_fields_pat() {
220 check_snippet(
221 r#"
222mod foo {
223 pub struct Foo { pub bar: i32, baz: i32 }
224 pub struct Bar(pub String, String);
225 pub struct Invisible(String, String);
226}
227use foo::*;
228
229fn outer() {
230 match () {
231 <|>
232 }
233}
234"#,
235 expect![[r#"
236 bn Foo Foo { bar, .. }$0
237 bn Bar Bar($1, ..)$0
238 "#]],
239 )
240 }
117} 241}
diff --git a/crates/completion/src/context.rs b/crates/completion/src/context.rs
index 5cd11cf77..41de324d8 100644
--- a/crates/completion/src/context.rs
+++ b/crates/completion/src/context.rs
@@ -51,7 +51,7 @@ pub(crate) struct CompletionContext<'a> {
51 /// If a name-binding or reference to a const in a pattern. 51 /// If a name-binding or reference to a const in a pattern.
52 /// Irrefutable patterns (like let) are excluded. 52 /// Irrefutable patterns (like let) are excluded.
53 pub(super) is_pat_binding_or_const: bool, 53 pub(super) is_pat_binding_or_const: bool,
54 pub(super) is_irrefutable_let_pat_binding: bool, 54 pub(super) is_irrefutable_pat_binding: bool,
55 /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. 55 /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path.
56 pub(super) is_trivial_path: bool, 56 pub(super) is_trivial_path: bool,
57 /// If not a trivial path, the prefix (qualifier). 57 /// If not a trivial path, the prefix (qualifier).
@@ -147,7 +147,7 @@ impl<'a> CompletionContext<'a> {
147 active_parameter: ActiveParameter::at(db, position), 147 active_parameter: ActiveParameter::at(db, position),
148 is_param: false, 148 is_param: false,
149 is_pat_binding_or_const: false, 149 is_pat_binding_or_const: false,
150 is_irrefutable_let_pat_binding: false, 150 is_irrefutable_pat_binding: false,
151 is_trivial_path: false, 151 is_trivial_path: false,
152 path_qual: None, 152 path_qual: None,
153 after_if: false, 153 after_if: false,
@@ -327,14 +327,19 @@ impl<'a> CompletionContext<'a> {
327 if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() { 327 if bind_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast).is_some() {
328 self.is_pat_binding_or_const = false; 328 self.is_pat_binding_or_const = false;
329 } 329 }
330 if let Some(let_stmt) = bind_pat.syntax().ancestors().find_map(ast::LetStmt::cast) { 330 if let Some(Some(pat)) = bind_pat.syntax().ancestors().find_map(|node| {
331 if let Some(pat) = let_stmt.pat() { 331 match_ast! {
332 if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) 332 match node {
333 { 333 ast::LetStmt(it) => Some(it.pat()),
334 self.is_pat_binding_or_const = false; 334 ast::Param(it) => Some(it.pat()),
335 self.is_irrefutable_let_pat_binding = true; 335 _ => None,
336 } 336 }
337 } 337 }
338 }) {
339 if pat.syntax().text_range().contains_range(bind_pat.syntax().text_range()) {
340 self.is_pat_binding_or_const = false;
341 self.is_irrefutable_pat_binding = true;
342 }
338 } 343 }
339 } 344 }
340 if is_node::<ast::Param>(name.syntax()) { 345 if is_node::<ast::Param>(name.syntax()) {
diff --git a/crates/completion/src/render.rs b/crates/completion/src/render.rs
index 1092a4825..945158d10 100644
--- a/crates/completion/src/render.rs
+++ b/crates/completion/src/render.rs
@@ -5,6 +5,7 @@ pub(crate) mod macro_;
5pub(crate) mod function; 5pub(crate) mod function;
6pub(crate) mod enum_variant; 6pub(crate) mod enum_variant;
7pub(crate) mod const_; 7pub(crate) mod const_;
8pub(crate) mod pattern;
8pub(crate) mod type_alias; 9pub(crate) mod type_alias;
9 10
10mod builder_ext; 11mod builder_ext;
diff --git a/crates/completion/src/render/pattern.rs b/crates/completion/src/render/pattern.rs
new file mode 100644
index 000000000..e20b0027b
--- /dev/null
+++ b/crates/completion/src/render/pattern.rs
@@ -0,0 +1,128 @@
1//! Renderer for patterns.
2
3use hir::{db::HirDatabase, HasVisibility, Name, StructKind};
4use itertools::Itertools;
5
6use crate::{item::CompletionKind, render::RenderContext, CompletionItem, CompletionItemKind};
7
8pub(crate) fn render_struct_pat<'a>(
9 ctx: RenderContext<'a>,
10 strukt: hir::Struct,
11 local_name: Option<Name>,
12) -> Option<CompletionItem> {
13 let _p = profile::span("render_struct_pat");
14
15 let module = ctx.completion.scope.module()?;
16 let fields = strukt.fields(ctx.db());
17 let n_fields = fields.len();
18 let fields = fields
19 .into_iter()
20 .filter(|field| field.is_visible_from(ctx.db(), module))
21 .collect::<Vec<_>>();
22
23 if fields.is_empty() {
24 // Matching a struct without matching its fields is pointless, unlike matching a Variant without its fields
25 return None;
26 }
27 let fields_omitted = n_fields - fields.len() > 0;
28
29 let name = local_name.unwrap_or_else(|| strukt.name(ctx.db())).to_string();
30 let mut pat = match strukt.kind(ctx.db()) {
31 StructKind::Tuple if ctx.snippet_cap().is_some() => {
32 render_tuple_as_pat(&fields, &name, fields_omitted)
33 }
34 StructKind::Record => render_record_as_pat(ctx.db(), &fields, &name, fields_omitted),
35 _ => return None,
36 };
37
38 if ctx.completion.is_param {
39 pat.push(':');
40 pat.push(' ');
41 pat.push_str(&name);
42 }
43 if ctx.snippet_cap().is_some() {
44 pat.push_str("$0");
45 }
46
47 let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
48 .kind(CompletionItemKind::Binding)
49 .set_documentation(ctx.docs(strukt))
50 .set_deprecated(ctx.is_deprecated(strukt))
51 .detail(&pat);
52 if let Some(snippet_cap) = ctx.snippet_cap() {
53 completion = completion.insert_snippet(snippet_cap, pat);
54 } else {
55 completion = completion.insert_text(pat);
56 }
57 Some(completion.build())
58}
59
60pub(crate) fn render_variant_pat<'a>(
61 ctx: RenderContext<'a>,
62 variant: hir::Variant,
63 local_name: Option<Name>,
64) -> Option<CompletionItem> {
65 let _p = profile::span("render_variant_pat");
66
67 let module = ctx.completion.scope.module()?;
68 let fields = variant.fields(ctx.db());
69 let n_fields = fields.len();
70 let fields = fields
71 .into_iter()
72 .filter(|field| field.is_visible_from(ctx.db(), module))
73 .collect::<Vec<_>>();
74
75 let fields_omitted = n_fields - fields.len() > 0;
76
77 let name = local_name.unwrap_or_else(|| variant.name(ctx.db())).to_string();
78 let mut pat = match variant.kind(ctx.db()) {
79 StructKind::Tuple if ctx.snippet_cap().is_some() => {
80 render_tuple_as_pat(&fields, &name, fields_omitted)
81 }
82 StructKind::Record => render_record_as_pat(ctx.db(), &fields, &name, fields_omitted),
83 _ => return None,
84 };
85
86 if ctx.completion.is_param {
87 pat.push(':');
88 pat.push(' ');
89 pat.push_str(&name);
90 }
91 if ctx.snippet_cap().is_some() {
92 pat.push_str("$0");
93 }
94 let mut completion = CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), name)
95 .kind(CompletionItemKind::Binding)
96 .set_documentation(ctx.docs(variant))
97 .set_deprecated(ctx.is_deprecated(variant))
98 .detail(&pat);
99 if let Some(snippet_cap) = ctx.snippet_cap() {
100 completion = completion.insert_snippet(snippet_cap, pat);
101 } else {
102 completion = completion.insert_text(pat);
103 }
104 Some(completion.build())
105}
106
107fn render_record_as_pat(
108 db: &dyn HirDatabase,
109 fields: &[hir::Field],
110 name: &str,
111 fields_omitted: bool,
112) -> String {
113 format!(
114 "{name} {{ {}{} }}",
115 fields.into_iter().map(|field| field.name(db)).format(", "),
116 if fields_omitted { ", .." } else { "" },
117 name = name
118 )
119}
120
121fn render_tuple_as_pat(fields: &[hir::Field], name: &str, fields_omitted: bool) -> String {
122 format!(
123 "{name}({}{})",
124 fields.into_iter().enumerate().map(|(idx, _)| format!("${}", idx + 1)).format(", "),
125 if fields_omitted { ", .." } else { "" },
126 name = name
127 )
128}
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index d6c7e71ea..dbc937e4f 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -511,6 +511,10 @@ impl Struct {
511 db.struct_data(self.id).repr.clone() 511 db.struct_data(self.id).repr.clone()
512 } 512 }
513 513
514 pub fn kind(self, db: &dyn HirDatabase) -> StructKind {
515 self.variant_data(db).kind()
516 }
517
514 fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> { 518 fn variant_data(self, db: &dyn HirDatabase) -> Arc<VariantData> {
515 db.struct_data(self.id).variant_data.clone() 519 db.struct_data(self.id).variant_data.clone()
516 } 520 }