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