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