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