Skip to main content

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