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.rs265
1 files changed, 186 insertions, 79 deletions
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index c8a88367d..caf0ef39f 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -4,12 +4,19 @@ 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)]
@@ -24,35 +31,61 @@ pub(crate) enum ImmediateLocation {
24 ItemList, 31 ItemList,
25} 32}
26 33
27pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> { 34pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
28 // First walk the element we are completing up to its highest node that has the same text range 35 let node = maximize_name_ref(name_like)?;
29 // as the element so that we can check in what context it immediately lies. We only do this for 36 let node = match node.parent().and_then(ast::MacroCall::cast) {
30 // NameRef -> Path as that's the only thing that makes sense to being "expanded" semantically. 37 // When a path is being typed after the name of a trait/type of an impl it is being
31 // We only wanna do this if the NameRef is the last segment of the path. 38 // parsed as a macro, so when the trait/impl has a block following it an we are between the
32 let node = match tok.parent().and_then(ast::NameLike::cast)? { 39 // name and block the macro will attach the block to itself so maximizing fails to take
33 ast::NameLike::NameRef(name_ref) => { 40 // that into account
34 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) { 41 // FIXME path expr and statement have a similar problem with attrs
35 let p = segment.parent_path(); 42 Some(call)
36 if p.parent_path().is_none() { 43 if call.excl_token().is_none()
37 p.syntax() 44 && call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
38 .ancestors() 45 && call.semicolon_token().is_none() =>
39 .take_while(|it| it.text_range() == p.syntax().text_range()) 46 {
40 .last()? 47 call.syntax().clone()
41 } else { 48 }
42 return None; 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 }
43 } 61 }
44 } else { 62 },
45 return None; 63 ast::Trait(it) => if it.assoc_item_list().is_none() {
46 } 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,
47 } 75 }
48 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
49 }; 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)?;
50 let parent = match node.parent() { 82 let parent = match node.parent() {
51 Some(parent) => match ast::MacroCall::cast(parent.clone()) { 83 Some(parent) => match ast::MacroCall::cast(parent.clone()) {
52 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call. 84 // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
53 // This is usually fine as the node expansion code above already accounts for that with 85 // This is usually fine as the node expansion code above already accounts for that with
54 // the ancestors call, but there is one exception to this which is that when an attribute 86 // the ancestors call, but there is one exception to this which is that when an attribute
55 // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ. 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
56 Some(call) 89 Some(call)
57 if call.excl_token().is_none() 90 if call.excl_token().is_none()
58 && call.token_tree().is_none() 91 && call.token_tree().is_none()
@@ -90,56 +123,30 @@ pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation>
90 Some(res) 123 Some(res)
91} 124}
92 125
93#[cfg(test)] 126fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> {
94fn 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
95 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
96 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.
97 true 130 // We only wanna do this if the NameRef is the last segment of the path.
98 }); 131 let node = match name_like {
99} 132 ast::NameLike::NameRef(name_ref) => {
100 133 if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
101#[test] 134 let p = segment.parent_path();
102fn test_has_trait_parent() { 135 if p.parent_path().is_none() {
103 check_location(r"trait A { f$0 }", ImmediateLocation::Trait); 136 p.syntax()
104} 137 .ancestors()
105 138 .take_while(|it| it.text_range() == p.syntax().text_range())
106#[test] 139 .last()?
107fn test_has_use_parent() { 140 } else {
108 check_location(r"use f$0", ImmediateLocation::Use); 141 return None;
109} 142 }
110 143 } else {
111#[test] 144 return None;
112fn test_has_impl_parent() { 145 }
113 check_location(r"impl A { f$0 }", ImmediateLocation::Impl); 146 }
114} 147 it @ ast::NameLike::Name(_) | it @ ast::NameLike::Lifetime(_) => it.syntax().clone(),
115#[test] 148 };
116fn test_has_field_list_parent() { 149 Some(node)
117 check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
118 check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
119}
120
121#[test]
122fn test_has_block_expr_parent() {
123 check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr);
124}
125
126#[test]
127fn test_has_ident_pat_parent() {
128 check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
129 check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
130 check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
131 check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
132}
133
134#[test]
135fn test_has_ref_expr_parent() {
136 check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
137}
138
139#[test]
140fn test_has_item_list_or_source_file_parent() {
141 check_location(r"i$0", ImmediateLocation::ItemList);
142 check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
143} 150}
144 151
145pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { 152pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
@@ -191,14 +198,6 @@ fn test_for_is_prev2() {
191 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);
192} 199}
193 200
194pub(crate) fn has_prev_sibling(element: SyntaxElement, kind: SyntaxKind) -> bool {
195 previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == kind).is_some()
196}
197#[test]
198fn test_has_impl_as_prev_sibling() {
199 check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL));
200}
201
202pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { 201pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool {
203 element 202 element
204 .ancestors() 203 .ancestors()
@@ -247,3 +246,111 @@ fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<Syntax
247 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) 246 non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev)
248 } 247 }
249} 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}