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