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