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.rs478
1 files changed, 308 insertions, 170 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index d82564381..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::*, 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,98 +212,8 @@ 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 { 215pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
57 not_same_range_ancestor(element).filter(|it| it.kind() == RECORD_FIELD_LIST).is_some() 216 element.into_token().and_then(|it| previous_non_trivia_token(it))
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#[test]
77fn test_has_bind_pat_parent() {
78 check_pattern_is_applicable(r"fn my_fn(m$0) {}", has_bind_pat_parent);
79 check_pattern_is_applicable(r"fn my_fn() { let m$0 }", has_bind_pat_parent);
80}
81
82pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool {
83 not_same_range_ancestor(element)
84 .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR)
85 .is_some()
86}
87#[test]
88fn test_has_ref_parent() {
89 check_pattern_is_applicable(r"fn my_fn(&m$0) {}", has_ref_parent);
90 check_pattern_is_applicable(r"fn my() { let &m$0 }", has_ref_parent);
91}
92
93pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool {
94 let ancestor = not_same_range_ancestor(element);
95 if !ancestor.is_some() {
96 return true;
97 }
98 ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some()
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 unsafe_is_prev(element: SyntaxElement) -> bool {
119 element
120 .into_token()
121 .and_then(|it| previous_non_trivia_token(it))
122 .filter(|it| it.kind() == T![unsafe])
123 .is_some()
124}
125#[test]
126fn test_unsafe_is_prev() {
127 check_pattern_is_applicable(r"unsafe i$0", unsafe_is_prev);
128}
129
130pub(crate) fn if_is_prev(element: SyntaxElement) -> bool {
131 element
132 .into_token()
133 .and_then(|it| previous_non_trivia_token(it))
134 .filter(|it| it.kind() == T![if])
135 .is_some()
136}
137
138pub(crate) fn fn_is_prev(element: SyntaxElement) -> bool {
139 element
140 .into_token()
141 .and_then(|it| previous_non_trivia_token(it))
142 .filter(|it| it.kind() == T![fn])
143 .is_some()
144}
145#[test]
146fn test_fn_is_prev() {
147 check_pattern_is_applicable(r"fn l$0", fn_is_prev);
148} 217}
149 218
150/// Check if the token previous to the previous one is `for`. 219/// Check if the token previous to the previous one is `for`.
@@ -162,55 +231,22 @@ fn test_for_is_prev2() {
162 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);
163} 232}
164 233
165#[test]
166fn test_if_is_prev() {
167 check_pattern_is_applicable(r"if l$0", if_is_prev);
168}
169
170pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool {
171 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT).is_some()
172}
173#[test]
174fn test_has_trait_as_prev_sibling() {
175 check_pattern_is_applicable(r"trait A w$0 {}", has_trait_as_prev_sibling);
176}
177
178pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool {
179 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL).is_some()
180}
181#[test]
182fn test_has_impl_as_prev_sibling() {
183 check_pattern_is_applicable(r"impl A w$0 {}", has_impl_as_prev_sibling);
184}
185
186pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { 234pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
187 for node in element.ancestors() {
188 if node.kind() == FN || node.kind() == CLOSURE_EXPR {
189 break;
190 }
191 let loop_body = match_ast! {
192 match node {
193 ast::ForExpr(it) => it.loop_body(),
194 ast::WhileExpr(it) => it.loop_body(),
195 ast::LoopExpr(it) => it.loop_body(),
196 _ => None,
197 }
198 };
199 if let Some(body) = loop_body {
200 if body.syntax().text_range().contains_range(element.text_range()) {
201 return true;
202 }
203 }
204 }
205 false
206}
207
208fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> {
209 element 235 element
210 .ancestors() 236 .ancestors()
211 .take_while(|it| it.text_range() == element.text_range()) 237 .take_while(|it| it.kind() != FN && it.kind() != CLOSURE_EXPR)
212 .last() 238 .find_map(|it| {
213 .and_then(|it| it.parent()) 239 let loop_body = match_ast! {
240 match it {
241 ast::ForExpr(it) => it.loop_body(),
242 ast::WhileExpr(it) => it.loop_body(),
243 ast::LoopExpr(it) => it.loop_body(),
244 _ => None,
245 }
246 };
247 loop_body.filter(|it| it.syntax().text_range().contains_range(element.text_range()))
248 })
249 .is_some()
214} 250}
215 251
216fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { 252fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
@@ -225,17 +261,119 @@ fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
225 None 261 None
226} 262}
227 263
228fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { 264#[cfg(test)]
229 let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); 265mod tests {
230 if let Some(sibling) = token_sibling { 266 use syntax::algo::find_node_at_offset;
231 Some(sibling) 267
232 } else { 268 use crate::test_utils::position;
233 // if not trying to find first ancestor which has such a sibling 269
234 let range = element.text_range(); 270 use super::*;
235 let top_node = element.ancestors().take_while(|it| it.text_range() == range).last()?; 271
236 let prev_sibling_node = top_node.ancestors().find(|it| { 272 fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) {
237 non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() 273 let (db, pos) = position(code);
238 })?; 274
239 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);
240 } 378 }
241} 379}