diff options
-rw-r--r-- | crates/ide_completion/src/completions/fn_param.rs | 15 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/keyword.rs | 18 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/mod_.rs | 6 | ||||
-rw-r--r-- | crates/ide_completion/src/completions/record.rs | 20 | ||||
-rw-r--r-- | crates/ide_completion/src/context.rs | 99 | ||||
-rw-r--r-- | crates/ide_completion/src/patterns.rs | 151 |
6 files changed, 163 insertions, 146 deletions
diff --git a/crates/ide_completion/src/completions/fn_param.rs b/crates/ide_completion/src/completions/fn_param.rs index 0ea558489..cb90e8a3e 100644 --- a/crates/ide_completion/src/completions/fn_param.rs +++ b/crates/ide_completion/src/completions/fn_param.rs | |||
@@ -128,4 +128,19 @@ fn outer(text: String) { | |||
128 | "#]], | 128 | "#]], |
129 | ) | 129 | ) |
130 | } | 130 | } |
131 | |||
132 | #[test] | ||
133 | fn completes_non_ident_pat_param() { | ||
134 | check( | ||
135 | r#" | ||
136 | struct Bar { bar: u32 } | ||
137 | |||
138 | fn foo(Bar { bar }: Bar) {} | ||
139 | fn foo2($0) {} | ||
140 | "#, | ||
141 | expect![[r#" | ||
142 | bn Bar { bar }: Bar | ||
143 | "#]], | ||
144 | ) | ||
145 | } | ||
131 | } | 146 | } |
diff --git a/crates/ide_completion/src/completions/keyword.rs b/crates/ide_completion/src/completions/keyword.rs index e71a04b6e..0d035c611 100644 --- a/crates/ide_completion/src/completions/keyword.rs +++ b/crates/ide_completion/src/completions/keyword.rs | |||
@@ -4,7 +4,10 @@ use std::iter; | |||
4 | 4 | ||
5 | use syntax::{SyntaxKind, T}; | 5 | use syntax::{SyntaxKind, T}; |
6 | 6 | ||
7 | use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; | 7 | use crate::{ |
8 | patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind, | ||
9 | CompletionKind, Completions, | ||
10 | }; | ||
8 | 11 | ||
9 | pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { | 12 | pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { |
10 | // complete keyword "crate" in use stmt | 13 | // complete keyword "crate" in use stmt |
@@ -44,7 +47,7 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
44 | cov_mark::hit!(no_keyword_completion_in_comments); | 47 | cov_mark::hit!(no_keyword_completion_in_comments); |
45 | return; | 48 | return; |
46 | } | 49 | } |
47 | if ctx.record_lit_syntax.is_some() { | 50 | if matches!(ctx.completion_location, Some(ImmediateLocation::RecordExpr(_))) { |
48 | cov_mark::hit!(no_keyword_completion_in_record_lit); | 51 | cov_mark::hit!(no_keyword_completion_in_record_lit); |
49 | return; | 52 | return; |
50 | } | 53 | } |
@@ -55,7 +58,6 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
55 | let expects_item = ctx.expects_item(); | 58 | let expects_item = ctx.expects_item(); |
56 | 59 | ||
57 | if ctx.has_impl_or_trait_prev_sibling() { | 60 | if ctx.has_impl_or_trait_prev_sibling() { |
58 | // FIXME this also incorrectly shows up after a complete trait/impl | ||
59 | add_keyword("where", "where "); | 61 | add_keyword("where", "where "); |
60 | return; | 62 | return; |
61 | } | 63 | } |
@@ -77,11 +79,8 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
77 | add_keyword("pub", "pub "); | 79 | add_keyword("pub", "pub "); |
78 | } | 80 | } |
79 | 81 | ||
80 | if expects_item || expects_assoc_item || has_block_expr_parent || ctx.is_match_arm { | ||
81 | add_keyword("unsafe", "unsafe "); | ||
82 | } | ||
83 | |||
84 | if expects_item || expects_assoc_item || has_block_expr_parent { | 82 | if expects_item || expects_assoc_item || has_block_expr_parent { |
83 | add_keyword("unsafe", "unsafe "); | ||
85 | add_keyword("fn", "fn $1($2) {\n $0\n}"); | 84 | add_keyword("fn", "fn $1($2) {\n $0\n}"); |
86 | add_keyword("const", "const $0"); | 85 | add_keyword("const", "const $0"); |
87 | add_keyword("type", "type $0"); | 86 | add_keyword("type", "type $0"); |
@@ -103,6 +102,9 @@ pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionConte | |||
103 | } | 102 | } |
104 | 103 | ||
105 | if ctx.expects_expression() { | 104 | if ctx.expects_expression() { |
105 | if !has_block_expr_parent { | ||
106 | add_keyword("unsafe", "unsafe {\n $0\n}"); | ||
107 | } | ||
106 | add_keyword("match", "match $1 {\n $0\n}"); | 108 | add_keyword("match", "match $1 {\n $0\n}"); |
107 | add_keyword("while", "while $1 {\n $0\n}"); | 109 | add_keyword("while", "while $1 {\n $0\n}"); |
108 | add_keyword("while let", "while let $1 = $2 {\n $0\n}"); | 110 | add_keyword("while let", "while let $1 = $2 {\n $0\n}"); |
@@ -574,6 +576,7 @@ pub mod future { | |||
574 | check( | 576 | check( |
575 | r#"fn main() { let _ = $0 }"#, | 577 | r#"fn main() { let _ = $0 }"#, |
576 | expect![[r#" | 578 | expect![[r#" |
579 | kw unsafe | ||
577 | kw match | 580 | kw match |
578 | kw while | 581 | kw while |
579 | kw while let | 582 | kw while let |
@@ -634,6 +637,7 @@ fn foo() { | |||
634 | } | 637 | } |
635 | "#, | 638 | "#, |
636 | expect![[r#" | 639 | expect![[r#" |
640 | kw unsafe | ||
637 | kw match | 641 | kw match |
638 | kw while | 642 | kw while |
639 | kw while let | 643 | kw while let |
diff --git a/crates/ide_completion/src/completions/mod_.rs b/crates/ide_completion/src/completions/mod_.rs index 4f9415736..6a5746fb9 100644 --- a/crates/ide_completion/src/completions/mod_.rs +++ b/crates/ide_completion/src/completions/mod_.rs | |||
@@ -9,14 +9,14 @@ use ide_db::{ | |||
9 | }; | 9 | }; |
10 | use rustc_hash::FxHashSet; | 10 | use rustc_hash::FxHashSet; |
11 | 11 | ||
12 | use crate::CompletionItem; | 12 | use crate::{patterns::ImmediateLocation, CompletionItem}; |
13 | 13 | ||
14 | use crate::{context::CompletionContext, item::CompletionKind, Completions}; | 14 | use crate::{context::CompletionContext, item::CompletionKind, Completions}; |
15 | 15 | ||
16 | /// Complete mod declaration, i.e. `mod $0 ;` | 16 | /// Complete mod declaration, i.e. `mod $0 ;` |
17 | pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 17 | pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
18 | let mod_under_caret = match &ctx.mod_declaration_under_caret { | 18 | let mod_under_caret = match &ctx.completion_location { |
19 | Some(mod_under_caret) if mod_under_caret.item_list().is_none() => mod_under_caret, | 19 | Some(ImmediateLocation::ModDeclaration(mod_under_caret)) => mod_under_caret, |
20 | _ => return None, | 20 | _ => return None, |
21 | }; | 21 | }; |
22 | 22 | ||
diff --git a/crates/ide_completion/src/completions/record.rs b/crates/ide_completion/src/completions/record.rs index e1526b70b..227c08d01 100644 --- a/crates/ide_completion/src/completions/record.rs +++ b/crates/ide_completion/src/completions/record.rs | |||
@@ -2,21 +2,21 @@ | |||
2 | use ide_db::{helpers::FamousDefs, SymbolKind}; | 2 | use ide_db::{helpers::FamousDefs, SymbolKind}; |
3 | use syntax::ast::Expr; | 3 | use syntax::ast::Expr; |
4 | 4 | ||
5 | use crate::{item::CompletionKind, CompletionContext, CompletionItem, Completions}; | 5 | use crate::{ |
6 | item::CompletionKind, patterns::ImmediateLocation, CompletionContext, CompletionItem, | ||
7 | Completions, | ||
8 | }; | ||
6 | 9 | ||
7 | pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { | 10 | pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { |
8 | let missing_fields = match (ctx.record_pat_syntax.as_ref(), ctx.record_lit_syntax.as_ref()) { | 11 | let missing_fields = match &ctx.completion_location { |
9 | (None, None) => return None, | 12 | Some(ImmediateLocation::RecordExpr(record_expr)) => { |
10 | (Some(_), Some(_)) => unreachable!("A record cannot be both a literal and a pattern"), | 13 | let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone())); |
11 | (Some(record_pat), _) => ctx.sema.record_pattern_missing_fields(record_pat), | ||
12 | (_, Some(record_lit)) => { | ||
13 | let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_lit.clone())); | ||
14 | let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default(); | 14 | let default_trait = FamousDefs(&ctx.sema, ctx.krate).core_default_Default(); |
15 | let impl_default_trait = default_trait | 15 | let impl_default_trait = default_trait |
16 | .zip(ty) | 16 | .zip(ty) |
17 | .map_or(false, |(default_trait, ty)| ty.impls_trait(ctx.db, default_trait, &[])); | 17 | .map_or(false, |(default_trait, ty)| ty.impls_trait(ctx.db, default_trait, &[])); |
18 | 18 | ||
19 | let missing_fields = ctx.sema.record_literal_missing_fields(record_lit); | 19 | let missing_fields = ctx.sema.record_literal_missing_fields(record_expr); |
20 | if impl_default_trait && !missing_fields.is_empty() { | 20 | if impl_default_trait && !missing_fields.is_empty() { |
21 | let completion_text = "..Default::default()"; | 21 | let completion_text = "..Default::default()"; |
22 | let mut item = CompletionItem::new( | 22 | let mut item = CompletionItem::new( |
@@ -32,6 +32,10 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> | |||
32 | 32 | ||
33 | missing_fields | 33 | missing_fields |
34 | } | 34 | } |
35 | Some(ImmediateLocation::RecordPat(record_pat)) => { | ||
36 | ctx.sema.record_pattern_missing_fields(record_pat) | ||
37 | } | ||
38 | _ => return None, | ||
35 | }; | 39 | }; |
36 | 40 | ||
37 | for (field, ty) in missing_fields { | 41 | for (field, ty) in missing_fields { |
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index 8d6440cb2..7c46c815d 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs | |||
@@ -18,7 +18,7 @@ use text_edit::Indel; | |||
18 | use crate::{ | 18 | use crate::{ |
19 | patterns::{ | 19 | patterns::{ |
20 | determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block, | 20 | determine_location, determine_prev_sibling, for_is_prev2, inside_impl_trait_block, |
21 | is_in_loop_body, is_match_arm, previous_token, ImmediateLocation, ImmediatePrevSibling, | 21 | is_in_loop_body, previous_token, ImmediateLocation, ImmediatePrevSibling, |
22 | }, | 22 | }, |
23 | CompletionConfig, | 23 | CompletionConfig, |
24 | }; | 24 | }; |
@@ -54,11 +54,6 @@ pub(crate) struct CompletionContext<'a> { | |||
54 | /// The parent impl of the cursor position if it exists. | 54 | /// The parent impl of the cursor position if it exists. |
55 | pub(super) impl_def: Option<ast::Impl>, | 55 | pub(super) impl_def: Option<ast::Impl>, |
56 | 56 | ||
57 | /// RecordExpr the token is a field of | ||
58 | pub(super) record_lit_syntax: Option<ast::RecordExpr>, | ||
59 | /// RecordPat the token is a field of | ||
60 | pub(super) record_pat_syntax: Option<ast::RecordPat>, | ||
61 | |||
62 | // potentially set if we are completing a lifetime | 57 | // potentially set if we are completing a lifetime |
63 | pub(super) lifetime_syntax: Option<ast::Lifetime>, | 58 | pub(super) lifetime_syntax: Option<ast::Lifetime>, |
64 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, | 59 | pub(super) lifetime_param_syntax: Option<ast::LifetimeParam>, |
@@ -71,6 +66,7 @@ pub(crate) struct CompletionContext<'a> { | |||
71 | 66 | ||
72 | pub(super) completion_location: Option<ImmediateLocation>, | 67 | pub(super) completion_location: Option<ImmediateLocation>, |
73 | pub(super) prev_sibling: Option<ImmediatePrevSibling>, | 68 | pub(super) prev_sibling: Option<ImmediatePrevSibling>, |
69 | pub(super) attribute_under_caret: Option<ast::Attr>, | ||
74 | 70 | ||
75 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong | 71 | /// FIXME: `ActiveParameter` is string-based, which is very very wrong |
76 | pub(super) active_parameter: Option<ActiveParameter>, | 72 | pub(super) active_parameter: Option<ActiveParameter>, |
@@ -95,14 +91,10 @@ pub(crate) struct CompletionContext<'a> { | |||
95 | pub(super) is_macro_call: bool, | 91 | pub(super) is_macro_call: bool, |
96 | pub(super) is_path_type: bool, | 92 | pub(super) is_path_type: bool, |
97 | pub(super) has_type_args: bool, | 93 | pub(super) has_type_args: bool, |
98 | pub(super) attribute_under_caret: Option<ast::Attr>, | ||
99 | pub(super) mod_declaration_under_caret: Option<ast::Module>, | ||
100 | pub(super) locals: Vec<(String, Local)>, | 94 | pub(super) locals: Vec<(String, Local)>, |
101 | 95 | ||
102 | // keyword patterns | ||
103 | pub(super) previous_token: Option<SyntaxToken>, | 96 | pub(super) previous_token: Option<SyntaxToken>, |
104 | pub(super) in_loop_body: bool, | 97 | pub(super) in_loop_body: bool, |
105 | pub(super) is_match_arm: bool, | ||
106 | pub(super) incomplete_let: bool, | 98 | pub(super) incomplete_let: bool, |
107 | 99 | ||
108 | no_completion_required: bool, | 100 | no_completion_required: bool, |
@@ -157,8 +149,6 @@ impl<'a> CompletionContext<'a> { | |||
157 | lifetime_param_syntax: None, | 149 | lifetime_param_syntax: None, |
158 | function_def: None, | 150 | function_def: None, |
159 | use_item_syntax: None, | 151 | use_item_syntax: None, |
160 | record_lit_syntax: None, | ||
161 | record_pat_syntax: None, | ||
162 | impl_def: None, | 152 | impl_def: None, |
163 | active_parameter: ActiveParameter::at(db, position), | 153 | active_parameter: ActiveParameter::at(db, position), |
164 | is_label_ref: false, | 154 | is_label_ref: false, |
@@ -176,15 +166,13 @@ impl<'a> CompletionContext<'a> { | |||
176 | is_macro_call: false, | 166 | is_macro_call: false, |
177 | is_path_type: false, | 167 | is_path_type: false, |
178 | has_type_args: false, | 168 | has_type_args: false, |
179 | attribute_under_caret: None, | ||
180 | mod_declaration_under_caret: None, | ||
181 | previous_token: None, | 169 | previous_token: None, |
182 | in_loop_body: false, | 170 | in_loop_body: false, |
183 | completion_location: None, | 171 | completion_location: None, |
184 | prev_sibling: None, | 172 | prev_sibling: None, |
185 | is_match_arm: false, | ||
186 | no_completion_required: false, | 173 | no_completion_required: false, |
187 | incomplete_let: false, | 174 | incomplete_let: false, |
175 | attribute_under_caret: None, | ||
188 | locals, | 176 | locals, |
189 | }; | 177 | }; |
190 | 178 | ||
@@ -227,7 +215,6 @@ impl<'a> CompletionContext<'a> { | |||
227 | break; | 215 | break; |
228 | } | 216 | } |
229 | } | 217 | } |
230 | ctx.fill_keyword_patterns(&speculative_file, offset); | ||
231 | ctx.fill(&original_file, speculative_file, offset); | 218 | ctx.fill(&original_file, speculative_file, offset); |
232 | Some(ctx) | 219 | Some(ctx) |
233 | } | 220 | } |
@@ -311,31 +298,13 @@ impl<'a> CompletionContext<'a> { | |||
311 | } | 298 | } |
312 | 299 | ||
313 | pub(crate) fn is_path_disallowed(&self) -> bool { | 300 | pub(crate) fn is_path_disallowed(&self) -> bool { |
314 | self.record_lit_syntax.is_some() | 301 | matches!( |
315 | || self.record_pat_syntax.is_some() | 302 | self.completion_location, |
316 | || self.attribute_under_caret.is_some() | 303 | Some(ImmediateLocation::Attribute(_)) |
317 | || self.mod_declaration_under_caret.is_some() | 304 | | Some(ImmediateLocation::ModDeclaration(_)) |
318 | } | 305 | | Some(ImmediateLocation::RecordPat(_)) |
319 | 306 | | Some(ImmediateLocation::RecordExpr(_)) | |
320 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | 307 | ) || self.attribute_under_caret.is_some() |
321 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
322 | let syntax_element = NodeOrToken::Token(fake_ident_token); | ||
323 | self.previous_token = previous_token(syntax_element.clone()); | ||
324 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
325 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
326 | |||
327 | self.mod_declaration_under_caret = | ||
328 | find_node_at_offset::<ast::Module>(&file_with_fake_ident, offset) | ||
329 | .filter(|module| module.item_list().is_none()); | ||
330 | self.incomplete_let = | ||
331 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { | ||
332 | it.syntax().text_range().end() == syntax_element.text_range().end() | ||
333 | }); | ||
334 | |||
335 | let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | ||
336 | let fn_is_prev = self.previous_token_is(T![fn]); | ||
337 | let for_is_prev2 = for_is_prev2(syntax_element.clone()); | ||
338 | self.no_completion_required = (fn_is_prev && !inside_impl_trait_block) || for_is_prev2; | ||
339 | } | 308 | } |
340 | 309 | ||
341 | fn fill_impl_def(&mut self) { | 310 | fn fill_impl_def(&mut self) { |
@@ -453,25 +422,43 @@ impl<'a> CompletionContext<'a> { | |||
453 | file_with_fake_ident: SyntaxNode, | 422 | file_with_fake_ident: SyntaxNode, |
454 | offset: TextSize, | 423 | offset: TextSize, |
455 | ) { | 424 | ) { |
425 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
426 | let syntax_element = NodeOrToken::Token(fake_ident_token); | ||
427 | self.previous_token = previous_token(syntax_element.clone()); | ||
428 | self.attribute_under_caret = syntax_element.ancestors().find_map(ast::Attr::cast); | ||
429 | self.no_completion_required = { | ||
430 | let inside_impl_trait_block = inside_impl_trait_block(syntax_element.clone()); | ||
431 | let fn_is_prev = self.previous_token_is(T![fn]); | ||
432 | let for_is_prev2 = for_is_prev2(syntax_element.clone()); | ||
433 | (fn_is_prev && !inside_impl_trait_block) || for_is_prev2 | ||
434 | }; | ||
435 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
436 | |||
437 | self.incomplete_let = | ||
438 | syntax_element.ancestors().take(6).find_map(ast::LetStmt::cast).map_or(false, |it| { | ||
439 | it.syntax().text_range().end() == syntax_element.text_range().end() | ||
440 | }); | ||
441 | |||
456 | let (expected_type, expected_name) = self.expected_type_and_name(); | 442 | let (expected_type, expected_name) = self.expected_type_and_name(); |
457 | self.expected_type = expected_type; | 443 | self.expected_type = expected_type; |
458 | self.expected_name = expected_name; | 444 | self.expected_name = expected_name; |
459 | self.attribute_under_caret = find_node_at_offset(&file_with_fake_ident, offset); | 445 | |
460 | let name_like = match find_node_at_offset(&&file_with_fake_ident, offset) { | 446 | let name_like = match find_node_at_offset(&&file_with_fake_ident, offset) { |
461 | Some(it) => it, | 447 | Some(it) => it, |
462 | None => return, | 448 | None => return, |
463 | }; | 449 | }; |
464 | self.completion_location = determine_location(&name_like); | 450 | self.completion_location = |
451 | determine_location(&self.sema, original_file, offset, &name_like); | ||
465 | self.prev_sibling = determine_prev_sibling(&name_like); | 452 | self.prev_sibling = determine_prev_sibling(&name_like); |
466 | match name_like { | 453 | match name_like { |
467 | ast::NameLike::Lifetime(lifetime) => { | 454 | ast::NameLike::Lifetime(lifetime) => { |
468 | self.classify_lifetime(original_file, lifetime, offset); | 455 | self.classify_lifetime(original_file, lifetime, offset); |
469 | } | 456 | } |
470 | ast::NameLike::NameRef(name_ref) => { | 457 | ast::NameLike::NameRef(name_ref) => { |
471 | self.classify_name_ref(original_file, name_ref, offset); | 458 | self.classify_name_ref(original_file, name_ref); |
472 | } | 459 | } |
473 | ast::NameLike::Name(name) => { | 460 | ast::NameLike::Name(name) => { |
474 | self.classify_name(original_file, name, offset); | 461 | self.classify_name(name); |
475 | } | 462 | } |
476 | } | 463 | } |
477 | } | 464 | } |
@@ -505,7 +492,7 @@ impl<'a> CompletionContext<'a> { | |||
505 | } | 492 | } |
506 | } | 493 | } |
507 | 494 | ||
508 | fn classify_name(&mut self, original_file: &SyntaxNode, name: ast::Name, offset: TextSize) { | 495 | fn classify_name(&mut self, name: ast::Name) { |
509 | if let Some(bind_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | 496 | if let Some(bind_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { |
510 | self.is_pat_or_const = Some(PatternRefutability::Refutable); | 497 | self.is_pat_or_const = Some(PatternRefutability::Refutable); |
511 | // if any of these is here our bind pat can't be a const pat anymore | 498 | // if any of these is here our bind pat can't be a const pat anymore |
@@ -543,28 +530,12 @@ impl<'a> CompletionContext<'a> { | |||
543 | 530 | ||
544 | self.fill_impl_def(); | 531 | self.fill_impl_def(); |
545 | } | 532 | } |
533 | |||
546 | self.is_param |= is_node::<ast::Param>(name.syntax()); | 534 | self.is_param |= is_node::<ast::Param>(name.syntax()); |
547 | if ast::RecordPatField::for_field_name(&name).is_some() { | ||
548 | self.record_pat_syntax = | ||
549 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
550 | } | ||
551 | } | 535 | } |
552 | 536 | ||
553 | fn classify_name_ref( | 537 | fn classify_name_ref(&mut self, original_file: &SyntaxNode, name_ref: ast::NameRef) { |
554 | &mut self, | ||
555 | original_file: &SyntaxNode, | ||
556 | name_ref: ast::NameRef, | ||
557 | offset: TextSize, | ||
558 | ) { | ||
559 | self.fill_impl_def(); | 538 | self.fill_impl_def(); |
560 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | ||
561 | self.record_lit_syntax = | ||
562 | self.sema.find_node_at_offset_with_macros(original_file, offset); | ||
563 | } | ||
564 | if ast::RecordPatField::for_field_name_ref(&name_ref).is_some() { | ||
565 | self.record_pat_syntax = | ||
566 | self.sema.find_node_at_offset_with_macros(&original_file, offset); | ||
567 | } | ||
568 | 539 | ||
569 | self.name_ref_syntax = | 540 | self.name_ref_syntax = |
570 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); | 541 | find_node_at_offset(original_file, name_ref.syntax().text_range().start()); |
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index caf0ef39f..26516046b 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs | |||
@@ -1,15 +1,18 @@ | |||
1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context | 1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context |
2 | 2 | ||
3 | use hir::Semantics; | ||
4 | use ide_db::RootDatabase; | ||
3 | use syntax::{ | 5 | use syntax::{ |
4 | algo::non_trivia_sibling, | 6 | algo::non_trivia_sibling, |
5 | ast::{self, LoopBodyOwner}, | 7 | ast::{self, LoopBodyOwner}, |
6 | match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, | 8 | match_ast, AstNode, Direction, SyntaxElement, |
7 | SyntaxKind::*, | 9 | SyntaxKind::*, |
8 | SyntaxNode, SyntaxToken, T, | 10 | SyntaxNode, SyntaxToken, TextSize, T, |
9 | }; | 11 | }; |
10 | 12 | ||
11 | #[cfg(test)] | 13 | #[cfg(test)] |
12 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; | 14 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; |
15 | |||
13 | /// Direct parent container of the cursor position | 16 | /// Direct parent container of the cursor position |
14 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | 17 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
15 | pub(crate) enum ImmediatePrevSibling { | 18 | pub(crate) enum ImmediatePrevSibling { |
@@ -19,7 +22,7 @@ pub(crate) enum ImmediatePrevSibling { | |||
19 | } | 22 | } |
20 | 23 | ||
21 | /// Direct parent container of the cursor position | 24 | /// Direct parent container of the cursor position |
22 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | 25 | #[derive(Clone, Debug, PartialEq, Eq)] |
23 | pub(crate) enum ImmediateLocation { | 26 | pub(crate) enum ImmediateLocation { |
24 | Use, | 27 | Use, |
25 | Impl, | 28 | Impl, |
@@ -29,10 +32,24 @@ pub(crate) enum ImmediateLocation { | |||
29 | IdentPat, | 32 | IdentPat, |
30 | BlockExpr, | 33 | BlockExpr, |
31 | ItemList, | 34 | ItemList, |
35 | // Fake file ast node | ||
36 | Attribute(ast::Attr), | ||
37 | // Fake file ast node | ||
38 | ModDeclaration(ast::Module), | ||
39 | // Original file ast node | ||
40 | /// The record expr of the field name we are completing | ||
41 | RecordExpr(ast::RecordExpr), | ||
42 | // Original file ast node | ||
43 | /// The record pat of the field name we are completing | ||
44 | RecordPat(ast::RecordPat), | ||
32 | } | 45 | } |
33 | 46 | ||
34 | pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { | 47 | pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { |
35 | let node = maximize_name_ref(name_like)?; | 48 | let node = match name_like { |
49 | ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref), | ||
50 | ast::NameLike::Name(n) => n.syntax().clone(), | ||
51 | ast::NameLike::Lifetime(lt) => lt.syntax().clone(), | ||
52 | }; | ||
36 | let node = match node.parent().and_then(ast::MacroCall::cast) { | 53 | let node = match node.parent().and_then(ast::MacroCall::cast) { |
37 | // When a path is being typed after the name of a trait/type of an impl it is being | 54 | // When a path is being typed after the name of a trait/type of an impl it is being |
38 | // parsed as a macro, so when the trait/impl has a block following it an we are between the | 55 | // parsed as a macro, so when the trait/impl has a block following it an we are between the |
@@ -77,8 +94,37 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<Immedi | |||
77 | Some(res) | 94 | Some(res) |
78 | } | 95 | } |
79 | 96 | ||
80 | pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> { | 97 | pub(crate) fn determine_location( |
81 | let node = maximize_name_ref(name_like)?; | 98 | sema: &Semantics<RootDatabase>, |
99 | original_file: &SyntaxNode, | ||
100 | offset: TextSize, | ||
101 | name_like: &ast::NameLike, | ||
102 | ) -> Option<ImmediateLocation> { | ||
103 | let node = match name_like { | ||
104 | ast::NameLike::NameRef(name_ref) => { | ||
105 | if ast::RecordExprField::for_field_name(&name_ref).is_some() { | ||
106 | return sema | ||
107 | .find_node_at_offset_with_macros(original_file, offset) | ||
108 | .map(ImmediateLocation::RecordExpr); | ||
109 | } | ||
110 | if ast::RecordPatField::for_field_name_ref(&name_ref).is_some() { | ||
111 | return sema | ||
112 | .find_node_at_offset_with_macros(original_file, offset) | ||
113 | .map(ImmediateLocation::RecordPat); | ||
114 | } | ||
115 | maximize_name_ref(name_ref) | ||
116 | } | ||
117 | ast::NameLike::Name(name) => { | ||
118 | if ast::RecordPatField::for_field_name(&name).is_some() { | ||
119 | return sema | ||
120 | .find_node_at_offset_with_macros(original_file, offset) | ||
121 | .map(ImmediateLocation::RecordPat); | ||
122 | } | ||
123 | name.syntax().clone() | ||
124 | } | ||
125 | ast::NameLike::Lifetime(lt) => lt.syntax().clone(), | ||
126 | }; | ||
127 | |||
82 | let parent = match node.parent() { | 128 | let parent = match node.parent() { |
83 | Some(parent) => match ast::MacroCall::cast(parent.clone()) { | 129 | Some(parent) => match ast::MacroCall::cast(parent.clone()) { |
84 | // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. | 130 | // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. |
@@ -103,6 +149,7 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateL | |||
103 | } | 149 | } |
104 | } | 150 | } |
105 | }; | 151 | }; |
152 | |||
106 | let res = match_ast! { | 153 | let res = match_ast! { |
107 | match parent { | 154 | match parent { |
108 | ast::IdentPat(_it) => ImmediateLocation::IdentPat, | 155 | ast::IdentPat(_it) => ImmediateLocation::IdentPat, |
@@ -117,36 +164,34 @@ pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateL | |||
117 | Some(TRAIT) => ImmediateLocation::Trait, | 164 | Some(TRAIT) => ImmediateLocation::Trait, |
118 | _ => return None, | 165 | _ => return None, |
119 | }, | 166 | }, |
167 | ast::Module(it) => if it.item_list().is_none() { | ||
168 | ImmediateLocation::ModDeclaration(it) | ||
169 | } else { | ||
170 | return None | ||
171 | }, | ||
172 | ast::Attr(it) => ImmediateLocation::Attribute(it), | ||
120 | _ => return None, | 173 | _ => return None, |
121 | } | 174 | } |
122 | }; | 175 | }; |
123 | Some(res) | 176 | Some(res) |
124 | } | 177 | } |
125 | 178 | ||
126 | fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> { | 179 | fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode { |
127 | // First walk the element we are completing up to its highest node that has the same text range | 180 | // Maximize a nameref to its enclosing path if its the last segment of said path |
128 | // as the element so that we can check in what context it immediately lies. We only do this for | 181 | if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { |
129 | // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. | 182 | let p = segment.parent_path(); |
130 | // We only wanna do this if the NameRef is the last segment of the path. | 183 | if p.parent_path().is_none() { |
131 | let node = match name_like { | 184 | if let Some(it) = p |
132 | ast::NameLike::NameRef(name_ref) => { | 185 | .syntax() |
133 | if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { | 186 | .ancestors() |
134 | let p = segment.parent_path(); | 187 | .take_while(|it| it.text_range() == p.syntax().text_range()) |
135 | if p.parent_path().is_none() { | 188 | .last() |
136 | p.syntax() | 189 | { |
137 | .ancestors() | 190 | return it; |
138 | .take_while(|it| it.text_range() == p.syntax().text_range()) | ||
139 | .last()? | ||
140 | } else { | ||
141 | return None; | ||
142 | } | ||
143 | } else { | ||
144 | return None; | ||
145 | } | 191 | } |
146 | } | 192 | } |
147 | it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(), | 193 | } |
148 | }; | 194 | name_ref.syntax().clone() |
149 | Some(node) | ||
150 | } | 195 | } |
151 | 196 | ||
152 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { | 197 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { |
@@ -167,18 +212,6 @@ fn test_inside_impl_trait_block() { | |||
167 | check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); | 212 | check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); |
168 | } | 213 | } |
169 | 214 | ||
170 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | ||
171 | not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() | ||
172 | && previous_sibling_or_ancestor_sibling(element) | ||
173 | .and_then(|it| it.into_token()) | ||
174 | .filter(|it| it.kind() == FAT_ARROW) | ||
175 | .is_some() | ||
176 | } | ||
177 | #[test] | ||
178 | fn test_is_match_arm() { | ||
179 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm); | ||
180 | } | ||
181 | |||
182 | pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> { | 215 | pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> { |
183 | element.into_token().and_then(|it| previous_non_trivia_token(it)) | 216 | element.into_token().and_then(|it| previous_non_trivia_token(it)) |
184 | } | 217 | } |
@@ -216,10 +249,6 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | |||
216 | .is_some() | 249 | .is_some() |
217 | } | 250 | } |
218 | 251 | ||
219 | pub(crate) fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | ||
220 | element.ancestors().skip_while(|it| it.text_range() == element.text_range()).next() | ||
221 | } | ||
222 | |||
223 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | 252 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { |
224 | let mut token = token.prev_token(); | 253 | let mut token = token.prev_token(); |
225 | while let Some(inner) = token.clone() { | 254 | while let Some(inner) = token.clone() { |
@@ -232,31 +261,25 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | |||
232 | None | 261 | None |
233 | } | 262 | } |
234 | 263 | ||
235 | fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { | ||
236 | let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); | ||
237 | if let Some(sibling) = token_sibling { | ||
238 | Some(sibling) | ||
239 | } else { | ||
240 | // if not trying to find first ancestor which has such a sibling | ||
241 | let range = element.text_range(); | ||
242 | let top_node = element.ancestors().take_while(|it| it.text_range() == range).last()?; | ||
243 | let prev_sibling_node = top_node.ancestors().find(|it| { | ||
244 | non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() | ||
245 | })?; | ||
246 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | ||
247 | } | ||
248 | } | ||
249 | |||
250 | #[cfg(test)] | 264 | #[cfg(test)] |
251 | mod tests { | 265 | mod tests { |
266 | use syntax::algo::find_node_at_offset; | ||
267 | |||
268 | use crate::test_utils::position; | ||
269 | |||
252 | use super::*; | 270 | use super::*; |
253 | 271 | ||
254 | fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) { | 272 | fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) { |
255 | check_pattern_is_applicable(code, |e| { | 273 | let (db, pos) = position(code); |
256 | let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike"); | 274 | |
257 | assert_eq!(determine_location(name), loc.into()); | 275 | let sema = Semantics::new(&db); |
258 | true | 276 | let original_file = sema.parse(pos.file_id); |
259 | }); | 277 | |
278 | let name_like = find_node_at_offset(original_file.syntax(), pos.offset).unwrap(); | ||
279 | assert_eq!( | ||
280 | determine_location(&sema, original_file.syntax(), pos.offset, &name_like), | ||
281 | loc.into() | ||
282 | ); | ||
260 | } | 283 | } |
261 | 284 | ||
262 | fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) { | 285 | fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) { |