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