]> git.lizzy.rs Git - rust.git/blob - crates/ide_completion/src/completions/postfix.rs
Merge #10434
[rust.git] / crates / ide_completion / src / completions / postfix.rs
1 //! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
2
3 mod format_like;
4
5 use hir::Documentation;
6 use ide_db::{
7     helpers::{insert_use::ImportScope, FamousDefs, SnippetCap},
8     ty_filter::TryEnum,
9 };
10 use syntax::{
11     ast::{self, AstNode, AstToken},
12     SyntaxKind::{EXPR_STMT, STMT_LIST},
13     TextRange, TextSize,
14 };
15 use text_edit::TextEdit;
16
17 use crate::{
18     completions::postfix::format_like::add_format_like_completions,
19     context::CompletionContext,
20     item::{Builder, CompletionKind},
21     patterns::ImmediateLocation,
22     CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
23 };
24
25 pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
26     if !ctx.config.enable_postfix_completions {
27         return;
28     }
29
30     let (dot_receiver, receiver_is_ambiguous_float_literal) = match &ctx.completion_location {
31         Some(ImmediateLocation::MethodCall { receiver: Some(it), .. }) => (it, false),
32         Some(ImmediateLocation::FieldAccess {
33             receiver: Some(it),
34             receiver_is_ambiguous_float_literal,
35         }) => (it, *receiver_is_ambiguous_float_literal),
36         _ => return,
37     };
38
39     let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
40
41     let receiver_ty = match ctx.sema.type_of_expr(dot_receiver) {
42         Some(it) => it.original,
43         None => return,
44     };
45
46     // Suggest .await syntax for types that implement Future trait
47     if receiver_ty.impls_future(ctx.db) {
48         let mut item = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await");
49         item.kind(CompletionItemKind::Keyword).detail("expr.await");
50         item.add_to(acc);
51     }
52
53     let cap = match ctx.config.snippet_cap {
54         Some(it) => it,
55         None => return,
56     };
57
58     let postfix_snippet = build_postfix_snippet_builder(ctx, cap, &dot_receiver);
59
60     if !ctx.config.snippets.is_empty() {
61         add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
62     }
63
64     let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
65     if let Some(try_enum) = &try_enum {
66         match try_enum {
67             TryEnum::Result => {
68                 postfix_snippet(
69                     "ifl",
70                     "if let Ok {}",
71                     &format!("if let Ok($1) = {} {{\n    $0\n}}", receiver_text),
72                 )
73                 .add_to(acc);
74
75                 postfix_snippet(
76                     "while",
77                     "while let Ok {}",
78                     &format!("while let Ok($1) = {} {{\n    $0\n}}", receiver_text),
79                 )
80                 .add_to(acc);
81             }
82             TryEnum::Option => {
83                 postfix_snippet(
84                     "ifl",
85                     "if let Some {}",
86                     &format!("if let Some($1) = {} {{\n    $0\n}}", receiver_text),
87                 )
88                 .add_to(acc);
89
90                 postfix_snippet(
91                     "while",
92                     "while let Some {}",
93                     &format!("while let Some($1) = {} {{\n    $0\n}}", receiver_text),
94                 )
95                 .add_to(acc);
96             }
97         }
98     } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
99         postfix_snippet("if", "if expr {}", &format!("if {} {{\n    $0\n}}", receiver_text))
100             .add_to(acc);
101         postfix_snippet(
102             "while",
103             "while expr {}",
104             &format!("while {} {{\n    $0\n}}", receiver_text),
105         )
106         .add_to(acc);
107         postfix_snippet("not", "!expr", &format!("!{}", receiver_text)).add_to(acc);
108     } else if let Some(trait_) = FamousDefs(&ctx.sema, ctx.krate).core_iter_IntoIterator() {
109         if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
110             postfix_snippet(
111                 "for",
112                 "for ele in expr {}",
113                 &format!("for ele in {} {{\n    $0\n}}", receiver_text),
114             )
115             .add_to(acc);
116         }
117     }
118
119     postfix_snippet("ref", "&expr", &format!("&{}", receiver_text)).add_to(acc);
120     postfix_snippet("refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc);
121
122     // The rest of the postfix completions create an expression that moves an argument,
123     // so it's better to consider references now to avoid breaking the compilation
124     let dot_receiver = include_references(dot_receiver);
125     let receiver_text = get_receiver_text(&dot_receiver, receiver_is_ambiguous_float_literal);
126     let postfix_snippet = build_postfix_snippet_builder(ctx, cap, &dot_receiver);
127
128     match try_enum {
129         Some(try_enum) => match try_enum {
130             TryEnum::Result => {
131                 postfix_snippet(
132                     "match",
133                     "match expr {}",
134                     &format!("match {} {{\n    Ok(${{1:_}}) => {{$2}},\n    Err(${{3:_}}) => {{$0}},\n}}", receiver_text),
135                 )
136                 .add_to(acc);
137             }
138             TryEnum::Option => {
139                 postfix_snippet(
140                     "match",
141                     "match expr {}",
142                     &format!(
143                         "match {} {{\n    Some(${{1:_}}) => {{$2}},\n    None => {{$0}},\n}}",
144                         receiver_text
145                     ),
146                 )
147                 .add_to(acc);
148             }
149         },
150         None => {
151             postfix_snippet(
152                 "match",
153                 "match expr {}",
154                 &format!("match {} {{\n    ${{1:_}} => {{$0}},\n}}", receiver_text),
155             )
156             .add_to(acc);
157         }
158     }
159
160     postfix_snippet("box", "Box::new(expr)", &format!("Box::new({})", receiver_text)).add_to(acc);
161     postfix_snippet("ok", "Ok(expr)", &format!("Ok({})", receiver_text)).add_to(acc);
162     postfix_snippet("err", "Err(expr)", &format!("Err({})", receiver_text)).add_to(acc);
163     postfix_snippet("some", "Some(expr)", &format!("Some({})", receiver_text)).add_to(acc);
164     postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc);
165     postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{})", receiver_text)).add_to(acc);
166     postfix_snippet("call", "function(expr)", &format!("${{1}}({})", receiver_text)).add_to(acc);
167
168     if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
169         if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
170             postfix_snippet("let", "let", &format!("let $0 = {};", receiver_text)).add_to(acc);
171             postfix_snippet("letm", "let mut", &format!("let mut $0 = {};", receiver_text))
172                 .add_to(acc);
173         }
174     }
175
176     if let ast::Expr::Literal(literal) = dot_receiver.clone() {
177         if let Some(literal_text) = ast::String::cast(literal.token()) {
178             add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
179         }
180     }
181 }
182
183 fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
184     if receiver_is_ambiguous_float_literal {
185         let text = receiver.syntax().text();
186         let without_dot = ..text.len() - TextSize::of('.');
187         text.slice(without_dot).to_string()
188     } else {
189         receiver.to_string()
190     }
191 }
192
193 fn include_references(initial_element: &ast::Expr) -> ast::Expr {
194     let mut resulting_element = initial_element.clone();
195     while let Some(parent_ref_element) =
196         resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
197     {
198         resulting_element = ast::Expr::from(parent_ref_element);
199     }
200     resulting_element
201 }
202
203 fn build_postfix_snippet_builder<'a>(
204     ctx: &'a CompletionContext,
205     cap: SnippetCap,
206     receiver: &'a ast::Expr,
207 ) -> impl Fn(&str, &str, &str) -> Builder + 'a {
208     let receiver_syntax = receiver.syntax();
209     let receiver_range = ctx.sema.original_range(receiver_syntax).range;
210     let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
211
212     move |label, detail, snippet| {
213         let edit = TextEdit::replace(delete_range, snippet.to_string());
214         let mut item = CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label);
215         item.detail(detail).kind(CompletionItemKind::Snippet).snippet_edit(cap, edit);
216         if ctx.original_token.text() == label {
217             let relevance =
218                 CompletionRelevance { exact_postfix_snippet_match: true, ..Default::default() };
219             item.set_relevance(relevance);
220         }
221
222         item
223     }
224 }
225
226 fn add_custom_postfix_completions(
227     acc: &mut Completions,
228     ctx: &CompletionContext,
229     postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
230     receiver_text: &str,
231 ) -> Option<()> {
232     let import_scope =
233         ImportScope::find_insert_use_container_with_macros(&ctx.token.parent()?, &ctx.sema)?;
234     ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(
235         |(trigger, snippet)| {
236             let imports = match snippet.imports(ctx, &import_scope) {
237                 Some(imports) => imports,
238                 None => return,
239             };
240             let body = snippet.postfix_snippet(&receiver_text);
241             let mut builder =
242                 postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
243             builder.documentation(Documentation::new(format!("```rust\n{}\n```", body)));
244             for import in imports.into_iter() {
245                 builder.add_import(import);
246             }
247             builder.add_to(acc);
248         },
249     );
250     None
251 }
252
253 #[cfg(test)]
254 mod tests {
255     use expect_test::{expect, Expect};
256
257     use crate::{
258         tests::{check_edit, check_edit_with_config, filtered_completion_list, TEST_CONFIG},
259         CompletionConfig, CompletionKind, Snippet,
260     };
261
262     fn check(ra_fixture: &str, expect: Expect) {
263         let actual = filtered_completion_list(ra_fixture, CompletionKind::Postfix);
264         expect.assert_eq(&actual)
265     }
266
267     #[test]
268     fn postfix_completion_works_for_trivial_path_expression() {
269         check(
270             r#"
271 fn main() {
272     let bar = true;
273     bar.$0
274 }
275 "#,
276             expect![[r#"
277                 sn if    if expr {}
278                 sn while while expr {}
279                 sn not   !expr
280                 sn ref   &expr
281                 sn refm  &mut expr
282                 sn match match expr {}
283                 sn box   Box::new(expr)
284                 sn ok    Ok(expr)
285                 sn err   Err(expr)
286                 sn some  Some(expr)
287                 sn dbg   dbg!(expr)
288                 sn dbgr  dbg!(&expr)
289                 sn call  function(expr)
290                 sn let   let
291                 sn letm  let mut
292             "#]],
293         );
294     }
295
296     #[test]
297     fn postfix_completion_works_for_function_calln() {
298         check(
299             r#"
300 fn foo(elt: bool) -> bool {
301     !elt
302 }
303
304 fn main() {
305     let bar = true;
306     foo(bar.$0)
307 }
308 "#,
309             expect![[r#"
310                 sn if    if expr {}
311                 sn while while expr {}
312                 sn not   !expr
313                 sn ref   &expr
314                 sn refm  &mut expr
315                 sn match match expr {}
316                 sn box   Box::new(expr)
317                 sn ok    Ok(expr)
318                 sn err   Err(expr)
319                 sn some  Some(expr)
320                 sn dbg   dbg!(expr)
321                 sn dbgr  dbg!(&expr)
322                 sn call  function(expr)
323             "#]],
324         );
325     }
326
327     #[test]
328     fn postfix_type_filtering() {
329         check(
330             r#"
331 fn main() {
332     let bar: u8 = 12;
333     bar.$0
334 }
335 "#,
336             expect![[r#"
337                 sn ref   &expr
338                 sn refm  &mut expr
339                 sn match match expr {}
340                 sn box   Box::new(expr)
341                 sn ok    Ok(expr)
342                 sn err   Err(expr)
343                 sn some  Some(expr)
344                 sn dbg   dbg!(expr)
345                 sn dbgr  dbg!(&expr)
346                 sn call  function(expr)
347                 sn let   let
348                 sn letm  let mut
349             "#]],
350         )
351     }
352
353     #[test]
354     fn let_middle_block() {
355         check(
356             r#"
357 fn main() {
358     baz.l$0
359     res
360 }
361 "#,
362             expect![[r#"
363                 sn if    if expr {}
364                 sn while while expr {}
365                 sn not   !expr
366                 sn ref   &expr
367                 sn refm  &mut expr
368                 sn match match expr {}
369                 sn box   Box::new(expr)
370                 sn ok    Ok(expr)
371                 sn err   Err(expr)
372                 sn some  Some(expr)
373                 sn dbg   dbg!(expr)
374                 sn dbgr  dbg!(&expr)
375                 sn call  function(expr)
376                 sn let   let
377                 sn letm  let mut
378             "#]],
379         );
380     }
381
382     #[test]
383     fn option_iflet() {
384         check_edit(
385             "ifl",
386             r#"
387 //- minicore: option
388 fn main() {
389     let bar = Some(true);
390     bar.$0
391 }
392 "#,
393             r#"
394 fn main() {
395     let bar = Some(true);
396     if let Some($1) = bar {
397     $0
398 }
399 }
400 "#,
401         );
402     }
403
404     #[test]
405     fn result_match() {
406         check_edit(
407             "match",
408             r#"
409 //- minicore: result
410 fn main() {
411     let bar = Ok(true);
412     bar.$0
413 }
414 "#,
415             r#"
416 fn main() {
417     let bar = Ok(true);
418     match bar {
419     Ok(${1:_}) => {$2},
420     Err(${3:_}) => {$0},
421 }
422 }
423 "#,
424         );
425     }
426
427     #[test]
428     fn postfix_completion_works_for_ambiguous_float_literal() {
429         check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
430     }
431
432     #[test]
433     fn works_in_simple_macro() {
434         check_edit(
435             "dbg",
436             r#"
437 macro_rules! m { ($e:expr) => { $e } }
438 fn main() {
439     let bar: u8 = 12;
440     m!(bar.d$0)
441 }
442 "#,
443             r#"
444 macro_rules! m { ($e:expr) => { $e } }
445 fn main() {
446     let bar: u8 = 12;
447     m!(dbg!(bar))
448 }
449 "#,
450         );
451     }
452
453     #[test]
454     fn postfix_completion_for_references() {
455         check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
456         check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
457         check_edit(
458             "ifl",
459             r#"
460 //- minicore: option
461 fn main() {
462     let bar = &Some(true);
463     bar.$0
464 }
465 "#,
466             r#"
467 fn main() {
468     let bar = &Some(true);
469     if let Some($1) = bar {
470     $0
471 }
472 }
473 "#,
474         )
475     }
476
477     #[test]
478     fn custom_postfix_completion() {
479         check_edit_with_config(
480             CompletionConfig {
481                 snippets: vec![Snippet::new(
482                     &[],
483                     &["break".into()],
484                     &["ControlFlow::Break(${receiver})".into()],
485                     "",
486                     &["core::ops::ControlFlow".into()],
487                     crate::SnippetScope::Expr,
488                 )
489                 .unwrap()],
490                 ..TEST_CONFIG
491             },
492             "break",
493             r#"
494 //- minicore: try
495 fn main() { 42.$0 }
496 "#,
497             r#"
498 use core::ops::ControlFlow;
499
500 fn main() { ControlFlow::Break(42) }
501 "#,
502         );
503     }
504
505     #[test]
506     fn postfix_completion_for_format_like_strings() {
507         check_edit(
508             "format",
509             r#"fn main() { "{some_var:?}".$0 }"#,
510             r#"fn main() { format!("{:?}", some_var) }"#,
511         );
512         check_edit(
513             "panic",
514             r#"fn main() { "Panic with {a}".$0 }"#,
515             r#"fn main() { panic!("Panic with {}", a) }"#,
516         );
517         check_edit(
518             "println",
519             r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
520             r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
521         );
522         check_edit(
523             "loge",
524             r#"fn main() { "{2+2}".$0 }"#,
525             r#"fn main() { log::error!("{}", 2+2) }"#,
526         );
527         check_edit(
528             "logt",
529             r#"fn main() { "{2+2}".$0 }"#,
530             r#"fn main() { log::trace!("{}", 2+2) }"#,
531         );
532         check_edit(
533             "logd",
534             r#"fn main() { "{2+2}".$0 }"#,
535             r#"fn main() { log::debug!("{}", 2+2) }"#,
536         );
537         check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
538         check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
539         check_edit(
540             "loge",
541             r#"fn main() { "{2+2}".$0 }"#,
542             r#"fn main() { log::error!("{}", 2+2) }"#,
543         );
544     }
545 }