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