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