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