aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_completion/src/patterns.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_completion/src/patterns.rs')
-rw-r--r--crates/ide_completion/src/patterns.rs405
1 files changed, 294 insertions, 111 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 04f2c532b..26516046b 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -1,38 +1,197 @@
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::{self, *}, 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};
13 15
14pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { 16/// Direct parent container of the cursor position
15 not_same_range_ancestor(element) 17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16 .filter(|it| it.kind() == ASSOC_ITEM_LIST) 18pub(crate) enum ImmediatePrevSibling {
17 .and_then(|it| it.parent()) 19 IfExpr,
18 .filter(|it| it.kind() == TRAIT) 20 TraitDefName,
19 .is_some() 21 ImplDefType,
20} 22}
21#[test] 23
22fn test_has_trait_parent() { 24/// Direct parent container of the cursor position
23 check_pattern_is_applicable(r"trait A { f$0 }", has_trait_parent); 25#[derive(Clone, Debug, PartialEq, Eq)]
26pub(crate) enum ImmediateLocation {
27 Use,
28 Impl,
29 Trait,
30 RecordField,
31 RefExpr,
32 IdentPat,
33 BlockExpr,
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),
24} 45}
25 46
26pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { 47pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
27 not_same_range_ancestor(element) 48 let node = match name_like {
28 .filter(|it| it.kind() == ASSOC_ITEM_LIST) 49 ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref),
29 .and_then(|it| it.parent()) 50 ast::NameLike::Name(n) => n.syntax().clone(),
30 .filter(|it| it.kind() == IMPL) 51 ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
31 .is_some() 52 };
53 let node = match node.parent().and_then(ast::MacroCall::cast) {
54 // When a path is being typed after the name of a trait/type of an impl it is being
55 // parsed as a macro, so when the trait/impl has a block following it an we are between the
56 // name and block the macro will attach the block to itself so maximizing fails to take
57 // that into account
58 // FIXME path expr and statement have a similar problem with attrs
59 Some(call)
60 if call.excl_token().is_none()
61 && call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
62 && call.semicolon_token().is_none() =>
63 {
64 call.syntax().clone()
65 }
66 _ => node,
67 };
68 let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
69 let res = match_ast! {
70 match prev_sibling {
71 ast::ExprStmt(it) => {
72 let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone();
73 match_ast! {
74 match node {
75 ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
76 _ => return None,
77 }
78 }
79 },
80 ast::Trait(it) => if it.assoc_item_list().is_none() {
81 ImmediatePrevSibling::TraitDefName
82 } else {
83 return None
84 },
85 ast::Impl(it) => if it.assoc_item_list().is_none()
86 && (it.for_token().is_none() || it.self_ty().is_some()) {
87 ImmediatePrevSibling::ImplDefType
88 } else {
89 return None
90 },
91 _ => return None,
92 }
93 };
94 Some(res)
32} 95}
33#[test] 96
34fn test_has_impl_parent() { 97pub(crate) fn determine_location(
35 check_pattern_is_applicable(r"impl A { f$0 }", has_impl_parent); 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
128 let parent = match node.parent() {
129 Some(parent) => match ast::MacroCall::cast(parent.clone()) {
130 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
131 // This is usually fine as the node expansion code above already accounts for that with
132 // the ancestors call, but there is one exception to this which is that when an attribute
133 // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ.
134 // FIXME path expr and statement have a similar problem
135 Some(call)
136 if call.excl_token().is_none()
137 && call.token_tree().is_none()
138 && call.semicolon_token().is_none() =>
139 {
140 call.syntax().parent()?
141 }
142 _ => parent,
143 },
144 // SourceFile
145 None => {
146 return match node.kind() {
147 MACRO_ITEMS | SOURCE_FILE => Some(ImmediateLocation::ItemList),
148 _ => None,
149 }
150 }
151 };
152
153 let res = match_ast! {
154 match parent {
155 ast::IdentPat(_it) => ImmediateLocation::IdentPat,
156 ast::Use(_it) => ImmediateLocation::Use,
157 ast::BlockExpr(_it) => ImmediateLocation::BlockExpr,
158 ast::SourceFile(_it) => ImmediateLocation::ItemList,
159 ast::ItemList(_it) => ImmediateLocation::ItemList,
160 ast::RefExpr(_it) => ImmediateLocation::RefExpr,
161 ast::RecordField(_it) => ImmediateLocation::RecordField,
162 ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) {
163 Some(IMPL) => ImmediateLocation::Impl,
164 Some(TRAIT) => ImmediateLocation::Trait,
165 _ => return None,
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),
173 _ => return None,
174 }
175 };
176 Some(res)
177}
178
179fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode {
180 // Maximize a nameref to its enclosing path if its the last segment of said path
181 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
182 let p = segment.parent_path();
183 if p.parent_path().is_none() {
184 if let Some(it) = p
185 .syntax()
186 .ancestors()
187 .take_while(|it| it.text_range() == p.syntax().text_range())
188 .last()
189 {
190 return it;
191 }
192 }
193 }
194 name_ref.syntax().clone()
36} 195}
37 196
38pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 197pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
@@ -53,68 +212,6 @@ fn test_inside_impl_trait_block() {
53 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);
54} 213}
55 214
56pub(crate) fn has_field_list_parent(element: SyntaxElement) -> bool {
57 not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some()
58}
59#[test]
60fn test_has_field_list_parent() {
61 check_pattern_is_applicable(r"struct Foo { f$0 }", has_field_list_parent);
62 check_pattern_is_applicable(r"struct Foo { f$0 pub f: i32}", has_field_list_parent);
63}
64
65pub(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]
69fn test_has_block_expr_parent() {
70 check_pattern_is_applicable(r"fn my_fn() { let a = 2; f$0 }", has_block_expr_parent);
71}
72
73pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool {
74 element.ancestors().any(|it| it.kind() == IDENT_PAT)
75}
76
77#[test]
78fn test_has_bind_pat_parent() {
79 check_pattern_is_applicable(r"fn my_fn(m$0) {}", has_bind_pat_parent);
80 check_pattern_is_applicable(r"fn my_fn() { let m$0 }", has_bind_pat_parent);
81}
82
83pub(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]
89fn test_has_ref_parent() {
90 check_pattern_is_applicable(r"fn my_fn(&m$0) {}", has_ref_parent);
91 check_pattern_is_applicable(r"fn my() { let &m$0 }", has_ref_parent);
92}
93
94pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool {
95 match not_same_range_ancestor(element) {
96 Some(it) => it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST,
97 None => true,
98 }
99}
100#[test]
101fn test_has_item_list_or_source_file_parent() {
102 check_pattern_is_applicable(r"i$0", has_item_list_or_source_file_parent);
103 check_pattern_is_applicable(r"mod foo { f$0 }", has_item_list_or_source_file_parent);
104}
105
106pub(crate) fn is_match_arm(element: SyntaxElement) -> bool {
107 not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some()
108 && previous_sibling_or_ancestor_sibling(element)
109 .and_then(|it| it.into_token())
110 .filter(|it| it.kind() == FAT_ARROW)
111 .is_some()
112}
113#[test]
114fn test_is_match_arm() {
115 check_pattern_is_applicable(r"fn my_fn() { match () { () => m$0 } }", is_match_arm);
116}
117
118pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> { 215pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
119 element.into_token().and_then(|it| previous_non_trivia_token(it)) 216 element.into_token().and_then(|it| previous_non_trivia_token(it))
120} 217}
@@ -134,14 +231,6 @@ fn test_for_is_prev2() {
134 check_pattern_is_applicable(r"for i i$0", for_is_prev2); 231 check_pattern_is_applicable(r"for i i$0", for_is_prev2);
135} 232}
136 233
137pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool {
138 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some()
139}
140#[test]
141fn test_has_impl_as_prev_sibling() {
142 check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL));
143}
144
145pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { 234pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
146 element 235 element
147 .ancestors() 236 .ancestors()
@@ -160,14 +249,6 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
160 .is_some() 249 .is_some()
161} 250}
162 251
163fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
164 element
165 .ancestors()
166 .take_while(|it| it.text_range() == element.text_range())
167 .last()
168 .and_then(|it| it.parent())
169}
170
171fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { 252fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
172 let mut token = token.prev_token(); 253 let mut token = token.prev_token();
173 while let Some(inner) = token.clone() { 254 while let Some(inner) = token.clone() {
@@ -180,17 +261,119 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
180 None 261 None
181} 262}
182 263
183fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { 264#[cfg(test)]
184 let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); 265mod tests {
185 if let Some(sibling) = token_sibling { 266 use syntax::algo::find_node_at_offset;
186 Some(sibling) 267
187 } else { 268 use crate::test_utils::position;
188 // if not trying to find first ancestor which has such a sibling 269
189 let range = element.text_range(); 270 use super::*;
190 let top_node = element.ancestors().take_while(|it| it.text_range() == range).last()?; 271
191 let prev_sibling_node = top_node.ancestors().find(|it| { 272 fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) {
192 non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() 273 let (db, pos) = position(code);
193 })?; 274
194 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) 275 let sema = Semantics::new(&db);
276 let original_file = sema.parse(pos.file_id);
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 );
283 }
284
285 fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {
286 check_pattern_is_applicable(code, |e| {
287 let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
288 assert_eq!(determine_prev_sibling(name), sibling.into());
289 true
290 });
291 }
292
293 #[test]
294 fn test_trait_loc() {
295 check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
296 check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait);
297 check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait);
298 check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait);
299 check_location(r"trait A$0 {}", None);
300 check_location(r"trait A { fn f$0 }", None);
301 }
302
303 #[test]
304 fn test_impl_loc() {
305 check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
306 check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl);
307 check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl);
308 check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl);
309 check_location(r"impl A$0 {}", None);
310 check_location(r"impl A { fn f$0 }", None);
311 }
312
313 #[test]
314 fn test_use_loc() {
315 check_location(r"use f$0", ImmediateLocation::Use);
316 check_location(r"use f$0;", ImmediateLocation::Use);
317 check_location(r"use f::{f$0}", None);
318 check_location(r"use {f$0}", None);
319 }
320
321 #[test]
322 fn test_record_field_loc() {
323 check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
324 check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
325 check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField);
326 }
327
328 #[test]
329 fn test_block_expr_loc() {
330 check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
331 check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::BlockExpr);
332 }
333
334 #[test]
335 fn test_ident_pat_loc() {
336 check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
337 check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
338 check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
339 check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
340 }
341
342 #[test]
343 fn test_ref_expr_loc() {
344 check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
345 }
346
347 #[test]
348 fn test_item_list_loc() {
349 check_location(r"i$0", ImmediateLocation::ItemList);
350 check_location(r"#[attr] i$0", ImmediateLocation::ItemList);
351 check_location(r"fn f() {} i$0", ImmediateLocation::ItemList);
352 check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
353 check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList);
354 check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList);
355 check_location(r"mod foo$0 {}", None);
356 }
357
358 #[test]
359 fn test_impl_prev_sibling() {
360 check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType);
361 check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType);
362 check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType);
363 check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType);
364 check_prev_sibling(r"impl A for w$0 {}", None);
365 check_prev_sibling(r"impl A for w$0", None);
366 }
367
368 #[test]
369 fn test_trait_prev_sibling() {
370 check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
371 check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
372 }
373
374 #[test]
375 fn test_if_expr_prev_sibling() {
376 check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
377 check_prev_sibling(r"fn foo() { if true {}; w$0", None);
195 } 378 }
196} 379}