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