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 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 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 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 escape_snippet_bits(&mut text);
363 text
364}
365
366fn 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 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 never!();
438 return None;
439 }
440 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
441
442 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 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 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 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 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}