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