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