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