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