aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion/src')
-rw-r--r--crates/ide_completion/src/completions/fn_param.rs15
-rw-r--r--crates/ide_completion/src/completions/keyword.rs18
-rw-r--r--crates/ide_completion/src/completions/mod_.rs6
-rw-r--r--crates/ide_completion/src/completions/record.rs20
-rw-r--r--crates/ide_completion/src/context.rs99
-rw-r--r--crates/ide_completion/src/patterns.rs151
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#"
136struct Bar { bar: u32 }
137
138fn foo(Bar { bar }: Bar) {}
139fn 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
5use syntax::{SyntaxKind, T}; 5use syntax::{SyntaxKind, T};
6 6
7use crate::{CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions}; 7use crate::{
8 patterns::ImmediateLocation, CompletionContext, CompletionItem, CompletionItemKind,
9 CompletionKind, Completions,
10};
8 11
9pub(crate) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { 12pub(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};
10use rustc_hash::FxHashSet; 10use rustc_hash::FxHashSet;
11 11
12use crate::CompletionItem; 12use crate::{patterns::ImmediateLocation, CompletionItem};
13 13
14use crate::{context::CompletionContext, item::CompletionKind, Completions}; 14use crate::{context::CompletionContext, item::CompletionKind, Completions};
15 15
16/// Complete mod declaration, i.e. `mod $0 ;` 16/// Complete mod declaration, i.e. `mod $0 ;`
17pub(crate) fn complete_mod(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 17pub(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 @@
2use ide_db::{helpers::FamousDefs, SymbolKind}; 2use ide_db::{helpers::FamousDefs, SymbolKind};
3use syntax::ast::Expr; 3use syntax::ast::Expr;
4 4
5use crate::{item::CompletionKind, CompletionContext, CompletionItem, Completions}; 5use crate::{
6 item::CompletionKind, patterns::ImmediateLocation, CompletionContext, CompletionItem,
7 Completions,
8};
6 9
7pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> { 10pub(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;
18use crate::{ 18use 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
3use hir::Semantics;
4use ide_db::RootDatabase;
3use syntax::{ 5use 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)]
12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; 14use 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)]
15pub(crate) enum ImmediatePrevSibling { 18pub(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)]
23pub(crate) enum ImmediateLocation { 26pub(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
34pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> { 47pub(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
80pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> { 97pub(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
126fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> { 179fn 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
152pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 197pub(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
170pub(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]
178fn test_is_match_arm() {
179 check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm);
180}
181
182pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> { 215pub(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
219pub(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
223fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { 252fn 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
235fn 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)]
251mod tests { 265mod 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>>) {