• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
2 
3 mod format_like;
4 
5 use hir::{Documentation, HasAttrs};
6 use ide_db::{imports::insert_use::ImportScope, ty_filter::TryEnum, SnippetCap};
7 use syntax::{
8     ast::{self, make, AstNode, AstToken},
9     SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
10     TextRange, TextSize,
11 };
12 use text_edit::TextEdit;
13 
14 use crate::{
15     completions::postfix::format_like::add_format_like_completions,
16     context::{CompletionContext, DotAccess, DotAccessKind},
17     item::{Builder, CompletionRelevancePostfixMatch},
18     CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
19 };
20 
complete_postfix( acc: &mut Completions, ctx: &CompletionContext<'_>, dot_access: &DotAccess, )21 pub(crate) fn complete_postfix(
22     acc: &mut Completions,
23     ctx: &CompletionContext<'_>,
24     dot_access: &DotAccess,
25 ) {
26     if !ctx.config.enable_postfix_completions {
27         return;
28     }
29 
30     let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match dot_access {
31         DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. } => (
32             it,
33             &ty.original,
34             match *kind {
35                 DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
36                     receiver_is_ambiguous_float_literal
37                 }
38                 DotAccessKind::Method { .. } => false,
39             },
40         ),
41         _ => return,
42     };
43 
44     let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
45 
46     let cap = match ctx.config.snippet_cap {
47         Some(it) => it,
48         None => return,
49     };
50 
51     let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
52         Some(it) => it,
53         None => return,
54     };
55 
56     if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop() {
57         if receiver_ty.impls_trait(ctx.db, drop_trait, &[]) {
58             if let &[hir::AssocItem::Function(drop_fn)] = &*drop_trait.items(ctx.db) {
59                 cov_mark::hit!(postfix_drop_completion);
60                 // FIXME: check that `drop` is in scope, use fully qualified path if it isn't/if shadowed
61                 let mut item = postfix_snippet(
62                     "drop",
63                     "fn drop(&mut self)",
64                     &format!("drop($0{receiver_text})"),
65                 );
66                 item.set_documentation(drop_fn.docs(ctx.db));
67                 item.add_to(acc, ctx.db);
68             }
69         }
70     }
71 
72     let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
73     if let Some(try_enum) = &try_enum {
74         match try_enum {
75             TryEnum::Result => {
76                 postfix_snippet(
77                     "ifl",
78                     "if let Ok {}",
79                     &format!("if let Ok($1) = {receiver_text} {{\n    $0\n}}"),
80                 )
81                 .add_to(acc, ctx.db);
82 
83                 postfix_snippet(
84                     "while",
85                     "while let Ok {}",
86                     &format!("while let Ok($1) = {receiver_text} {{\n    $0\n}}"),
87                 )
88                 .add_to(acc, ctx.db);
89             }
90             TryEnum::Option => {
91                 postfix_snippet(
92                     "ifl",
93                     "if let Some {}",
94                     &format!("if let Some($1) = {receiver_text} {{\n    $0\n}}"),
95                 )
96                 .add_to(acc, ctx.db);
97 
98                 postfix_snippet(
99                     "while",
100                     "while let Some {}",
101                     &format!("while let Some($1) = {receiver_text} {{\n    $0\n}}"),
102                 )
103                 .add_to(acc, ctx.db);
104             }
105         }
106     } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
107         postfix_snippet("if", "if expr {}", &format!("if {receiver_text} {{\n    $0\n}}"))
108             .add_to(acc, ctx.db);
109         postfix_snippet("while", "while expr {}", &format!("while {receiver_text} {{\n    $0\n}}"))
110             .add_to(acc, ctx.db);
111         postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
112     } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() {
113         if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
114             postfix_snippet(
115                 "for",
116                 "for ele in expr {}",
117                 &format!("for ele in {receiver_text} {{\n    $0\n}}"),
118             )
119             .add_to(acc, ctx.db);
120         }
121     }
122 
123     postfix_snippet("ref", "&expr", &format!("&{receiver_text}")).add_to(acc, ctx.db);
124     postfix_snippet("refm", "&mut expr", &format!("&mut {receiver_text}")).add_to(acc, ctx.db);
125 
126     let mut unsafe_should_be_wrapped = true;
127     if dot_receiver.syntax().kind() == BLOCK_EXPR {
128         unsafe_should_be_wrapped = false;
129         if let Some(parent) = dot_receiver.syntax().parent() {
130             if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) {
131                 unsafe_should_be_wrapped = true;
132             }
133         }
134     };
135     let unsafe_completion_string = if unsafe_should_be_wrapped {
136         format!("unsafe {{ {receiver_text} }}")
137     } else {
138         format!("unsafe {receiver_text}")
139     };
140     postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
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 
145     let (dot_receiver, node_to_replace_with) = include_references(dot_receiver);
146     let receiver_text =
147         get_receiver_text(&node_to_replace_with, receiver_is_ambiguous_float_literal);
148     let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
149         Some(it) => it,
150         None => return,
151     };
152 
153     if !ctx.config.snippets.is_empty() {
154         add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
155     }
156 
157     match try_enum {
158         Some(try_enum) => match try_enum {
159             TryEnum::Result => {
160                 postfix_snippet(
161                     "match",
162                     "match expr {}",
163                     &format!("match {receiver_text} {{\n    Ok(${{1:_}}) => {{$2}},\n    Err(${{3:_}}) => {{$0}},\n}}"),
164                 )
165                 .add_to(acc, ctx.db);
166             }
167             TryEnum::Option => {
168                 postfix_snippet(
169                     "match",
170                     "match expr {}",
171                     &format!(
172                         "match {receiver_text} {{\n    Some(${{1:_}}) => {{$2}},\n    None => {{$0}},\n}}"
173                     ),
174                 )
175                 .add_to(acc, ctx.db);
176             }
177         },
178         None => {
179             postfix_snippet(
180                 "match",
181                 "match expr {}",
182                 &format!("match {receiver_text} {{\n    ${{1:_}} => {{$0}},\n}}"),
183             )
184             .add_to(acc, ctx.db);
185         }
186     }
187 
188     postfix_snippet("box", "Box::new(expr)", &format!("Box::new({receiver_text})"))
189         .add_to(acc, ctx.db);
190     postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
191     postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
192     postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
193         .add_to(acc, ctx.db);
194 
195     if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
196         if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
197             postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
198                 .add_to(acc, ctx.db);
199             postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
200                 .add_to(acc, ctx.db);
201         }
202     }
203 
204     if let ast::Expr::Literal(literal) = dot_receiver.clone() {
205         if let Some(literal_text) = ast::String::cast(literal.token()) {
206             add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
207         }
208     }
209 }
210 
get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String211 fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
212     let text = if receiver_is_ambiguous_float_literal {
213         let text = receiver.syntax().text();
214         let without_dot = ..text.len() - TextSize::of('.');
215         text.slice(without_dot).to_string()
216     } else {
217         receiver.to_string()
218     };
219 
220     // The receiver texts should be interpreted as-is, as they are expected to be
221     // normal Rust expressions. We escape '\' and '$' so they don't get treated as
222     // snippet-specific constructs.
223     //
224     // Note that we don't need to escape the other characters that can be escaped,
225     // because they wouldn't be treated as snippet-specific constructs without '$'.
226     text.replace('\\', "\\\\").replace('$', "\\$")
227 }
228 
229 fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
230     let mut resulting_element = initial_element.clone();
231 
232     while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
233     {
234         resulting_element = ast::Expr::from(field_expr);
235     }
236 
237     let mut new_element_opt = initial_element.clone();
238 
239     if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) {
240         if let Some(expr) = first_ref_expr.expr() {
241             resulting_element = expr;
242         }
243 
244         while let Some(parent_ref_element) =
245             resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
246         {
247             resulting_element = ast::Expr::from(parent_ref_element);
248 
249             new_element_opt = make::expr_ref(new_element_opt, false);
250         }
251     } else {
252         // If we do not find any ref expressions, restore
253         // all the progress of tree climbing
254         resulting_element = initial_element.clone();
255     }
256 
257     (resulting_element, new_element_opt)
258 }
259 
260 fn build_postfix_snippet_builder<'ctx>(
261     ctx: &'ctx CompletionContext<'_>,
262     cap: SnippetCap,
263     receiver: &'ctx ast::Expr,
264 ) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
265     let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
266     if ctx.source_range().end() < receiver_range.start() {
267         // This shouldn't happen, yet it does. I assume this might be due to an incorrect token mapping.
268         return None;
269     }
270     let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
271 
272     // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that
273     // can't be annotated for the closure, hence fix it by constructing it without the Option first
274     fn build<'ctx>(
275         ctx: &'ctx CompletionContext<'_>,
276         cap: SnippetCap,
277         delete_range: TextRange,
278     ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
279         move |label, detail, snippet| {
280             let edit = TextEdit::replace(delete_range, snippet.to_string());
281             let mut item =
282                 CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
283             item.detail(detail).snippet_edit(cap, edit);
284             let postfix_match = if ctx.original_token.text() == label {
285                 cov_mark::hit!(postfix_exact_match_is_high_priority);
286                 Some(CompletionRelevancePostfixMatch::Exact)
287             } else {
288                 cov_mark::hit!(postfix_inexact_match_is_low_priority);
289                 Some(CompletionRelevancePostfixMatch::NonExact)
290             };
291             let relevance = CompletionRelevance { postfix_match, ..Default::default() };
292             item.set_relevance(relevance);
293             item
294         }
295     }
296     Some(build(ctx, cap, delete_range))
297 }
298 
299 fn add_custom_postfix_completions(
300     acc: &mut Completions,
301     ctx: &CompletionContext<'_>,
302     postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
303     receiver_text: &str,
304 ) -> Option<()> {
305     if ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema).is_none() {
306         return None;
307     }
308     ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(
309         |(trigger, snippet)| {
310             let imports = match snippet.imports(ctx) {
311                 Some(imports) => imports,
312                 None => return,
313             };
314             let body = snippet.postfix_snippet(receiver_text);
315             let mut builder =
316                 postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
317             builder.documentation(Documentation::new(format!("```rust\n{body}\n```")));
318             for import in imports.into_iter() {
319                 builder.add_import(import);
320             }
321             builder.add_to(acc, ctx.db);
322         },
323     );
324     None
325 }
326 
327 #[cfg(test)]
328 mod tests {
329     use expect_test::{expect, Expect};
330 
331     use crate::{
332         tests::{check_edit, check_edit_with_config, completion_list, TEST_CONFIG},
333         CompletionConfig, Snippet,
334     };
335 
336     fn check(ra_fixture: &str, expect: Expect) {
337         let actual = completion_list(ra_fixture);
338         expect.assert_eq(&actual)
339     }
340 
341     #[test]
342     fn postfix_completion_works_for_trivial_path_expression() {
343         check(
344             r#"
main()345 fn main() {
346     let bar = true;
347     bar.$0
348 }
349 "#,
350             expect![[r#"
351                 sn box    Box::new(expr)
352                 sn call   function(expr)
353                 sn dbg    dbg!(expr)
354                 sn dbgr   dbg!(&expr)
355                 sn if     if expr {}
356                 sn let    let
357                 sn letm   let mut
358                 sn match  match expr {}
359                 sn not    !expr
360                 sn ref    &expr
361                 sn refm   &mut expr
362                 sn unsafe unsafe {}
363                 sn while  while expr {}
364             "#]],
365         );
366     }
367 
368     #[test]
369     fn postfix_completion_works_for_function_calln() {
370         check(
371             r#"
foo(elt: bool) -> bool372 fn foo(elt: bool) -> bool {
373     !elt
374 }
375 
376 fn main() {
377     let bar = true;
378     foo(bar.$0)
379 }
380 "#,
381             expect![[r#"
382                 sn box    Box::new(expr)
383                 sn call   function(expr)
384                 sn dbg    dbg!(expr)
385                 sn dbgr   dbg!(&expr)
386                 sn if     if expr {}
387                 sn match  match expr {}
388                 sn not    !expr
389                 sn ref    &expr
390                 sn refm   &mut expr
391                 sn unsafe unsafe {}
392                 sn while  while expr {}
393             "#]],
394         );
395     }
396 
397     #[test]
398     fn postfix_type_filtering() {
399         check(
400             r#"
main()401 fn main() {
402     let bar: u8 = 12;
403     bar.$0
404 }
405 "#,
406             expect![[r#"
407                 sn box    Box::new(expr)
408                 sn call   function(expr)
409                 sn dbg    dbg!(expr)
410                 sn dbgr   dbg!(&expr)
411                 sn let    let
412                 sn letm   let mut
413                 sn match  match expr {}
414                 sn ref    &expr
415                 sn refm   &mut expr
416                 sn unsafe unsafe {}
417             "#]],
418         )
419     }
420 
421     #[test]
422     fn let_middle_block() {
423         check(
424             r#"
425 fn main() {
426     baz.l$0
427     res
428 }
429 "#,
430             expect![[r#"
431                 sn box    Box::new(expr)
432                 sn call   function(expr)
433                 sn dbg    dbg!(expr)
434                 sn dbgr   dbg!(&expr)
435                 sn if     if expr {}
436                 sn let    let
437                 sn letm   let mut
438                 sn match  match expr {}
439                 sn not    !expr
440                 sn ref    &expr
441                 sn refm   &mut expr
442                 sn unsafe unsafe {}
443                 sn while  while expr {}
444             "#]],
445         );
446     }
447 
448     #[test]
449     fn option_iflet() {
450         check_edit(
451             "ifl",
452             r#"
453 //- minicore: option
main()454 fn main() {
455     let bar = Some(true);
456     bar.$0
457 }
458 "#,
459             r#"
460 fn main() {
461     let bar = Some(true);
462     if let Some($1) = bar {
463     $0
464 }
465 }
466 "#,
467         );
468     }
469 
470     #[test]
471     fn result_match() {
472         check_edit(
473             "match",
474             r#"
475 //- minicore: result
main()476 fn main() {
477     let bar = Ok(true);
478     bar.$0
479 }
480 "#,
481             r#"
482 fn main() {
483     let bar = Ok(true);
484     match bar {
485     Ok(${1:_}) => {$2},
486     Err(${3:_}) => {$0},
487 }
488 }
489 "#,
490         );
491     }
492 
493     #[test]
494     fn postfix_completion_works_for_ambiguous_float_literal() {
main()495         check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
496     }
497 
498     #[test]
499     fn works_in_simple_macro() {
500         check_edit(
501             "dbg",
502             r#"
503 macro_rules! m { ($e:expr) => { $e } }
504 fn main() {
505     let bar: u8 = 12;
506     m!(bar.d$0)
507 }
508 "#,
509             r#"
510 macro_rules! m { ($e:expr) => { $e } }
511 fn main() {
512     let bar: u8 = 12;
513     m!(dbg!(bar))
514 }
515 "#,
516         );
517     }
518 
519     #[test]
520     fn postfix_completion_for_references() {
main()521         check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
main()522         check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
523         check_edit(
524             "ifl",
525             r#"
526 //- minicore: option
main()527 fn main() {
528     let bar = &Some(true);
529     bar.$0
530 }
531 "#,
532             r#"
533 fn main() {
534     let bar = &Some(true);
535     if let Some($1) = bar {
536     $0
537 }
538 }
539 "#,
540         )
541     }
542 
543     #[test]
544     fn postfix_completion_for_unsafe() {
545         check_edit("unsafe", r#"fn main() { foo.$0 }"#, r#"fn main() { unsafe { foo } }"#);
546         check_edit("unsafe", r#"fn main() { { foo }.$0 }"#, r#"fn main() { unsafe { foo } }"#);
547         check_edit(
548             "unsafe",
549             r#"fn main() { if x { foo }.$0 }"#,
550             r#"fn main() { unsafe { if x { foo } } }"#,
551         );
552         check_edit(
553             "unsafe",
554             r#"fn main() { loop { foo }.$0 }"#,
555             r#"fn main() { unsafe { loop { foo } } }"#,
556         );
557         check_edit(
558             "unsafe",
559             r#"fn main() { if true {}.$0 }"#,
560             r#"fn main() { unsafe { if true {} } }"#,
561         );
562         check_edit(
563             "unsafe",
564             r#"fn main() { while true {}.$0 }"#,
565             r#"fn main() { unsafe { while true {} } }"#,
566         );
567         check_edit(
568             "unsafe",
569             r#"fn main() { for i in 0..10 {}.$0 }"#,
570             r#"fn main() { unsafe { for i in 0..10 {} } }"#,
571         );
572         check_edit(
573             "unsafe",
574             r#"fn main() { let x = if true {1} else {2}.$0 }"#,
575             r#"fn main() { let x = unsafe { if true {1} else {2} } }"#,
576         );
577 
578         // completion will not be triggered
579         check_edit(
580             "unsafe",
581             r#"fn main() { let x = true else {panic!()}.$0}"#,
582             r#"fn main() { let x = true else {panic!()}.unsafe}"#,
583         );
584     }
585 
586     #[test]
587     fn custom_postfix_completion() {
588         let config = CompletionConfig {
589             snippets: vec![Snippet::new(
590                 &[],
591                 &["break".into()],
592                 &["ControlFlow::Break(${receiver})".into()],
593                 "",
594                 &["core::ops::ControlFlow".into()],
595                 crate::SnippetScope::Expr,
596             )
597             .unwrap()],
598             ..TEST_CONFIG
599         };
600 
601         check_edit_with_config(
602             config.clone(),
603             "break",
604             r#"
605 //- minicore: try
main()606 fn main() { 42.$0 }
607 "#,
608             r#"
609 use core::ops::ControlFlow;
610 
611 fn main() { ControlFlow::Break(42) }
612 "#,
613         );
614 
615         // The receiver texts should be escaped, see comments in `get_receiver_text()`
616         // for detail.
617         //
618         // Note that the last argument is what *lsp clients would see* rather than
619         // what users would see. Unescaping happens thereafter.
620         check_edit_with_config(
621             config.clone(),
622             "break",
623             r#"
624 //- minicore: try
main()625 fn main() { '\\'.$0 }
626 "#,
627             r#"
628 use core::ops::ControlFlow;
629 
630 fn main() { ControlFlow::Break('\\\\') }
631 "#,
632         );
633 
634         check_edit_with_config(
635             config,
636             "break",
637             r#"
638 //- minicore: try
main()639 fn main() {
640     match true {
641         true => "${1:placeholder}",
642         false => "\$",
643     }.$0
644 }
645 "#,
646             r#"
647 use core::ops::ControlFlow;
648 
649 fn main() {
650     ControlFlow::Break(match true {
651         true => "\${1:placeholder}",
652         false => "\\\$",
653     })
654 }
655 "#,
656         );
657     }
658 
659     #[test]
660     fn postfix_completion_for_format_like_strings() {
661         check_edit(
662             "format",
main()663             r#"fn main() { "{some_var:?}".$0 }"#,
664             r#"fn main() { format!("{some_var:?}") }"#,
665         );
666         check_edit(
667             "panic",
main()668             r#"fn main() { "Panic with {a}".$0 }"#,
669             r#"fn main() { panic!("Panic with {a}") }"#,
670         );
671         check_edit(
672             "println",
main()673             r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
674             r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
675         );
676         check_edit(
677             "loge",
main()678             r#"fn main() { "{2+2}".$0 }"#,
679             r#"fn main() { log::error!("{}", 2+2) }"#,
680         );
681         check_edit(
682             "logt",
main()683             r#"fn main() { "{2+2}".$0 }"#,
684             r#"fn main() { log::trace!("{}", 2+2) }"#,
685         );
686         check_edit(
687             "logd",
main()688             r#"fn main() { "{2+2}".$0 }"#,
689             r#"fn main() { log::debug!("{}", 2+2) }"#,
690         );
main()691         check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
main()692         check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
693         check_edit(
694             "loge",
main()695             r#"fn main() { "{2+2}".$0 }"#,
696             r#"fn main() { log::error!("{}", 2+2) }"#,
697         );
698     }
699 
700     #[test]
701     fn postfix_custom_snippets_completion_for_references() {
702         // https://github.com/rust-lang/rust-analyzer/issues/7929
703 
704         let snippet = Snippet::new(
705             &[],
706             &["ok".into()],
707             &["Ok(${receiver})".into()],
708             "",
709             &[],
710             crate::SnippetScope::Expr,
711         )
712         .unwrap();
713 
714         check_edit_with_config(
715             CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
716             "ok",
main()717             r#"fn main() { &&42.o$0 }"#,
718             r#"fn main() { Ok(&&42) }"#,
719         );
720 
721         check_edit_with_config(
722             CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
723             "ok",
main()724             r#"fn main() { &&42.$0 }"#,
725             r#"fn main() { Ok(&&42) }"#,
726         );
727 
728         check_edit_with_config(
729             CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },
730             "ok",
731             r#"
732 struct A {
733     a: i32,
734 }
735 
main()736 fn main() {
737     let a = A {a :1};
738     &a.a.$0
739 }
740             "#,
741             r#"
742 struct A {
743     a: i32,
744 }
745 
746 fn main() {
747     let a = A {a :1};
748     Ok(&a.a)
749 }
750             "#,
751         );
752     }
753 
754     #[test]
755     fn no_postfix_completions_in_if_block_that_has_an_else() {
756         check(
757             r#"
test()758 fn test() {
759     if true {}.$0 else {}
760 }
761 "#,
762             expect![[r#""#]],
763         );
764     }
765 }
766