diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-05-27 17:34:46 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2021-05-27 17:34:46 +0100 |
commit | a2940c42c0ab30e80e1a63494ca17fb2d81bdd1f (patch) | |
tree | 7d755c4edd86c0b031452eb45536eb42d802f65c /crates/ide_completion/src/patterns.rs | |
parent | cc5d8069219a0a52f9c98b6766d2421eaf4664d8 (diff) | |
parent | 3a16950fd919f46fd879c36423810a40105b2c10 (diff) |
Merge #9020
9020: fix: Don't complete non-macro item paths in impls and modules r=Veykril a=Veykril
Part of #8518
bors r+
Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates/ide_completion/src/patterns.rs')
-rw-r--r-- | crates/ide_completion/src/patterns.rs | 173 |
1 files changed, 103 insertions, 70 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index 04f2c532b..ed289d561 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs | |||
@@ -11,96 +11,133 @@ use syntax::{ | |||
11 | #[cfg(test)] | 11 | #[cfg(test)] |
12 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; | 12 | use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; |
13 | 13 | ||
14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | 14 | /// Direct parent container of the cursor position |
15 | not_same_range_ancestor(element) | 15 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
16 | .filter(|it| it.kind() == ASSOC_ITEM_LIST) | 16 | pub(crate) enum ImmediateLocation { |
17 | .and_then(|it| it.parent()) | 17 | Impl, |
18 | .filter(|it| it.kind() == TRAIT) | 18 | Trait, |
19 | .is_some() | 19 | RecordField, |
20 | } | 20 | RefExpr, |
21 | #[test] | 21 | IdentPat, |
22 | fn test_has_trait_parent() { | 22 | BlockExpr, |
23 | check_pattern_is_applicable(r"trait A { f$0 }", has_trait_parent); | 23 | ItemList, |
24 | } | ||
25 | |||
26 | pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> { | ||
27 | // First "expand" the element we are completing to its maximum so that we can check in what | ||
28 | // context it immediately lies. This for example means if the token is a NameRef at the end of | ||
29 | // a path, we want to look at where the path is in the tree. | ||
30 | let node = match tok.parent().and_then(ast::NameLike::cast)? { | ||
31 | ast::NameLike::NameRef(name_ref) => { | ||
32 | if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { | ||
33 | let p = segment.parent_path(); | ||
34 | if p.parent_path().is_none() { | ||
35 | p.syntax() | ||
36 | .ancestors() | ||
37 | .take_while(|it| it.text_range() == p.syntax().text_range()) | ||
38 | .last()? | ||
39 | } else { | ||
40 | return None; | ||
41 | } | ||
42 | } else { | ||
43 | return None; | ||
44 | } | ||
45 | } | ||
46 | it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(), | ||
47 | }; | ||
48 | let parent = match node.parent() { | ||
49 | Some(parent) => parent, | ||
50 | // SourceFile | ||
51 | None => { | ||
52 | return match node.kind() { | ||
53 | MACRO_ITEMS | SOURCE_FILE => Some(ImmediateLocation::ItemList), | ||
54 | _ => None, | ||
55 | } | ||
56 | } | ||
57 | }; | ||
58 | let res = match_ast! { | ||
59 | match parent { | ||
60 | ast::IdentPat(_it) => ImmediateLocation::IdentPat, | ||
61 | ast::BlockExpr(_it) => ImmediateLocation::BlockExpr, | ||
62 | ast::SourceFile(_it) => ImmediateLocation::ItemList, | ||
63 | ast::ItemList(_it) => ImmediateLocation::ItemList, | ||
64 | ast::RefExpr(_it) => ImmediateLocation::RefExpr, | ||
65 | ast::RefPat(_it) => ImmediateLocation::RefExpr, | ||
66 | ast::RecordField(_it) => ImmediateLocation::RecordField, | ||
67 | ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) { | ||
68 | Some(IMPL) => ImmediateLocation::Impl, | ||
69 | Some(TRAIT) => ImmediateLocation::Trait, | ||
70 | _ => return None, | ||
71 | }, | ||
72 | _ => return None, | ||
73 | } | ||
74 | }; | ||
75 | Some(res) | ||
24 | } | 76 | } |
25 | 77 | ||
26 | pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | 78 | #[cfg(test)] |
27 | not_same_range_ancestor(element) | 79 | fn check_location(code: &str, loc: ImmediateLocation) { |
28 | .filter(|it| it.kind() == ASSOC_ITEM_LIST) | 80 | check_pattern_is_applicable(code, |e| { |
29 | .and_then(|it| it.parent()) | 81 | assert_eq!(determine_location(e.into_token().expect("Expected a token")), Some(loc)); |
30 | .filter(|it| it.kind() == IMPL) | 82 | true |
31 | .is_some() | 83 | }); |
32 | } | ||
33 | #[test] | ||
34 | fn test_has_impl_parent() { | ||
35 | check_pattern_is_applicable(r"impl A { f$0 }", has_impl_parent); | ||
36 | } | 84 | } |
37 | 85 | ||
38 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { | ||
39 | // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`, | ||
40 | // where we only check the first parent with different text range. | ||
41 | element | ||
42 | .ancestors() | ||
43 | .find(|it| it.kind() == IMPL) | ||
44 | .map(|it| ast::Impl::cast(it).unwrap()) | ||
45 | .map(|it| it.trait_().is_some()) | ||
46 | .unwrap_or(false) | ||
47 | } | ||
48 | #[test] | 86 | #[test] |
49 | fn test_inside_impl_trait_block() { | 87 | fn test_has_trait_parent() { |
50 | check_pattern_is_applicable(r"impl Foo for Bar { f$0 }", inside_impl_trait_block); | 88 | check_location(r"trait A { f$0 }", ImmediateLocation::Trait); |
51 | check_pattern_is_applicable(r"impl Foo for Bar { fn f$0 }", inside_impl_trait_block); | ||
52 | check_pattern_is_not_applicable(r"impl A { f$0 }", inside_impl_trait_block); | ||
53 | check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); | ||
54 | } | 89 | } |
55 | 90 | ||
56 | pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool { | 91 | #[test] |
57 | not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some() | 92 | fn test_has_impl_parent() { |
93 | check_location(r"impl A { f$0 }", ImmediateLocation::Impl); | ||
58 | } | 94 | } |
59 | #[test] | 95 | #[test] |
60 | fn test_has_field_list_parent() { | 96 | fn test_has_field_list_parent() { |
61 | check_pattern_is_applicable(r"struct Foo { f$0 }", has_field_list_parent); | 97 | check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField); |
62 | check_pattern_is_applicable(r"struct Foo { f$0 pub f: i32}", has_field_list_parent); | 98 | check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField); |
63 | } | 99 | } |
64 | 100 | ||
65 | pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { | ||
66 | not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() | ||
67 | } | ||
68 | #[test] | 101 | #[test] |
69 | fn test_has_block_expr_parent() { | 102 | fn test_has_block_expr_parent() { |
70 | check_pattern_is_applicable(r"fn my_fn() { let a = 2; f$0 }", has_block_expr_parent); | 103 | check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr); |
71 | } | 104 | } |
72 | 105 | ||
73 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | 106 | #[test] |
74 | element.ancestors().any(|it| it.kind() == IDENT_PAT) | 107 | fn test_has_ident_pat_parent() { |
108 | check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat); | ||
109 | check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat); | ||
110 | check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat); | ||
111 | check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat); | ||
75 | } | 112 | } |
76 | 113 | ||
77 | #[test] | 114 | #[test] |
78 | fn test_has_bind_pat_parent() { | 115 | fn test_has_ref_expr_parent() { |
79 | check_pattern_is_applicable(r"fn my_fn(m$0) {}", has_bind_pat_parent); | 116 | check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); |
80 | check_pattern_is_applicable(r"fn my_fn() { let m$0 }", has_bind_pat_parent); | ||
81 | } | 117 | } |
82 | 118 | ||
83 | pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { | ||
84 | not_same_range_ancestor(element) | ||
85 | .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) | ||
86 | .is_some() | ||
87 | } | ||
88 | #[test] | 119 | #[test] |
89 | fn test_has_ref_parent() { | 120 | fn test_has_item_list_or_source_file_parent() { |
90 | check_pattern_is_applicable(r"fn my_fn(&m$0) {}", has_ref_parent); | 121 | check_location(r"i$0", ImmediateLocation::ItemList); |
91 | check_pattern_is_applicable(r"fn my() { let &m$0 }", has_ref_parent); | 122 | check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList); |
92 | } | 123 | } |
93 | 124 | ||
94 | pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { | 125 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { |
95 | match not_same_range_ancestor(element) { | 126 | // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`, |
96 | Some(it) => it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST, | 127 | // where we only check the first parent with different text range. |
97 | None => true, | 128 | element |
98 | } | 129 | .ancestors() |
130 | .find(|it| it.kind() == IMPL) | ||
131 | .map(|it| ast::Impl::cast(it).unwrap()) | ||
132 | .map(|it| it.trait_().is_some()) | ||
133 | .unwrap_or(false) | ||
99 | } | 134 | } |
100 | #[test] | 135 | #[test] |
101 | fn test_has_item_list_or_source_file_parent() { | 136 | fn test_inside_impl_trait_block() { |
102 | check_pattern_is_applicable(r"i$0", has_item_list_or_source_file_parent); | 137 | check_pattern_is_applicable(r"impl Foo for Bar { f$0 }", inside_impl_trait_block); |
103 | check_pattern_is_applicable(r"mod foo { f$0 }", has_item_list_or_source_file_parent); | 138 | check_pattern_is_applicable(r"impl Foo for Bar { fn f$0 }", inside_impl_trait_block); |
139 | check_pattern_is_not_applicable(r"impl A { f$0 }", inside_impl_trait_block); | ||
140 | check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block); | ||
104 | } | 141 | } |
105 | 142 | ||
106 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | 143 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { |
@@ -160,12 +197,8 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | |||
160 | .is_some() | 197 | .is_some() |
161 | } | 198 | } |
162 | 199 | ||
163 | fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | 200 | pub(crate) fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { |
164 | element | 201 | element.ancestors().skip_while(|it| it.text_range() == element.text_range()).next() |
165 | .ancestors() | ||
166 | .take_while(|it| it.text_range() == element.text_range()) | ||
167 | .last() | ||
168 | .and_then(|it| it.parent()) | ||
169 | } | 202 | } |
170 | 203 | ||
171 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | 204 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { |