ide_completion/completions/
postfix.rs

1//! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
2
3mod format_like;
4
5use base_db::SourceDatabase;
6use hir::{ItemInNs, Semantics};
7use ide_db::{
8    RootDatabase, SnippetCap,
9    documentation::{Documentation, HasDocs},
10    imports::insert_use::ImportScope,
11    syntax_helpers::suggest_name::NameGenerator,
12    text_edit::TextEdit,
13    ty_filter::TryEnum,
14};
15use itertools::Itertools;
16use stdx::never;
17use syntax::{
18    SmolStr,
19    SyntaxKind::{EXPR_STMT, STMT_LIST},
20    T, TextRange, TextSize, ToSmolStr,
21    ast::{self, AstNode, AstToken},
22    format_smolstr, match_ast,
23};
24
25use crate::{
26    CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
27    completions::postfix::format_like::add_format_like_completions,
28    context::{BreakableKind, CompletionContext, DotAccess, DotAccessKind},
29    item::{Builder, CompletionRelevancePostfixMatch},
30};
31
32pub(crate) fn complete_postfix(
33    acc: &mut Completions,
34    ctx: &CompletionContext<'_>,
35    dot_access: &DotAccess<'_>,
36) {
37    if !ctx.config.enable_postfix_completions {
38        return;
39    }
40
41    let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match dot_access {
42        DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. } => (
43            it,
44            &ty.original,
45            match *kind {
46                DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
47                    receiver_is_ambiguous_float_literal
48                }
49                DotAccessKind::Method => false,
50            },
51        ),
52        _ => return,
53    };
54    let expr_ctx = &dot_access.ctx;
55
56    let receiver_text =
57        get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
58
59    let cap = match ctx.config.snippet_cap {
60        Some(it) => it,
61        None => return,
62    };
63
64    let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
65        Some(it) => it,
66        None => return,
67    };
68
69    let cfg = ctx.config.find_path_config(ctx.is_nightly);
70
71    if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop()
72        && receiver_ty.impls_trait(ctx.db, drop_trait, &[])
73        && let Some(drop_fn) = ctx.famous_defs().core_mem_drop()
74        && let Some(path) = ctx.module.find_path(ctx.db, ItemInNs::Values(drop_fn.into()), cfg)
75    {
76        cov_mark::hit!(postfix_drop_completion);
77        let mut item = postfix_snippet(
78            "drop",
79            "fn drop(&mut self)",
80            &format!("{path}($0{receiver_text})", path = path.display(ctx.db, ctx.edition)),
81        );
82        item.set_documentation(drop_fn.docs(ctx.db));
83        item.add_to(acc, ctx.db);
84    }
85
86    postfix_snippet("ref", "&expr", &format!("&{receiver_text}")).add_to(acc, ctx.db);
87    postfix_snippet("refm", "&mut expr", &format!("&mut {receiver_text}")).add_to(acc, ctx.db);
88    postfix_snippet("deref", "*expr", &format!("*{receiver_text}")).add_to(acc, ctx.db);
89
90    // The rest of the postfix completions create an expression that moves an argument,
91    // so it's better to consider references now to avoid breaking the compilation
92
93    let (dot_receiver_including_refs, prefix) = include_references(dot_receiver);
94    let mut receiver_text =
95        get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);
96    receiver_text.insert_str(0, &prefix);
97    let postfix_snippet =
98        match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {
99            Some(it) => it,
100            None => return,
101        };
102
103    if !ctx.config.snippets.is_empty() {
104        add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
105    }
106
107    postfix_snippet("box", "Box::new(expr)", &format!("Box::new({receiver_text})"))
108        .add_to(acc, ctx.db);
109    postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
110    postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
111    postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
112        .add_to(acc, ctx.db);
113
114    let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
115    let mut is_in_cond = false;
116    if let Some(parent) = dot_receiver_including_refs.syntax().parent()
117        && let Some(second_ancestor) = parent.parent()
118    {
119        if let Some(parent_expr) = ast::Expr::cast(parent) {
120            is_in_cond = is_in_condition(&parent_expr);
121        }
122        let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema);
123        match &try_enum {
124            Some(try_enum) if is_in_cond => match try_enum {
125                TryEnum::Result => {
126                    postfix_snippet(
127                        "let",
128                        "let Ok(_)",
129                        &format!("let Ok({placeholder}) = {receiver_text}"),
130                    )
131                    .add_to(acc, ctx.db);
132                    postfix_snippet(
133                        "letm",
134                        "let Ok(mut _)",
135                        &format!("let Ok(mut {placeholder}) = {receiver_text}"),
136                    )
137                    .add_to(acc, ctx.db);
138                }
139                TryEnum::Option => {
140                    postfix_snippet(
141                        "let",
142                        "let Some(_)",
143                        &format!("let Some({placeholder}) = {receiver_text}"),
144                    )
145                    .add_to(acc, ctx.db);
146                    postfix_snippet(
147                        "letm",
148                        "let Some(mut _)",
149                        &format!("let Some(mut {placeholder}) = {receiver_text}"),
150                    )
151                    .add_to(acc, ctx.db);
152                }
153            },
154            _ if is_in_cond => {
155                postfix_snippet("let", "let", &format!("let $1 = {receiver_text}"))
156                    .add_to(acc, ctx.db);
157            }
158            _ if matches!(second_ancestor.kind(), STMT_LIST | EXPR_STMT) => {
159                postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
160                    .add_to(acc, ctx.db);
161                postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
162                    .add_to(acc, ctx.db);
163            }
164            _ => (),
165        }
166    }
167
168    if !is_in_cond {
169        match try_enum {
170            Some(try_enum) => match try_enum {
171                TryEnum::Result => {
172                    postfix_snippet(
173                    "match",
174                    "match expr {}",
175                    &format!("match {receiver_text} {{\n    Ok(${{1:_}}) => {{$2}},\n    Err(${{3:_}}) => {{$0}},\n}}"),
176                )
177                .add_to(acc, ctx.db);
178                }
179                TryEnum::Option => {
180                    postfix_snippet(
181                    "match",
182                    "match expr {}",
183                    &format!(
184                        "match {receiver_text} {{\n    Some(${{1:_}}) => {{$2}},\n    None => {{$0}},\n}}"
185                    ),
186                )
187                .add_to(acc, ctx.db);
188                }
189            },
190            None => {
191                postfix_snippet(
192                    "match",
193                    "match expr {}",
194                    &format!("match {receiver_text} {{\n    ${{1:_}} => {{$0}},\n}}"),
195                )
196                .add_to(acc, ctx.db);
197            }
198        }
199        if let Some(try_enum) = &try_enum {
200            let placeholder = suggest_receiver_name(dot_receiver, "1", &ctx.sema);
201            match try_enum {
202                TryEnum::Result => {
203                    postfix_snippet(
204                        "ifl",
205                        "if let Ok {}",
206                        &format!("if let Ok({placeholder}) = {receiver_text} {{\n    $0\n}}"),
207                    )
208                    .add_to(acc, ctx.db);
209
210                    postfix_snippet(
211                        "lete",
212                        "let Ok else {}",
213                        &format!(
214                            "let Ok({placeholder}) = {receiver_text} else {{\n    $2\n}};\n$0"
215                        ),
216                    )
217                    .add_to(acc, ctx.db);
218
219                    postfix_snippet(
220                        "while",
221                        "while let Ok {}",
222                        &format!("while let Ok({placeholder}) = {receiver_text} {{\n    $0\n}}"),
223                    )
224                    .add_to(acc, ctx.db);
225                }
226                TryEnum::Option => {
227                    postfix_snippet(
228                        "ifl",
229                        "if let Some {}",
230                        &format!("if let Some({placeholder}) = {receiver_text} {{\n    $0\n}}"),
231                    )
232                    .add_to(acc, ctx.db);
233
234                    postfix_snippet(
235                        "lete",
236                        "let Some else {}",
237                        &format!(
238                            "let Some({placeholder}) = {receiver_text} else {{\n    $2\n}};\n$0"
239                        ),
240                    )
241                    .add_to(acc, ctx.db);
242
243                    postfix_snippet(
244                        "while",
245                        "while let Some {}",
246                        &format!("while let Some({placeholder}) = {receiver_text} {{\n    $0\n}}"),
247                    )
248                    .add_to(acc, ctx.db);
249                }
250            }
251        } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
252            postfix_snippet("if", "if expr {}", &format!("if {receiver_text} {{\n    $0\n}}"))
253                .add_to(acc, ctx.db);
254            postfix_snippet(
255                "while",
256                "while expr {}",
257                &format!("while {receiver_text} {{\n    $0\n}}"),
258            )
259            .add_to(acc, ctx.db);
260        } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator()
261            && receiver_ty.impls_trait(ctx.db, trait_, &[])
262        {
263            postfix_snippet(
264                "for",
265                "for ele in expr {}",
266                &format!("for ele in {receiver_text} {{\n    $0\n}}"),
267            )
268            .add_to(acc, ctx.db);
269        }
270    }
271
272    if receiver_ty.is_bool() || receiver_ty.is_unknown() {
273        postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
274    }
275
276    let block_should_be_wrapped = if let ast::Expr::BlockExpr(block) = dot_receiver {
277        block.modifier().is_some() || !block.is_standalone()
278    } else {
279        true
280    };
281    {
282        let (open_brace, close_brace) =
283            if block_should_be_wrapped { ("{ ", " }") } else { ("", "") };
284        // FIXME: Why add parentheses
285        let (open_paren, close_paren) = if is_in_cond { ("(", ")") } else { ("", "") };
286        let unsafe_completion_string =
287            format!("{open_paren}unsafe {open_brace}{receiver_text}{close_brace}{close_paren}");
288        postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
289
290        let const_completion_string =
291            format!("{open_paren}const {open_brace}{receiver_text}{close_brace}{close_paren}");
292        postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);
293    }
294
295    if let ast::Expr::Literal(literal) = dot_receiver_including_refs.clone()
296        && let Some(literal_text) = ast::String::cast(literal.token())
297    {
298        add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text);
299    }
300
301    postfix_snippet(
302        "return",
303        "return expr",
304        &format!(
305            "return {receiver_text}{semi}",
306            semi = if expr_ctx.in_block_expr { ";" } else { "" }
307        ),
308    )
309    .add_to(acc, ctx.db);
310
311    if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {
312        postfix_snippet(
313            "break",
314            "break expr",
315            &format!(
316                "break {receiver_text}{semi}",
317                semi = if expr_ctx.in_block_expr { ";" } else { "" }
318            ),
319        )
320        .add_to(acc, ctx.db);
321    }
322}
323
324fn suggest_receiver_name(
325    receiver: &ast::Expr,
326    n: &str,
327    sema: &Semantics<'_, RootDatabase>,
328) -> SmolStr {
329    let placeholder = |name| format_smolstr!("${{{n}:{name}}}");
330
331    match receiver {
332        ast::Expr::PathExpr(path) => {
333            if let Some(name) = path.path().and_then(|it| it.as_single_name_ref()) {
334                return placeholder(name.text().as_str());
335            }
336        }
337        ast::Expr::RefExpr(it) => {
338            if let Some(receiver) = it.expr() {
339                return suggest_receiver_name(&receiver, n, sema);
340            }
341        }
342        _ => {}
343    }
344
345    let name = NameGenerator::new_with_names([].into_iter()).try_for_variable(receiver, sema);
346    match name {
347        Some(name) => placeholder(&name),
348        None => format_smolstr!("${n}"),
349    }
350}
351
352fn get_receiver_text(
353    sema: &Semantics<'_, RootDatabase>,
354    receiver: &ast::Expr,
355    receiver_is_ambiguous_float_literal: bool,
356) -> String {
357    // Do not just call `receiver.to_string()`, as that will mess up whitespaces inside macros.
358    let Some(mut range) = sema.original_range_opt(receiver.syntax()) else {
359        return receiver.to_string();
360    };
361    if receiver_is_ambiguous_float_literal {
362        range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))
363    }
364    let file_text = sema.db.file_text(range.file_id.file_id(sema.db));
365    let mut text = file_text.text(sema.db)[range.range].to_owned();
366
367    // The receiver texts should be interpreted as-is, as they are expected to be
368    // normal Rust expressions.
369    escape_snippet_bits(&mut text);
370    text
371}
372
373/// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs.
374///
375/// Note that we don't need to escape the other characters that can be escaped,
376/// because they wouldn't be treated as snippet-specific constructs without '$'.
377fn escape_snippet_bits(text: &mut String) {
378    stdx::replace(text, '\\', "\\\\");
379    stdx::replace(text, '$', "\\$");
380}
381
382fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
383    let mut resulting_element = initial_element.clone();
384
385    while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
386    {
387        resulting_element = ast::Expr::from(field_expr);
388    }
389
390    let mut prefix = String::new();
391
392    let mut found_ref_or_deref = false;
393
394    while let Some(parent_deref_element) =
395        resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)
396    {
397        if parent_deref_element.op_kind() != Some(ast::UnaryOp::Deref) {
398            break;
399        }
400
401        found_ref_or_deref = true;
402        resulting_element = ast::Expr::from(parent_deref_element);
403
404        prefix.insert(0, '*');
405    }
406
407    while let Some(parent_ref_element) =
408        resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
409    {
410        found_ref_or_deref = true;
411        let last_child_or_token = parent_ref_element.syntax().last_child_or_token();
412        prefix.insert_str(
413            0,
414            parent_ref_element
415                .syntax()
416                .children_with_tokens()
417                .filter(|it| Some(it) != last_child_or_token.as_ref())
418                .format("")
419                .to_smolstr()
420                .as_str(),
421        );
422        resulting_element = ast::Expr::from(parent_ref_element);
423    }
424
425    if !found_ref_or_deref {
426        // If we do not find any ref/deref expressions, restore
427        // all the progress of tree climbing
428        prefix.clear();
429        resulting_element = initial_element.clone();
430    }
431
432    (resulting_element, prefix)
433}
434
435fn build_postfix_snippet_builder<'ctx>(
436    ctx: &'ctx CompletionContext<'_>,
437    cap: SnippetCap,
438    receiver: &'ctx ast::Expr,
439) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
440    let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
441    if ctx.source_range().end() < receiver_range.start() {
442        // This shouldn't happen, yet it does. I assume this might be due to an incorrect token
443        // mapping.
444        never!();
445        return None;
446    }
447    let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
448
449    // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that
450    // can't be annotated for the closure, hence fix it by constructing it without the Option first
451    fn build<'ctx>(
452        ctx: &'ctx CompletionContext<'_>,
453        cap: SnippetCap,
454        delete_range: TextRange,
455    ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
456        move |label, detail, snippet| {
457            let edit = TextEdit::replace(delete_range, snippet.to_owned());
458            let mut item = CompletionItem::new(
459                CompletionItemKind::Snippet,
460                ctx.source_range(),
461                label,
462                ctx.edition,
463            );
464            item.detail(detail).snippet_edit(cap, edit);
465            let postfix_match = if ctx.original_token.text() == label {
466                cov_mark::hit!(postfix_exact_match_is_high_priority);
467                Some(CompletionRelevancePostfixMatch::Exact)
468            } else {
469                cov_mark::hit!(postfix_inexact_match_is_low_priority);
470                Some(CompletionRelevancePostfixMatch::NonExact)
471            };
472            let relevance = CompletionRelevance { postfix_match, ..Default::default() };
473            item.set_relevance(relevance);
474            item
475        }
476    }
477    Some(build(ctx, cap, delete_range))
478}
479
480fn add_custom_postfix_completions(
481    acc: &mut Completions,
482    ctx: &CompletionContext<'_>,
483    postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
484    receiver_text: &str,
485) -> Option<()> {
486    ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;
487    ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(
488        |(trigger, snippet)| {
489            let imports = match snippet.imports(ctx) {
490                Some(imports) => imports,
491                None => return,
492            };
493            let body = snippet.postfix_snippet(receiver_text);
494            let mut builder =
495                postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
496            builder.documentation(Documentation::new_owned(format!("```rust\n{body}\n```")));
497            for import in imports.into_iter() {
498                builder.add_import(import);
499            }
500            builder.add_to(acc, ctx.db);
501        },
502    );
503    None
504}
505
506pub(crate) fn is_in_condition(it: &ast::Expr) -> bool {
507    it.syntax()
508        .parent()
509        .and_then(|parent| {
510            Some(match_ast! { match parent {
511                ast::IfExpr(expr) => expr.condition()? == *it,
512                ast::WhileExpr(expr) => expr.condition()? == *it,
513                ast::MatchGuard(guard) => guard.condition()? == *it,
514                ast::BinExpr(bin_expr) => (bin_expr.op_token()?.kind() == T![&&])
515                    .then(|| is_in_condition(&bin_expr.into()))?,
516                ast::Expr(expr) => (expr.syntax().text_range().start() == it.syntax().text_range().start())
517                    .then(|| is_in_condition(&expr))?,
518                _ => return None,
519            } })
520        })
521        .unwrap_or(false)
522}
523
524#[cfg(test)]
525mod tests {
526    use expect_test::expect;
527
528    use crate::{
529        CompletionConfig, Snippet,
530        tests::{TEST_CONFIG, check, check_edit, check_edit_with_config},
531    };
532
533    #[test]
534    fn postfix_completion_works_for_trivial_path_expression() {
535        check(
536            r#"
537fn main() {
538    let bar = true;
539    bar.$0
540}
541"#,
542            expect![[r#"
543                sn box  Box::new(expr)
544                sn call function(expr)
545                sn const      const {}
546                sn dbg      dbg!(expr)
547                sn dbgr    dbg!(&expr)
548                sn deref         *expr
549                sn if       if expr {}
550                sn let             let
551                sn letm        let mut
552                sn match match expr {}
553                sn not           !expr
554                sn ref           &expr
555                sn refm      &mut expr
556                sn return  return expr
557                sn unsafe    unsafe {}
558                sn while while expr {}
559            "#]],
560        );
561    }
562
563    #[test]
564    fn postfix_completion_works_for_function_calln() {
565        check(
566            r#"
567fn foo(elt: bool) -> bool {
568    !elt
569}
570
571fn main() {
572    let bar = true;
573    foo(bar.$0)
574}
575"#,
576            expect![[r#"
577                sn box  Box::new(expr)
578                sn call function(expr)
579                sn const      const {}
580                sn dbg      dbg!(expr)
581                sn dbgr    dbg!(&expr)
582                sn deref         *expr
583                sn if       if expr {}
584                sn match match expr {}
585                sn not           !expr
586                sn ref           &expr
587                sn refm      &mut expr
588                sn return  return expr
589                sn unsafe    unsafe {}
590                sn while while expr {}
591            "#]],
592        );
593    }
594
595    #[test]
596    fn postfix_completion_works_in_if_condition() {
597        check(
598            r#"
599fn foo(cond: bool) {
600    if cond.$0
601}
602"#,
603            expect![[r#"
604                sn box  Box::new(expr)
605                sn call function(expr)
606                sn const      const {}
607                sn dbg      dbg!(expr)
608                sn dbgr    dbg!(&expr)
609                sn deref         *expr
610                sn let             let
611                sn not           !expr
612                sn ref           &expr
613                sn refm      &mut expr
614                sn return  return expr
615                sn unsafe    unsafe {}
616            "#]],
617        );
618    }
619
620    #[test]
621    fn postfix_type_filtering() {
622        check(
623            r#"
624fn main() {
625    let bar: u8 = 12;
626    bar.$0
627}
628"#,
629            expect![[r#"
630                sn box  Box::new(expr)
631                sn call function(expr)
632                sn const      const {}
633                sn dbg      dbg!(expr)
634                sn dbgr    dbg!(&expr)
635                sn deref         *expr
636                sn let             let
637                sn letm        let mut
638                sn match match expr {}
639                sn ref           &expr
640                sn refm      &mut expr
641                sn return  return expr
642                sn unsafe    unsafe {}
643            "#]],
644        )
645    }
646
647    #[test]
648    fn let_middle_block() {
649        check(
650            r#"
651fn main() {
652    baz.l$0
653    res
654}
655"#,
656            expect![[r#"
657                sn box  Box::new(expr)
658                sn call function(expr)
659                sn const      const {}
660                sn dbg      dbg!(expr)
661                sn dbgr    dbg!(&expr)
662                sn deref         *expr
663                sn if       if expr {}
664                sn let             let
665                sn letm        let mut
666                sn match match expr {}
667                sn not           !expr
668                sn ref           &expr
669                sn refm      &mut expr
670                sn return  return expr
671                sn unsafe    unsafe {}
672                sn while while expr {}
673            "#]],
674        );
675    }
676
677    #[test]
678    fn option_iflet() {
679        check_edit(
680            "ifl",
681            r#"
682//- minicore: option
683fn main() {
684    let bar = Some(true);
685    bar.$0
686}
687"#,
688            r#"
689fn main() {
690    let bar = Some(true);
691    if let Some(${1:bar}) = bar {
692    $0
693}
694}
695"#,
696        );
697    }
698
699    #[test]
700    fn option_iflet_cond() {
701        check(
702            r#"
703//- minicore: option
704fn main() {
705    let bar = Some(true);
706    if bar.$0
707}
708"#,
709            expect![[r#"
710                me and(…)    fn(self, Option<U>) -> Option<U>
711                me as_ref()     const fn(&self) -> Option<&T>
712                me ok_or(…) const fn(self, E) -> Result<T, E>
713                me unwrap()               const fn(self) -> T
714                me unwrap_or(…)              fn(self, T) -> T
715                sn box                         Box::new(expr)
716                sn call                        function(expr)
717                sn const                             const {}
718                sn dbg                             dbg!(expr)
719                sn dbgr                           dbg!(&expr)
720                sn deref                                *expr
721                sn let                            let Some(_)
722                sn letm                       let Some(mut _)
723                sn ref                                  &expr
724                sn refm                             &mut expr
725                sn return                         return expr
726                sn unsafe                           unsafe {}
727            "#]],
728        );
729        check_edit(
730            "let",
731            r#"
732//- minicore: option
733fn main() {
734    let bar = Some(true);
735    if bar.$0
736}
737"#,
738            r#"
739fn main() {
740    let bar = Some(true);
741    if let Some(${0:bar}) = bar
742}
743"#,
744        );
745        check_edit(
746            "let",
747            r#"
748//- minicore: option
749fn main() {
750    let bar = Some(true);
751    if true && bar.$0
752}
753"#,
754            r#"
755fn main() {
756    let bar = Some(true);
757    if true && let Some(${0:bar}) = bar
758}
759"#,
760        );
761        check_edit(
762            "let",
763            r#"
764//- minicore: option
765fn main() {
766    let bar = Some(true);
767    if true && true && bar.$0
768}
769"#,
770            r#"
771fn main() {
772    let bar = Some(true);
773    if true && true && let Some(${0:bar}) = bar
774}
775"#,
776        );
777    }
778
779    #[test]
780    fn iflet_fallback_cond() {
781        check_edit(
782            "let",
783            r#"
784fn main() {
785    let bar = 2;
786    if bar.$0
787}
788"#,
789            r#"
790fn main() {
791    let bar = 2;
792    if let $1 = bar
793}
794"#,
795        );
796    }
797
798    #[test]
799    fn option_letelse() {
800        check_edit(
801            "lete",
802            r#"
803//- minicore: option
804fn main() {
805    let bar = Some(true);
806    bar.$0
807}
808"#,
809            r#"
810fn main() {
811    let bar = Some(true);
812    let Some(${1:bar}) = bar else {
813    $2
814};
815$0
816}
817"#,
818        );
819    }
820
821    #[test]
822    fn result_match() {
823        check_edit(
824            "match",
825            r#"
826//- minicore: result
827fn main() {
828    let bar = Ok(true);
829    bar.$0
830}
831"#,
832            r#"
833fn main() {
834    let bar = Ok(true);
835    match bar {
836    Ok(${1:_}) => {$2},
837    Err(${3:_}) => {$0},
838}
839}
840"#,
841        );
842    }
843
844    #[test]
845    fn postfix_completion_works_for_ambiguous_float_literal() {
846        check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
847    }
848
849    #[test]
850    fn works_in_simple_macro() {
851        check_edit(
852            "dbg",
853            r#"
854macro_rules! m { ($e:expr) => { $e } }
855fn main() {
856    let bar: u8 = 12;
857    m!(bar.d$0)
858}
859"#,
860            r#"
861macro_rules! m { ($e:expr) => { $e } }
862fn main() {
863    let bar: u8 = 12;
864    m!(dbg!(bar))
865}
866"#,
867        );
868    }
869
870    #[test]
871    fn postfix_completion_for_references() {
872        check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
873        check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
874        check_edit(
875            "ifl",
876            r#"
877//- minicore: option
878fn main() {
879    let bar = &Some(true);
880    bar.$0
881}
882"#,
883            r#"
884fn main() {
885    let bar = &Some(true);
886    if let Some(${1:bar}) = bar {
887    $0
888}
889}
890"#,
891        )
892    }
893
894    #[test]
895    fn postfix_completion_for_unsafe() {
896        postfix_completion_for_block("unsafe");
897    }
898
899    #[test]
900    fn postfix_completion_for_const() {
901        postfix_completion_for_block("const");
902    }
903
904    fn postfix_completion_for_block(kind: &str) {
905        check_edit(kind, r#"fn main() { foo.$0 }"#, &format!("fn main() {{ {kind} {{ foo }} }}"));
906        check_edit(
907            kind,
908            r#"fn main() { { foo }.$0 }"#,
909            &format!("fn main() {{ {kind} {{ foo }} }}"),
910        );
911        check_edit(
912            kind,
913            r#"fn main() { if x { foo }.$0 }"#,
914            &format!("fn main() {{ {kind} {{ if x {{ foo }} }} }}"),
915        );
916        check_edit(
917            kind,
918            r#"fn main() { loop { foo }.$0 }"#,
919            &format!("fn main() {{ {kind} {{ loop {{ foo }} }} }}"),
920        );
921        check_edit(
922            kind,
923            r#"fn main() { if true {}.$0 }"#,
924            &format!("fn main() {{ {kind} {{ if true {{}} }} }}"),
925        );
926        check_edit(
927            kind,
928            r#"fn main() { while true {}.$0 }"#,
929            &format!("fn main() {{ {kind} {{ while true {{}} }} }}"),
930        );
931        check_edit(
932            kind,
933            r#"fn main() { for i in 0..10 {}.$0 }"#,
934            &format!("fn main() {{ {kind} {{ for i in 0..10 {{}} }} }}"),
935        );
936        check_edit(
937            kind,
938            r#"fn main() { let x = if true {1} else {2}.$0 }"#,
939            &format!("fn main() {{ let x = {kind} {{ if true {{1}} else {{2}} }} }}"),
940        );
941
942        if kind == "const" {
943            check_edit(
944                kind,
945                r#"fn main() { unsafe {1}.$0 }"#,
946                &format!("fn main() {{ {kind} {{ unsafe {{1}} }} }}"),
947            );
948        } else {
949            check_edit(
950                kind,
951                r#"fn main() { const {1}.$0 }"#,
952                &format!("fn main() {{ {kind} {{ const {{1}} }} }}"),
953            );
954        }
955
956        // completion will not be triggered
957        check_edit(
958            kind,
959            r#"fn main() { let x = true else {panic!()}.$0}"#,
960            &format!("fn main() {{ let x = true else {{panic!()}}.{kind} $0}}"),
961        );
962    }
963
964    #[test]
965    fn custom_postfix_completion() {
966        let config = CompletionConfig {
967            snippets: vec![
968                Snippet::new(
969                    &[],
970                    &["break".into()],
971                    &["ControlFlow::Break(${receiver})".into()],
972                    "",
973                    &["core::ops::ControlFlow".into()],
974                    crate::SnippetScope::Expr,
975                )
976                .unwrap(),
977            ],
978            ..TEST_CONFIG
979        };
980
981        check_edit_with_config(
982            config.clone(),
983            "break",
984            r#"
985//- minicore: try
986fn main() { 42.$0 }
987"#,
988            r#"
989use core::ops::ControlFlow;
990
991fn main() { ControlFlow::Break(42) }
992"#,
993        );
994
995        // The receiver texts should be escaped, see comments in `get_receiver_text()`
996        // for detail.
997        //
998        // Note that the last argument is what *lsp clients would see* rather than
999        // what users would see. Unescaping happens thereafter.
1000        check_edit_with_config(
1001            config.clone(),
1002            "break",
1003            r#"
1004//- minicore: try
1005fn main() { '\\'.$0 }
1006"#,
1007            r#"
1008use core::ops::ControlFlow;
1009
1010fn main() { ControlFlow::Break('\\\\') }
1011"#,
1012        );
1013
1014        check_edit_with_config(
1015            config,
1016            "break",
1017            r#"
1018//- minicore: try
1019fn main() {
1020    match true {
1021        true => "${1:placeholder}",
1022        false => "\$",
1023    }.$0
1024}
1025"#,
1026            r#"
1027use core::ops::ControlFlow;
1028
1029fn main() {
1030    ControlFlow::Break(match true {
1031        true => "\${1:placeholder}",
1032        false => "\\\$",
1033    })
1034}
1035"#,
1036        );
1037    }
1038
1039    #[test]
1040    fn postfix_completion_for_format_like_strings() {
1041        check_edit(
1042            "format",
1043            r#"fn main() { "{some_var:?}".$0 }"#,
1044            r#"fn main() { format!("{some_var:?}") }"#,
1045        );
1046        check_edit(
1047            "panic",
1048            r#"fn main() { "Panic with {a}".$0 }"#,
1049            r#"fn main() { panic!("Panic with {a}") }"#,
1050        );
1051        check_edit(
1052            "println",
1053            r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
1054            r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
1055        );
1056        check_edit(
1057            "loge",
1058            r#"fn main() { "{2+2}".$0 }"#,
1059            r#"fn main() { log::error!("{}", 2+2) }"#,
1060        );
1061        check_edit(
1062            "logt",
1063            r#"fn main() { "{2+2}".$0 }"#,
1064            r#"fn main() { log::trace!("{}", 2+2) }"#,
1065        );
1066        check_edit(
1067            "logd",
1068            r#"fn main() { "{2+2}".$0 }"#,
1069            r#"fn main() { log::debug!("{}", 2+2) }"#,
1070        );
1071        check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
1072        check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
1073        check_edit(
1074            "loge",
1075            r#"fn main() { "{2+2}".$0 }"#,
1076            r#"fn main() { log::error!("{}", 2+2) }"#,
1077        );
1078    }
1079
1080    #[test]
1081    fn postfix_custom_snippets_completion_for_references() {
1082        // https://github.com/rust-lang/rust-analyzer/issues/7929
1083
1084        let snippet = Snippet::new(
1085            &[],
1086            &["ok".into()],
1087            &["Ok(${receiver})".into()],
1088            "",
1089            &[],
1090            crate::SnippetScope::Expr,
1091        )
1092        .unwrap();
1093
1094        check_edit_with_config(
1095            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1096            "ok",
1097            r#"fn main() { &&42.o$0 }"#,
1098            r#"fn main() { Ok(&&42) }"#,
1099        );
1100
1101        check_edit_with_config(
1102            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1103            "ok",
1104            r#"fn main() { &&42.$0 }"#,
1105            r#"fn main() { Ok(&&42) }"#,
1106        );
1107
1108        check_edit_with_config(
1109            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1110            "ok",
1111            r#"fn main() { &raw mut 42.$0 }"#,
1112            r#"fn main() { Ok(&raw mut 42) }"#,
1113        );
1114
1115        check_edit_with_config(
1116            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1117            "ok",
1118            r#"fn main() { &raw const 42.$0 }"#,
1119            r#"fn main() { Ok(&raw const 42) }"#,
1120        );
1121
1122        check_edit_with_config(
1123            CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },
1124            "ok",
1125            r#"
1126struct A {
1127    a: i32,
1128}
1129
1130fn main() {
1131    let a = A {a :1};
1132    &a.a.$0
1133}
1134            "#,
1135            r#"
1136struct A {
1137    a: i32,
1138}
1139
1140fn main() {
1141    let a = A {a :1};
1142    Ok(&a.a)
1143}
1144            "#,
1145        );
1146    }
1147
1148    #[test]
1149    fn postfix_custom_snippets_completion_for_reference_expr() {
1150        // https://github.com/rust-lang/rust-analyzer/issues/21035
1151        let snippet = Snippet::new(
1152            &[],
1153            &["group".into()],
1154            &["(${receiver})".into()],
1155            "",
1156            &[],
1157            crate::SnippetScope::Expr,
1158        )
1159        .unwrap();
1160
1161        check_edit_with_config(
1162            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1163            "group",
1164            r#"fn main() { &[1, 2, 3].g$0 }"#,
1165            r#"fn main() { (&[1, 2, 3]) }"#,
1166        );
1167
1168        check_edit_with_config(
1169            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1170            "group",
1171            r#"fn main() { &&foo(a, b, 1+1).$0 }"#,
1172            r#"fn main() { (&&foo(a, b, 1+1)) }"#,
1173        );
1174
1175        check_edit_with_config(
1176            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1177            "group",
1178            r#"fn main() { &mut Foo { a: 1, b: 2, c: 3 }.$0 }"#,
1179            r#"fn main() { (&mut Foo { a: 1, b: 2, c: 3 }) }"#,
1180        );
1181
1182        check_edit_with_config(
1183            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1184            "group",
1185            r#"fn main() { &raw mut Foo::new().$0 }"#,
1186            r#"fn main() { (&raw mut Foo::new()) }"#,
1187        );
1188
1189        check_edit_with_config(
1190            CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
1191            "group",
1192            r#"fn main() { &raw const Foo::bar::SOME_CONST.$0 }"#,
1193            r#"fn main() { (&raw const Foo::bar::SOME_CONST) }"#,
1194        );
1195    }
1196
1197    #[test]
1198    fn no_postfix_completions_in_if_block_that_has_an_else() {
1199        check(
1200            r#"
1201fn test() {
1202    if true {}.$0 else {}
1203}
1204"#,
1205            expect![[r#""#]],
1206        );
1207    }
1208
1209    #[test]
1210    fn mut_ref_consuming() {
1211        check_edit(
1212            "call",
1213            r#"
1214fn main() {
1215    let mut x = &mut 2;
1216    &mut x.$0;
1217}
1218"#,
1219            r#"
1220fn main() {
1221    let mut x = &mut 2;
1222    ${1}(&mut x);
1223}
1224"#,
1225        );
1226    }
1227
1228    #[test]
1229    fn deref_consuming() {
1230        check_edit(
1231            "call",
1232            r#"
1233fn main() {
1234    let mut x = &mut 2;
1235    &mut *x.$0;
1236}
1237"#,
1238            r#"
1239fn main() {
1240    let mut x = &mut 2;
1241    ${1}(&mut *x);
1242}
1243"#,
1244        );
1245    }
1246
1247    #[test]
1248    fn inside_macro() {
1249        check_edit(
1250            "box",
1251            r#"
1252macro_rules! assert {
1253    ( $it:expr $(,)? ) => { $it };
1254}
1255
1256fn foo() {
1257    let a = true;
1258    assert!(if a == false { true } else { false }.$0);
1259}
1260        "#,
1261            r#"
1262macro_rules! assert {
1263    ( $it:expr $(,)? ) => { $it };
1264}
1265
1266fn foo() {
1267    let a = true;
1268    assert!(Box::new(if a == false { true } else { false }));
1269}
1270        "#,
1271        );
1272    }
1273}