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.rs276
1 files changed, 202 insertions, 74 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index 19e42ba43..caf0ef39f 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -4,16 +4,24 @@ use syntax::{
4 algo::non_trivia_sibling, 4 algo::non_trivia_sibling,
5 ast::{self, LoopBodyOwner}, 5 ast::{self, LoopBodyOwner},
6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, 6 match_ast, AstNode, Direction, NodeOrToken, SyntaxElement,
7 SyntaxKind::{self, *}, 7 SyntaxKind::*,
8 SyntaxNode, SyntaxToken, T, 8 SyntaxNode, SyntaxToken, T,
9}; 9};
10 10
11#[cfg(test)] 11#[cfg(test)]
12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable}; 12use crate::test_utils::{check_pattern_is_applicable, check_pattern_is_not_applicable};
13/// Direct parent container of the cursor position
14#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub(crate) enum ImmediatePrevSibling {
16 IfExpr,
17 TraitDefName,
18 ImplDefType,
19}
13 20
14/// Direct parent container of the cursor position 21/// Direct parent container of the cursor position
15#[derive(Copy, Clone, Debug, PartialEq, Eq)] 22#[derive(Copy, Clone, Debug, PartialEq, Eq)]
16pub(crate) enum ImmediateLocation { 23pub(crate) enum ImmediateLocation {
24 Use,
17 Impl, 25 Impl,
18 Trait, 26 Trait,
19 RecordField, 27 RecordField,
@@ -23,30 +31,70 @@ pub(crate) enum ImmediateLocation {
23 ItemList, 31 ItemList,
24} 32}
25 33
26pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> { 34pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
27 // First "expand" the element we are completing to its maximum so that we can check in what 35 let node = maximize_name_ref(name_like)?;
28 // context it immediately lies. This for example means if the token is a NameRef at the end of 36 let node = match node.parent().and_then(ast::MacroCall::cast) {
29 // a path, we want to look at where the path is in the tree. 37 // When a path is being typed after the name of a trait/type of an impl it is being
30 let node = match tok.parent().and_then(ast::NameLike::cast)? { 38 // parsed as a macro, so when the trait/impl has a block following it an we are between the
31 ast::NameLike::NameRef(name_ref) => { 39 // name and block the macro will attach the block to itself so maximizing fails to take
32 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { 40 // that into account
33 let p = segment.parent_path(); 41 // FIXME path expr and statement have a similar problem with attrs
34 if p.parent_path().is_none() { 42 Some(call)
35 p.syntax() 43 if call.excl_token().is_none()
36 .ancestors() 44 && call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
37 .take_while(|it| it.text_range() == p.syntax().text_range()) 45 && call.semicolon_token().is_none() =>
38 .last()? 46 {
39 } else { 47 call.syntax().clone()
40 return None; 48 }
49 _ => node,
50 };
51 let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
52 let res = match_ast! {
53 match prev_sibling {
54 ast::ExprStmt(it) => {
55 let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone();
56 match_ast! {
57 match node {
58 ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
59 _ => return None,
60 }
41 } 61 }
42 } else { 62 },
43 return None; 63 ast::Trait(it) => if it.assoc_item_list().is_none() {
44 } 64 ImmediatePrevSibling::TraitDefName
65 } else {
66 return None
67 },
68 ast::Impl(it) => if it.assoc_item_list().is_none()
69 && (it.for_token().is_none() || it.self_ty().is_some()) {
70 ImmediatePrevSibling::ImplDefType
71 } else {
72 return None
73 },
74 _ => return None,
45 } 75 }
46 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
47 }; 76 };
77 Some(res)
78}
79
80pub(crate) fn determine_location(name_like: &ast::NameLike) -> Option<ImmediateLocation> {
81 let node = maximize_name_ref(name_like)?;
48 let parent = match node.parent() { 82 let parent = match node.parent() {
49 Some(parent) => parent, 83 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.
85 // This is usually fine as the node expansion code above already accounts for that with
86 // the ancestors call, but there is one exception to this which is that when an attribute
87 // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ.
88 // FIXME path expr and statement have a similar problem
89 Some(call)
90 if call.excl_token().is_none()
91 && call.token_tree().is_none()
92 && call.semicolon_token().is_none() =>
93 {
94 call.syntax().parent()?
95 }
96 _ => parent,
97 },
50 // SourceFile 98 // SourceFile
51 None => { 99 None => {
52 return match node.kind() { 100 return match node.kind() {
@@ -58,6 +106,7 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation>
58 let res = match_ast! { 106 let res = match_ast! {
59 match parent { 107 match parent {
60 ast::IdentPat(_it) => ImmediateLocation::IdentPat, 108 ast::IdentPat(_it) => ImmediateLocation::IdentPat,
109 ast::Use(_it) => ImmediateLocation::Use,
61 ast::BlockExpr(_it) => ImmediateLocation::BlockExpr, 110 ast::BlockExpr(_it) => ImmediateLocation::BlockExpr,
62 ast::SourceFile(_it) => ImmediateLocation::ItemList, 111 ast::SourceFile(_it) => ImmediateLocation::ItemList,
63 ast::ItemList(_it) => ImmediateLocation::ItemList, 112 ast::ItemList(_it) => ImmediateLocation::ItemList,
@@ -74,51 +123,30 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation>
74 Some(res) 123 Some(res)
75} 124}
76 125
77#[cfg(test)] 126fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> {
78fn check_location(code: &str, loc: ImmediateLocation) { 127 // First walk the element we are completing up to its highest node that has the same text range
79 check_pattern_is_applicable(code, |e| { 128 // as the element so that we can check in what context it immediately lies. We only do this for
80 assert_eq!(determine_location(e.into_token().expect("Expected a token")), Some(loc)); 129 // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically.
81 true 130 // We only wanna do this if the NameRef is the last segment of the path.
82 }); 131 let node = match name_like {
83} 132 ast::NameLike::NameRef(name_ref) => {
84 133 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
85#[test] 134 let p = segment.parent_path();
86fn test_has_trait_parent() { 135 if p.parent_path().is_none() {
87 check_location(r"trait A { f$0 }", ImmediateLocation::Trait); 136 p.syntax()
88} 137 .ancestors()
89 138 .take_while(|it| it.text_range() == p.syntax().text_range())
90#[test] 139 .last()?
91fn test_has_impl_parent() { 140 } else {
92 check_location(r"impl A { f$0 }", ImmediateLocation::Impl); 141 return None;
93} 142 }
94#[test] 143 } else {
95fn test_has_field_list_parent() { 144 return None;
96 check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField); 145 }
97 check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField); 146 }
98} 147 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
99 148 };
100#[test] 149 Some(node)
101fn test_has_block_expr_parent() {
102 check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
103}
104
105#[test]
106fn test_has_ident_pat_parent() {
107 check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
108 check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
109 check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
110 check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
111}
112
113#[test]
114fn test_has_ref_expr_parent() {
115 check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
116}
117
118#[test]
119fn test_has_item_list_or_source_file_parent() {
120 check_location(r"i$0", ImmediateLocation::ItemList);
121 check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
122} 150}
123 151
124pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 152pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
@@ -170,14 +198,6 @@ fn test_for_is_prev2() {
170 check_pattern_is_applicable(r"for i i$0", for_is_prev2); 198 check_pattern_is_applicable(r"for i i$0", for_is_prev2);
171} 199}
172 200
173pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool {
174 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some()
175}
176#[test]
177fn test_has_impl_as_prev_sibling() {
178 check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL));
179}
180
181pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { 201pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
182 element 202 element
183 .ancestors() 203 .ancestors()
@@ -226,3 +246,111 @@ fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<Syntax
226 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) 246 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
227 } 247 }
228} 248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) {
255 check_pattern_is_applicable(code, |e| {
256 let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
257 assert_eq!(determine_location(name), loc.into());
258 true
259 });
260 }
261
262 fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {
263 check_pattern_is_applicable(code, |e| {
264 let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
265 assert_eq!(determine_prev_sibling(name), sibling.into());
266 true
267 });
268 }
269
270 #[test]
271 fn test_trait_loc() {
272 check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
273 check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait);
274 check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait);
275 check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait);
276 check_location(r"trait A$0 {}", None);
277 check_location(r"trait A { fn f$0 }", None);
278 }
279
280 #[test]
281 fn test_impl_loc() {
282 check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
283 check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl);
284 check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl);
285 check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl);
286 check_location(r"impl A$0 {}", None);
287 check_location(r"impl A { fn f$0 }", None);
288 }
289
290 #[test]
291 fn test_use_loc() {
292 check_location(r"use f$0", ImmediateLocation::Use);
293 check_location(r"use f$0;", ImmediateLocation::Use);
294 check_location(r"use f::{f$0}", None);
295 check_location(r"use {f$0}", None);
296 }
297
298 #[test]
299 fn test_record_field_loc() {
300 check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
301 check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
302 check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField);
303 }
304
305 #[test]
306 fn test_block_expr_loc() {
307 check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
308 check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::BlockExpr);
309 }
310
311 #[test]
312 fn test_ident_pat_loc() {
313 check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
314 check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
315 check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
316 check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
317 }
318
319 #[test]
320 fn test_ref_expr_loc() {
321 check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
322 }
323
324 #[test]
325 fn test_item_list_loc() {
326 check_location(r"i$0", ImmediateLocation::ItemList);
327 check_location(r"#[attr] i$0", ImmediateLocation::ItemList);
328 check_location(r"fn f() {} i$0", ImmediateLocation::ItemList);
329 check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
330 check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList);
331 check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList);
332 check_location(r"mod foo$0 {}", None);
333 }
334
335 #[test]
336 fn test_impl_prev_sibling() {
337 check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType);
338 check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType);
339 check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType);
340 check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType);
341 check_prev_sibling(r"impl A for w$0 {}", None);
342 check_prev_sibling(r"impl A for w$0", None);
343 }
344
345 #[test]
346 fn test_trait_prev_sibling() {
347 check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
348 check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
349 }
350
351 #[test]
352 fn test_if_expr_prev_sibling() {
353 check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
354 check_prev_sibling(r"fn foo() { if true {}; w$0", None);
355 }
356}