diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-05-28 22:21:37 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2021-05-28 22:21:37 +0100 |
commit | 7869b01b702d1f12acb03ac131b6eb90ad82e9bb (patch) | |
tree | e6d76df5212db7589ef8dc6583b32d6c64871a33 /crates/ide_completion/src/patterns.rs | |
parent | d5f7b2e52a41a7d3b841f4d0e2225eb703f6a50a (diff) | |
parent | e42c448077ca2b7675320da3c5294bf4bebaedeb (diff) |
Merge #9041
9041: internal: Implement prev sibling determination for `CompletionContext ` r=Veykril a=Veykril
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 | 265 |
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)] |
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 | /// Direct parent container of the cursor position | ||
14 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
15 | pub(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 | ||
27 | pub(crate) fn determine_location(tok: SyntaxToken) -> Option<ImmediateLocation> { | 34 | pub(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 | |||
80 | pub(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)] | 126 | fn maximize_name_ref(name_like: &ast::NameLike) -> Option<SyntaxNode> { |
94 | fn 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(); |
102 | fn 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()? |
107 | fn 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; |
112 | fn 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 | }; |
116 | fn 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] | ||
122 | fn test_has_block_expr_parent() { | ||
123 | check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::BlockExpr); | ||
124 | } | ||
125 | |||
126 | #[test] | ||
127 | fn 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] | ||
135 | fn test_has_ref_expr_parent() { | ||
136 | check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr); | ||
137 | } | ||
138 | |||
139 | #[test] | ||
140 | fn 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 | ||
145 | pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool { | 152 | pub(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 | ||
194 | pub(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] | ||
198 | fn test_has_impl_as_prev_sibling() { | ||
199 | check_pattern_is_applicable(r"impl A w$0 {}", |it| has_prev_sibling(it, IMPL)); | ||
200 | } | ||
201 | |||
202 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | 201 | pub(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)] | ||
251 | mod 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 | } | ||