]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/patterns.rs
Merge #10411
[rust.git] / crates / ide_completion / src / patterns.rs
1 //! Patterns telling us certain facts about current syntax element, they are used in completion context
2 //!
3 //! Most logic in this module first expands the token below the cursor to a maximum node that acts similar to the token itself.
4 //! This means we for example expand a NameRef token to its outermost Path node, as semantically these act in the same location
5 //! and the completions usually query for path specific things on the Path context instead. This simplifies some location handling.
6
7 use hir::Semantics;
8 use ide_db::RootDatabase;
9 use syntax::{
10     algo::non_trivia_sibling,
11     ast::{self, HasArgList, HasLoopBody},
12     match_ast, AstNode, Direction, SyntaxElement,
13     SyntaxKind::*,
14     SyntaxNode, SyntaxToken, TextRange, TextSize, T,
15 };
16
17 #[cfg(test)]
18 use crate::tests::{check_pattern_is_applicable, check_pattern_is_not_applicable};
19
20 /// Immediate previous node to what we are completing.
21 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
22 pub(crate) enum ImmediatePrevSibling {
23     IfExpr,
24     TraitDefName,
25     ImplDefType,
26     Visibility,
27     Attribute,
28 }
29
30 /// Direct parent "thing" of what we are currently completing.
31 #[derive(Clone, Debug, PartialEq, Eq)]
32 pub(crate) enum ImmediateLocation {
33     Use,
34     UseTree,
35     Rename,
36     Impl,
37     Trait,
38     RecordField,
39     TupleField,
40     RefExpr,
41     IdentPat,
42     StmtList,
43     ItemList,
44     TypeBound,
45     // Fake file ast node
46     Attribute(ast::Attr),
47     // Fake file ast node
48     ModDeclaration(ast::Module),
49     Visibility(ast::Visibility),
50     // Original file ast node
51     MethodCall {
52         receiver: Option<ast::Expr>,
53         has_parens: bool,
54     },
55     // Original file ast node
56     FieldAccess {
57         receiver: Option<ast::Expr>,
58         receiver_is_ambiguous_float_literal: bool,
59     },
60     // Original file ast node
61     // Only set from a type arg
62     GenericArgList(ast::GenericArgList),
63     // Original file ast node
64     /// The record expr of the field name we are completing
65     RecordExpr(ast::RecordExpr),
66     // Original file ast node
67     /// The record expr of the functional update syntax we are completing
68     RecordExprUpdate(ast::RecordExpr),
69     // Original file ast node
70     /// The record pat of the field name we are completing
71     RecordPat(ast::RecordPat),
72 }
73
74 pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<ImmediatePrevSibling> {
75     let node = match name_like {
76         ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref),
77         ast::NameLike::Name(n) => n.syntax().clone(),
78         ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
79     };
80     let node = match node.parent().and_then(ast::MacroCall::cast) {
81         // When a path is being typed after the name of a trait/type of an impl it is being
82         // parsed as a macro, so when the trait/impl has a block following it an we are between the
83         // name and block the macro will attach the block to itself so maximizing fails to take
84         // that into account
85         // FIXME path expr and statement have a similar problem with attrs
86         Some(call)
87             if call.excl_token().is_none()
88                 && call.token_tree().map_or(false, |t| t.l_curly_token().is_some())
89                 && call.semicolon_token().is_none() =>
90         {
91             call.syntax().clone()
92         }
93         _ => node,
94     };
95     let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;
96     if prev_sibling.kind() == ERROR {
97         let prev_sibling = prev_sibling.first_child()?;
98         let res = match_ast! {
99             match prev_sibling {
100                 // vis followed by random ident will always error the parser
101                 ast::Visibility(_it) => ImmediatePrevSibling::Visibility,
102                 _ => return None,
103             }
104         };
105         return Some(res);
106     }
107     let res = match_ast! {
108         match prev_sibling {
109             ast::ExprStmt(it) => {
110                 let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone();
111                 match_ast! {
112                     match node {
113                         ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
114                         _ => return None,
115                     }
116                 }
117             },
118             ast::Trait(it) => if it.assoc_item_list().is_none() {
119                     ImmediatePrevSibling::TraitDefName
120                 } else {
121                     return None
122             },
123             ast::Impl(it) => if it.assoc_item_list().is_none()
124                 && (it.for_token().is_none() || it.self_ty().is_some()) {
125                     ImmediatePrevSibling::ImplDefType
126                 } else {
127                     return None
128             },
129             ast::Attr(_it) => ImmediatePrevSibling::Attribute,
130             _ => return None,
131         }
132     };
133     Some(res)
134 }
135
136 pub(crate) fn determine_location(
137     sema: &Semantics<RootDatabase>,
138     original_file: &SyntaxNode,
139     offset: TextSize,
140     name_like: &ast::NameLike,
141 ) -> Option<ImmediateLocation> {
142     let node = match name_like {
143         ast::NameLike::NameRef(name_ref) => {
144             if ast::RecordExprField::for_field_name(name_ref).is_some() {
145                 return sema
146                     .find_node_at_offset_with_macros(original_file, offset)
147                     .map(ImmediateLocation::RecordExpr);
148             }
149             if ast::RecordPatField::for_field_name_ref(name_ref).is_some() {
150                 return sema
151                     .find_node_at_offset_with_macros(original_file, offset)
152                     .map(ImmediateLocation::RecordPat);
153             }
154             maximize_name_ref(name_ref)
155         }
156         ast::NameLike::Name(name) => {
157             if ast::RecordPatField::for_field_name(name).is_some() {
158                 return sema
159                     .find_node_at_offset_with_macros(original_file, offset)
160                     .map(ImmediateLocation::RecordPat);
161             }
162             name.syntax().clone()
163         }
164         ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
165     };
166
167     match_ast! {
168         match node {
169             ast::TypeBoundList(_it) => return Some(ImmediateLocation::TypeBound),
170             _ => (),
171         }
172     };
173
174     let parent = match node.parent() {
175         Some(parent) => match ast::MacroCall::cast(parent.clone()) {
176             // When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
177             // This is usually fine as the node expansion code above already accounts for that with
178             // the ancestors call, but there is one exception to this which is that when an attribute
179             // precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ.
180             // FIXME path expr and statement have a similar problem
181             Some(call)
182                 if call.excl_token().is_none()
183                     && call.token_tree().is_none()
184                     && call.semicolon_token().is_none() =>
185             {
186                 call.syntax().parent()?
187             }
188             _ => parent,
189         },
190         // SourceFile
191         None => {
192             return match node.kind() {
193                 MACRO_ITEMS | SOURCE_FILE => Some(ImmediateLocation::ItemList),
194                 _ => None,
195             }
196         }
197     };
198
199     let res = match_ast! {
200         match parent {
201             ast::IdentPat(_it) => ImmediateLocation::IdentPat,
202             ast::Use(_it) => ImmediateLocation::Use,
203             ast::UseTree(_it) => ImmediateLocation::UseTree,
204             ast::UseTreeList(_it) => ImmediateLocation::UseTree,
205             ast::Rename(_it) => ImmediateLocation::Rename,
206             ast::StmtList(_it) => ImmediateLocation::StmtList,
207             ast::SourceFile(_it) => ImmediateLocation::ItemList,
208             ast::ItemList(_it) => ImmediateLocation::ItemList,
209             ast::RefExpr(_it) => ImmediateLocation::RefExpr,
210             ast::RecordField(it) => if it.ty().map_or(false, |it| it.syntax().text_range().contains(offset)) {
211                 return None;
212             } else {
213                 ImmediateLocation::RecordField
214             },
215             ast::RecordExprFieldList(_it) => sema
216                 .find_node_at_offset_with_macros(original_file, offset)
217                 .map(ImmediateLocation::RecordExprUpdate)?,
218             ast::TupleField(_it) => ImmediateLocation::TupleField,
219             ast::TupleFieldList(_it) => ImmediateLocation::TupleField,
220             ast::TypeBound(_it) => ImmediateLocation::TypeBound,
221             ast::TypeBoundList(_it) => ImmediateLocation::TypeBound,
222             ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) {
223                 Some(IMPL) => ImmediateLocation::Impl,
224                 Some(TRAIT) => ImmediateLocation::Trait,
225                 _ => return None,
226             },
227             ast::GenericArgList(_it) => sema
228                 .find_node_at_offset_with_macros(original_file, offset)
229                 .map(ImmediateLocation::GenericArgList)?,
230             ast::Module(it) => {
231                 if it.item_list().is_none() {
232                     ImmediateLocation::ModDeclaration(it)
233                 } else {
234                     return None;
235                 }
236             },
237             ast::Attr(it) => ImmediateLocation::Attribute(it),
238             ast::FieldExpr(it) => {
239                 let receiver = it
240                     .expr()
241                     .map(|e| e.syntax().text_range())
242                     .and_then(|r| find_node_with_range(original_file, r));
243                 let receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = &receiver {
244                     match l.kind() {
245                         ast::LiteralKind::FloatNumber { .. } => l.token().text().ends_with('.'),
246                         _ => false,
247                     }
248                 } else {
249                     false
250                 };
251                 ImmediateLocation::FieldAccess {
252                     receiver,
253                     receiver_is_ambiguous_float_literal,
254                 }
255             },
256             ast::MethodCallExpr(it) => ImmediateLocation::MethodCall {
257                 receiver: it
258                     .receiver()
259                     .map(|e| e.syntax().text_range())
260                     .and_then(|r| find_node_with_range(original_file, r)),
261                 has_parens: it.arg_list().map_or(false, |it| it.l_paren_token().is_some())
262             },
263             ast::Visibility(it) => it.pub_token()
264                 .and_then(|t| (t.text_range().end() < offset).then(|| ImmediateLocation::Visibility(it)))?,
265             _ => return None,
266         }
267     };
268     Some(res)
269 }
270
271 fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode {
272     // Maximize a nameref to its enclosing path if its the last segment of said path
273     if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
274         let p = segment.parent_path();
275         if p.parent_path().is_none() {
276             if let Some(it) = p
277                 .syntax()
278                 .ancestors()
279                 .take_while(|it| it.text_range() == p.syntax().text_range())
280                 .last()
281             {
282                 return it;
283             }
284         }
285     }
286     name_ref.syntax().clone()
287 }
288
289 fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
290     let range = syntax.text_range().intersect(range)?;
291     syntax.covering_element(range).ancestors().find_map(N::cast)
292 }
293
294 pub(crate) fn inside_impl_trait_block(element: SyntaxElement) -> bool {
295     // Here we search `impl` keyword up through the all ancestors, unlike in `has_impl_parent`,
296     // where we only check the first parent with different text range.
297     element
298         .ancestors()
299         .find(|it| it.kind() == IMPL)
300         .map(|it| ast::Impl::cast(it).unwrap())
301         .map(|it| it.trait_().is_some())
302         .unwrap_or(false)
303 }
304 #[test]
305 fn test_inside_impl_trait_block() {
306     check_pattern_is_applicable(r"impl Foo for Bar { f$0 }", inside_impl_trait_block);
307     check_pattern_is_applicable(r"impl Foo for Bar { fn f$0 }", inside_impl_trait_block);
308     check_pattern_is_not_applicable(r"impl A { f$0 }", inside_impl_trait_block);
309     check_pattern_is_not_applicable(r"impl A { fn f$0 }", inside_impl_trait_block);
310 }
311
312 pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
313     element.into_token().and_then(previous_non_trivia_token)
314 }
315
316 /// Check if the token previous to the previous one is `for`.
317 /// For example, `for _ i$0` => true.
318 pub(crate) fn for_is_prev2(element: SyntaxElement) -> bool {
319     element
320         .into_token()
321         .and_then(previous_non_trivia_token)
322         .and_then(previous_non_trivia_token)
323         .filter(|it| it.kind() == T![for])
324         .is_some()
325 }
326 #[test]
327 fn test_for_is_prev2() {
328     check_pattern_is_applicable(r"for i i$0", for_is_prev2);
329 }
330
331 pub(crate) fn is_in_loop_body(node: &SyntaxNode) -> bool {
332     node.ancestors()
333         .take_while(|it| it.kind() != FN && it.kind() != CLOSURE_EXPR)
334         .find_map(|it| {
335             let loop_body = match_ast! {
336                 match it {
337                     ast::ForExpr(it) => it.loop_body(),
338                     ast::WhileExpr(it) => it.loop_body(),
339                     ast::LoopExpr(it) => it.loop_body(),
340                     _ => None,
341                 }
342             };
343             loop_body.filter(|it| it.syntax().text_range().contains_range(node.text_range()))
344         })
345         .is_some()
346 }
347
348 fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> {
349     let mut token = token.prev_token();
350     while let Some(inner) = token.clone() {
351         if !inner.kind().is_trivia() {
352             return Some(inner);
353         } else {
354             token = inner.prev_token();
355         }
356     }
357     None
358 }
359
360 #[cfg(test)]
361 mod tests {
362     use syntax::algo::find_node_at_offset;
363
364     use crate::tests::position;
365
366     use super::*;
367
368     fn check_location(code: &str, loc: impl Into<Option<ImmediateLocation>>) {
369         let (db, pos) = position(code);
370
371         let sema = Semantics::new(&db);
372         let original_file = sema.parse(pos.file_id);
373
374         let name_like = find_node_at_offset(original_file.syntax(), pos.offset).unwrap();
375         assert_eq!(
376             determine_location(&sema, original_file.syntax(), pos.offset, &name_like),
377             loc.into()
378         );
379     }
380
381     fn check_prev_sibling(code: &str, sibling: impl Into<Option<ImmediatePrevSibling>>) {
382         check_pattern_is_applicable(code, |e| {
383             let name = &e.parent().and_then(ast::NameLike::cast).expect("Expected a namelike");
384             assert_eq!(determine_prev_sibling(name), sibling.into());
385             true
386         });
387     }
388
389     #[test]
390     fn test_trait_loc() {
391         check_location(r"trait A { f$0 }", ImmediateLocation::Trait);
392         check_location(r"trait A { #[attr] f$0 }", ImmediateLocation::Trait);
393         check_location(r"trait A { f$0 fn f() {} }", ImmediateLocation::Trait);
394         check_location(r"trait A { fn f() {} f$0 }", ImmediateLocation::Trait);
395         check_location(r"trait A$0 {}", None);
396         check_location(r"trait A { fn f$0 }", None);
397     }
398
399     #[test]
400     fn test_impl_loc() {
401         check_location(r"impl A { f$0 }", ImmediateLocation::Impl);
402         check_location(r"impl A { #[attr] f$0 }", ImmediateLocation::Impl);
403         check_location(r"impl A { f$0 fn f() {} }", ImmediateLocation::Impl);
404         check_location(r"impl A { fn f() {} f$0 }", ImmediateLocation::Impl);
405         check_location(r"impl A$0 {}", None);
406         check_location(r"impl A { fn f$0 }", None);
407     }
408
409     #[test]
410     fn test_use_loc() {
411         check_location(r"use f$0", ImmediateLocation::Use);
412         check_location(r"use f$0;", ImmediateLocation::Use);
413         check_location(r"use f::{f$0}", ImmediateLocation::UseTree);
414         check_location(r"use {f$0}", ImmediateLocation::UseTree);
415     }
416
417     #[test]
418     fn test_record_field_loc() {
419         check_location(r"struct Foo { f$0 }", ImmediateLocation::RecordField);
420         check_location(r"struct Foo { f$0 pub f: i32}", ImmediateLocation::RecordField);
421         check_location(r"struct Foo { pub f: i32, f$0 }", ImmediateLocation::RecordField);
422     }
423
424     #[test]
425     fn test_block_expr_loc() {
426         check_location(r"fn my_fn() { let a = 2; f$0 }", ImmediateLocation::StmtList);
427         check_location(r"fn my_fn() { f$0 f }", ImmediateLocation::StmtList);
428     }
429
430     #[test]
431     fn test_ident_pat_loc() {
432         check_location(r"fn my_fn(m$0) {}", ImmediateLocation::IdentPat);
433         check_location(r"fn my_fn() { let m$0 }", ImmediateLocation::IdentPat);
434         check_location(r"fn my_fn(&m$0) {}", ImmediateLocation::IdentPat);
435         check_location(r"fn my_fn() { let &m$0 }", ImmediateLocation::IdentPat);
436     }
437
438     #[test]
439     fn test_ref_expr_loc() {
440         check_location(r"fn my_fn() { let x = &m$0 foo; }", ImmediateLocation::RefExpr);
441     }
442
443     #[test]
444     fn test_item_list_loc() {
445         check_location(r"i$0", ImmediateLocation::ItemList);
446         check_location(r"#[attr] i$0", ImmediateLocation::ItemList);
447         check_location(r"fn f() {} i$0", ImmediateLocation::ItemList);
448         check_location(r"mod foo { f$0 }", ImmediateLocation::ItemList);
449         check_location(r"mod foo { #[attr] f$0 }", ImmediateLocation::ItemList);
450         check_location(r"mod foo { fn f() {} f$0 }", ImmediateLocation::ItemList);
451         check_location(r"mod foo$0 {}", None);
452     }
453
454     #[test]
455     fn test_impl_prev_sibling() {
456         check_prev_sibling(r"impl A w$0 ", ImmediatePrevSibling::ImplDefType);
457         check_prev_sibling(r"impl A w$0 {}", ImmediatePrevSibling::ImplDefType);
458         check_prev_sibling(r"impl A for A w$0 ", ImmediatePrevSibling::ImplDefType);
459         check_prev_sibling(r"impl A for A w$0 {}", ImmediatePrevSibling::ImplDefType);
460         check_prev_sibling(r"impl A for w$0 {}", None);
461         check_prev_sibling(r"impl A for w$0", None);
462     }
463
464     #[test]
465     fn test_trait_prev_sibling() {
466         check_prev_sibling(r"trait A w$0 ", ImmediatePrevSibling::TraitDefName);
467         check_prev_sibling(r"trait A w$0 {}", ImmediatePrevSibling::TraitDefName);
468     }
469
470     #[test]
471     fn test_if_expr_prev_sibling() {
472         check_prev_sibling(r"fn foo() { if true {} w$0", ImmediatePrevSibling::IfExpr);
473         check_prev_sibling(r"fn foo() { if true {}; w$0", None);
474     }
475
476     #[test]
477     fn test_vis_prev_sibling() {
478         check_prev_sibling(r"pub w$0", ImmediatePrevSibling::Visibility);
479     }
480
481     #[test]
482     fn test_attr_prev_sibling() {
483         check_prev_sibling(r"#[attr] w$0", ImmediatePrevSibling::Attribute);
484     }
485 }