]> git.proxmox.com Git - rustc.git/blob - src/tools/rust-analyzer/crates/ide-completion/src/completions/postfix.rs
bump version to 1.80.1+dfsg1-1~bpo12+pve1
[rustc.git] / src / tools / rust-analyzer / crates / ide-completion / src / completions / postfix.rs
1 //! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.
2
3 mod format_like;
4
5 use hir::{ImportPathConfig, ItemInNs};
6 use ide_db::{
7 documentation::{Documentation, HasDocs},
8 imports::insert_use::ImportScope,
9 ty_filter::TryEnum,
10 SnippetCap,
11 };
12 use stdx::never;
13 use syntax::{
14 ast::{self, make, AstNode, AstToken},
15 SyntaxKind::{BLOCK_EXPR, EXPR_STMT, FOR_EXPR, IF_EXPR, LOOP_EXPR, STMT_LIST, WHILE_EXPR},
16 TextRange, TextSize,
17 };
18 use text_edit::TextEdit;
19
20 use crate::{
21 completions::postfix::format_like::add_format_like_completions,
22 context::{BreakableKind, CompletionContext, DotAccess, DotAccessKind},
23 item::{Builder, CompletionRelevancePostfixMatch},
24 CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
25 };
26
27 pub(crate) fn complete_postfix(
28 acc: &mut Completions,
29 ctx: &CompletionContext<'_>,
30 dot_access: &DotAccess,
31 ) {
32 if !ctx.config.enable_postfix_completions {
33 return;
34 }
35
36 let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match dot_access {
37 DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. } => (
38 it,
39 &ty.original,
40 match *kind {
41 DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {
42 receiver_is_ambiguous_float_literal
43 }
44 DotAccessKind::Method { .. } => false,
45 },
46 ),
47 _ => return,
48 };
49 let expr_ctx = &dot_access.ctx;
50
51 let receiver_text = get_receiver_text(dot_receiver, receiver_is_ambiguous_float_literal);
52
53 let cap = match ctx.config.snippet_cap {
54 Some(it) => it,
55 None => return,
56 };
57
58 let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {
59 Some(it) => it,
60 None => return,
61 };
62
63 let cfg = ImportPathConfig {
64 prefer_no_std: ctx.config.prefer_no_std,
65 prefer_prelude: ctx.config.prefer_prelude,
66 };
67
68 if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop() {
69 if receiver_ty.impls_trait(ctx.db, drop_trait, &[]) {
70 if let Some(drop_fn) = ctx.famous_defs().core_mem_drop() {
71 if let Some(path) =
72 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)),
79 );
80 item.set_documentation(drop_fn.docs(ctx.db));
81 item.add_to(acc, ctx.db);
82 }
83 }
84 }
85 }
86
87 let try_enum = TryEnum::from_ty(&ctx.sema, &receiver_ty.strip_references());
88 if let Some(try_enum) = &try_enum {
89 match try_enum {
90 TryEnum::Result => {
91 postfix_snippet(
92 "ifl",
93 "if let Ok {}",
94 &format!("if let Ok($1) = {receiver_text} {{\n $0\n}}"),
95 )
96 .add_to(acc, ctx.db);
97
98 postfix_snippet(
99 "lete",
100 "let Ok else {}",
101 &format!("let Ok($1) = {receiver_text} else {{\n $2\n}};\n$0"),
102 )
103 .add_to(acc, ctx.db);
104
105 postfix_snippet(
106 "while",
107 "while let Ok {}",
108 &format!("while let Ok($1) = {receiver_text} {{\n $0\n}}"),
109 )
110 .add_to(acc, ctx.db);
111 }
112 TryEnum::Option => {
113 postfix_snippet(
114 "ifl",
115 "if let Some {}",
116 &format!("if let Some($1) = {receiver_text} {{\n $0\n}}"),
117 )
118 .add_to(acc, ctx.db);
119
120 postfix_snippet(
121 "lete",
122 "let Some else {}",
123 &format!("let Some($1) = {receiver_text} else {{\n $2\n}};\n$0"),
124 )
125 .add_to(acc, ctx.db);
126
127 postfix_snippet(
128 "while",
129 "while let Some {}",
130 &format!("while let Some($1) = {receiver_text} {{\n $0\n}}"),
131 )
132 .add_to(acc, ctx.db);
133 }
134 }
135 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {
136 postfix_snippet("if", "if expr {}", &format!("if {receiver_text} {{\n $0\n}}"))
137 .add_to(acc, ctx.db);
138 postfix_snippet("while", "while expr {}", &format!("while {receiver_text} {{\n $0\n}}"))
139 .add_to(acc, ctx.db);
140 postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);
141 } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator() {
142 if receiver_ty.impls_trait(ctx.db, trait_, &[]) {
143 postfix_snippet(
144 "for",
145 "for ele in expr {}",
146 &format!("for ele in {receiver_text} {{\n $0\n}}"),
147 )
148 .add_to(acc, ctx.db);
149 }
150 }
151
152 postfix_snippet("ref", "&expr", &format!("&{receiver_text}")).add_to(acc, ctx.db);
153 postfix_snippet("refm", "&mut expr", &format!("&mut {receiver_text}")).add_to(acc, ctx.db);
154 postfix_snippet("deref", "*expr", &format!("*{receiver_text}")).add_to(acc, ctx.db);
155
156 let mut unsafe_should_be_wrapped = true;
157 if dot_receiver.syntax().kind() == BLOCK_EXPR {
158 unsafe_should_be_wrapped = false;
159 if let Some(parent) = dot_receiver.syntax().parent() {
160 if matches!(parent.kind(), IF_EXPR | WHILE_EXPR | LOOP_EXPR | FOR_EXPR) {
161 unsafe_should_be_wrapped = true;
162 }
163 }
164 };
165 let unsafe_completion_string = if unsafe_should_be_wrapped {
166 format!("unsafe {{ {receiver_text} }}")
167 } else {
168 format!("unsafe {receiver_text}")
169 };
170 postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);
171
172 // The rest of the postfix completions create an expression that moves an argument,
173 // so it's better to consider references now to avoid breaking the compilation
174
175 let (dot_receiver, node_to_replace_with) = include_references(dot_receiver);
176 let receiver_text =
177 get_receiver_text(&node_to_replace_with, receiver_is_ambiguous_float_literal);
178 let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, &dot_receiver) {
179 Some(it) => it,
180 None => return,
181 };
182
183 if !ctx.config.snippets.is_empty() {
184 add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);
185 }
186
187 match try_enum {
188 Some(try_enum) => match try_enum {
189 TryEnum::Result => {
190 postfix_snippet(
191 "match",
192 "match expr {}",
193 &format!("match {receiver_text} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}"),
194 )
195 .add_to(acc, ctx.db);
196 }
197 TryEnum::Option => {
198 postfix_snippet(
199 "match",
200 "match expr {}",
201 &format!(
202 "match {receiver_text} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}"
203 ),
204 )
205 .add_to(acc, ctx.db);
206 }
207 },
208 None => {
209 postfix_snippet(
210 "match",
211 "match expr {}",
212 &format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),
213 )
214 .add_to(acc, ctx.db);
215 }
216 }
217
218 postfix_snippet("box", "Box::new(expr)", &format!("Box::new({receiver_text})"))
219 .add_to(acc, ctx.db);
220 postfix_snippet("dbg", "dbg!(expr)", &format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme
221 postfix_snippet("dbgr", "dbg!(&expr)", &format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);
222 postfix_snippet("call", "function(expr)", &format!("${{1}}({receiver_text})"))
223 .add_to(acc, ctx.db);
224
225 if let Some(parent) = dot_receiver.syntax().parent().and_then(|p| p.parent()) {
226 if matches!(parent.kind(), STMT_LIST | EXPR_STMT) {
227 postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
228 .add_to(acc, ctx.db);
229 postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
230 .add_to(acc, ctx.db);
231 }
232 }
233
234 if let ast::Expr::Literal(literal) = dot_receiver.clone() {
235 if let Some(literal_text) = ast::String::cast(literal.token()) {
236 add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
237 }
238 }
239
240 postfix_snippet(
241 "return",
242 "return expr",
243 &format!(
244 "return {receiver_text}{semi}",
245 semi = if expr_ctx.in_block_expr { ";" } else { "" }
246 ),
247 )
248 .add_to(acc, ctx.db);
249
250 if let BreakableKind::Block | BreakableKind::Loop = expr_ctx.in_breakable {
251 postfix_snippet(
252 "break",
253 "break expr",
254 &format!(
255 "break {receiver_text}{semi}",
256 semi = if expr_ctx.in_block_expr { ";" } else { "" }
257 ),
258 )
259 .add_to(acc, ctx.db);
260 }
261 }
262
263 fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
264 let mut text = if receiver_is_ambiguous_float_literal {
265 let text = receiver.syntax().text();
266 let without_dot = ..text.len() - TextSize::of('.');
267 text.slice(without_dot).to_string()
268 } else {
269 receiver.to_string()
270 };
271
272 // The receiver texts should be interpreted as-is, as they are expected to be
273 // normal Rust expressions.
274 escape_snippet_bits(&mut text);
275 text
276 }
277
278 /// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs.
279 ///
280 /// Note that we don't need to escape the other characters that can be escaped,
281 /// because they wouldn't be treated as snippet-specific constructs without '$'.
282 fn escape_snippet_bits(text: &mut String) {
283 stdx::replace(text, '\\', "\\\\");
284 stdx::replace(text, '$', "\\$");
285 }
286
287 fn include_references(initial_element: &ast::Expr) -> (ast::Expr, ast::Expr) {
288 let mut resulting_element = initial_element.clone();
289
290 while let Some(field_expr) = resulting_element.syntax().parent().and_then(ast::FieldExpr::cast)
291 {
292 resulting_element = ast::Expr::from(field_expr);
293 }
294
295 let mut new_element_opt = initial_element.clone();
296
297 if let Some(first_ref_expr) = resulting_element.syntax().parent().and_then(ast::RefExpr::cast) {
298 if let Some(expr) = first_ref_expr.expr() {
299 resulting_element = expr;
300 }
301
302 while let Some(parent_ref_element) =
303 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)
304 {
305 resulting_element = ast::Expr::from(parent_ref_element);
306
307 new_element_opt = make::expr_ref(new_element_opt, false);
308 }
309 } else {
310 // If we do not find any ref expressions, restore
311 // all the progress of tree climbing
312 resulting_element = initial_element.clone();
313 }
314
315 (resulting_element, new_element_opt)
316 }
317
318 fn build_postfix_snippet_builder<'ctx>(
319 ctx: &'ctx CompletionContext<'_>,
320 cap: SnippetCap,
321 receiver: &'ctx ast::Expr,
322 ) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {
323 let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;
324 if ctx.source_range().end() < receiver_range.start() {
325 // This shouldn't happen, yet it does. I assume this might be due to an incorrect token
326 // mapping.
327 never!();
328 return None;
329 }
330 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());
331
332 // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that
333 // can't be annotated for the closure, hence fix it by constructing it without the Option first
334 fn build<'ctx>(
335 ctx: &'ctx CompletionContext<'_>,
336 cap: SnippetCap,
337 delete_range: TextRange,
338 ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {
339 move |label, detail, snippet| {
340 let edit = TextEdit::replace(delete_range, snippet.to_owned());
341 let mut item =
342 CompletionItem::new(CompletionItemKind::Snippet, ctx.source_range(), label);
343 item.detail(detail).snippet_edit(cap, edit);
344 let postfix_match = if ctx.original_token.text() == label {
345 cov_mark::hit!(postfix_exact_match_is_high_priority);
346 Some(CompletionRelevancePostfixMatch::Exact)
347 } else {
348 cov_mark::hit!(postfix_inexact_match_is_low_priority);
349 Some(CompletionRelevancePostfixMatch::NonExact)
350 };
351 let relevance = CompletionRelevance { postfix_match, ..Default::default() };
352 item.set_relevance(relevance);
353 item
354 }
355 }
356 Some(build(ctx, cap, delete_range))
357 }
358
359 fn add_custom_postfix_completions(
360 acc: &mut Completions,
361 ctx: &CompletionContext<'_>,
362 postfix_snippet: impl Fn(&str, &str, &str) -> Builder,
363 receiver_text: &str,
364 ) -> Option<()> {
365 ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;
366 ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(
367 |(trigger, snippet)| {
368 let imports = match snippet.imports(ctx) {
369 Some(imports) => imports,
370 None => return,
371 };
372 let body = snippet.postfix_snippet(receiver_text);
373 let mut builder =
374 postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);
375 builder.documentation(Documentation::new(format!("```rust\n{body}\n```")));
376 for import in imports.into_iter() {
377 builder.add_import(import);
378 }
379 builder.add_to(acc, ctx.db);
380 },
381 );
382 None
383 }
384
385 #[cfg(test)]
386 mod tests {
387 use expect_test::{expect, Expect};
388
389 use crate::{
390 tests::{check_edit, check_edit_with_config, completion_list, TEST_CONFIG},
391 CompletionConfig, Snippet,
392 };
393
394 fn check(ra_fixture: &str, expect: Expect) {
395 let actual = completion_list(ra_fixture);
396 expect.assert_eq(&actual)
397 }
398
399 #[test]
400 fn postfix_completion_works_for_trivial_path_expression() {
401 check(
402 r#"
403 fn main() {
404 let bar = true;
405 bar.$0
406 }
407 "#,
408 expect![[r#"
409 sn box Box::new(expr)
410 sn call function(expr)
411 sn dbg dbg!(expr)
412 sn dbgr dbg!(&expr)
413 sn deref *expr
414 sn if if expr {}
415 sn let let
416 sn letm let mut
417 sn match match expr {}
418 sn not !expr
419 sn ref &expr
420 sn refm &mut expr
421 sn return return expr
422 sn unsafe unsafe {}
423 sn while while expr {}
424 "#]],
425 );
426 }
427
428 #[test]
429 fn postfix_completion_works_for_function_calln() {
430 check(
431 r#"
432 fn foo(elt: bool) -> bool {
433 !elt
434 }
435
436 fn main() {
437 let bar = true;
438 foo(bar.$0)
439 }
440 "#,
441 expect![[r#"
442 sn box Box::new(expr)
443 sn call function(expr)
444 sn dbg dbg!(expr)
445 sn dbgr dbg!(&expr)
446 sn deref *expr
447 sn if if expr {}
448 sn match match expr {}
449 sn not !expr
450 sn ref &expr
451 sn refm &mut expr
452 sn return return expr
453 sn unsafe unsafe {}
454 sn while while expr {}
455 "#]],
456 );
457 }
458
459 #[test]
460 fn postfix_type_filtering() {
461 check(
462 r#"
463 fn main() {
464 let bar: u8 = 12;
465 bar.$0
466 }
467 "#,
468 expect![[r#"
469 sn box Box::new(expr)
470 sn call function(expr)
471 sn dbg dbg!(expr)
472 sn dbgr dbg!(&expr)
473 sn deref *expr
474 sn let let
475 sn letm let mut
476 sn match match expr {}
477 sn ref &expr
478 sn refm &mut expr
479 sn return return expr
480 sn unsafe unsafe {}
481 "#]],
482 )
483 }
484
485 #[test]
486 fn let_middle_block() {
487 check(
488 r#"
489 fn main() {
490 baz.l$0
491 res
492 }
493 "#,
494 expect![[r#"
495 sn box Box::new(expr)
496 sn call function(expr)
497 sn dbg dbg!(expr)
498 sn dbgr dbg!(&expr)
499 sn deref *expr
500 sn if if expr {}
501 sn let let
502 sn letm let mut
503 sn match match expr {}
504 sn not !expr
505 sn ref &expr
506 sn refm &mut expr
507 sn return return expr
508 sn unsafe unsafe {}
509 sn while while expr {}
510 "#]],
511 );
512 }
513
514 #[test]
515 fn option_iflet() {
516 check_edit(
517 "ifl",
518 r#"
519 //- minicore: option
520 fn main() {
521 let bar = Some(true);
522 bar.$0
523 }
524 "#,
525 r#"
526 fn main() {
527 let bar = Some(true);
528 if let Some($1) = bar {
529 $0
530 }
531 }
532 "#,
533 );
534 }
535
536 #[test]
537 fn option_letelse() {
538 check_edit(
539 "lete",
540 r#"
541 //- minicore: option
542 fn main() {
543 let bar = Some(true);
544 bar.$0
545 }
546 "#,
547 r#"
548 fn main() {
549 let bar = Some(true);
550 let Some($1) = bar else {
551 $2
552 };
553 $0
554 }
555 "#,
556 );
557 }
558
559 #[test]
560 fn result_match() {
561 check_edit(
562 "match",
563 r#"
564 //- minicore: result
565 fn main() {
566 let bar = Ok(true);
567 bar.$0
568 }
569 "#,
570 r#"
571 fn main() {
572 let bar = Ok(true);
573 match bar {
574 Ok(${1:_}) => {$2},
575 Err(${3:_}) => {$0},
576 }
577 }
578 "#,
579 );
580 }
581
582 #[test]
583 fn postfix_completion_works_for_ambiguous_float_literal() {
584 check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)
585 }
586
587 #[test]
588 fn works_in_simple_macro() {
589 check_edit(
590 "dbg",
591 r#"
592 macro_rules! m { ($e:expr) => { $e } }
593 fn main() {
594 let bar: u8 = 12;
595 m!(bar.d$0)
596 }
597 "#,
598 r#"
599 macro_rules! m { ($e:expr) => { $e } }
600 fn main() {
601 let bar: u8 = 12;
602 m!(dbg!(bar))
603 }
604 "#,
605 );
606 }
607
608 #[test]
609 fn postfix_completion_for_references() {
610 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
611 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
612 check_edit(
613 "ifl",
614 r#"
615 //- minicore: option
616 fn main() {
617 let bar = &Some(true);
618 bar.$0
619 }
620 "#,
621 r#"
622 fn main() {
623 let bar = &Some(true);
624 if let Some($1) = bar {
625 $0
626 }
627 }
628 "#,
629 )
630 }
631
632 #[test]
633 fn postfix_completion_for_unsafe() {
634 check_edit("unsafe", r#"fn main() { foo.$0 }"#, r#"fn main() { unsafe { foo } }"#);
635 check_edit("unsafe", r#"fn main() { { foo }.$0 }"#, r#"fn main() { unsafe { foo } }"#);
636 check_edit(
637 "unsafe",
638 r#"fn main() { if x { foo }.$0 }"#,
639 r#"fn main() { unsafe { if x { foo } } }"#,
640 );
641 check_edit(
642 "unsafe",
643 r#"fn main() { loop { foo }.$0 }"#,
644 r#"fn main() { unsafe { loop { foo } } }"#,
645 );
646 check_edit(
647 "unsafe",
648 r#"fn main() { if true {}.$0 }"#,
649 r#"fn main() { unsafe { if true {} } }"#,
650 );
651 check_edit(
652 "unsafe",
653 r#"fn main() { while true {}.$0 }"#,
654 r#"fn main() { unsafe { while true {} } }"#,
655 );
656 check_edit(
657 "unsafe",
658 r#"fn main() { for i in 0..10 {}.$0 }"#,
659 r#"fn main() { unsafe { for i in 0..10 {} } }"#,
660 );
661 check_edit(
662 "unsafe",
663 r#"fn main() { let x = if true {1} else {2}.$0 }"#,
664 r#"fn main() { let x = unsafe { if true {1} else {2} } }"#,
665 );
666
667 // completion will not be triggered
668 check_edit(
669 "unsafe",
670 r#"fn main() { let x = true else {panic!()}.$0}"#,
671 r#"fn main() { let x = true else {panic!()}.unsafe}"#,
672 );
673 }
674
675 #[test]
676 fn custom_postfix_completion() {
677 let config = CompletionConfig {
678 snippets: vec![Snippet::new(
679 &[],
680 &["break".into()],
681 &["ControlFlow::Break(${receiver})".into()],
682 "",
683 &["core::ops::ControlFlow".into()],
684 crate::SnippetScope::Expr,
685 )
686 .unwrap()],
687 ..TEST_CONFIG
688 };
689
690 check_edit_with_config(
691 config.clone(),
692 "break",
693 r#"
694 //- minicore: try
695 fn main() { 42.$0 }
696 "#,
697 r#"
698 use core::ops::ControlFlow;
699
700 fn main() { ControlFlow::Break(42) }
701 "#,
702 );
703
704 // The receiver texts should be escaped, see comments in `get_receiver_text()`
705 // for detail.
706 //
707 // Note that the last argument is what *lsp clients would see* rather than
708 // what users would see. Unescaping happens thereafter.
709 check_edit_with_config(
710 config.clone(),
711 "break",
712 r#"
713 //- minicore: try
714 fn main() { '\\'.$0 }
715 "#,
716 r#"
717 use core::ops::ControlFlow;
718
719 fn main() { ControlFlow::Break('\\\\') }
720 "#,
721 );
722
723 check_edit_with_config(
724 config,
725 "break",
726 r#"
727 //- minicore: try
728 fn main() {
729 match true {
730 true => "${1:placeholder}",
731 false => "\$",
732 }.$0
733 }
734 "#,
735 r#"
736 use core::ops::ControlFlow;
737
738 fn main() {
739 ControlFlow::Break(match true {
740 true => "\${1:placeholder}",
741 false => "\\\$",
742 })
743 }
744 "#,
745 );
746 }
747
748 #[test]
749 fn postfix_completion_for_format_like_strings() {
750 check_edit(
751 "format",
752 r#"fn main() { "{some_var:?}".$0 }"#,
753 r#"fn main() { format!("{some_var:?}") }"#,
754 );
755 check_edit(
756 "panic",
757 r#"fn main() { "Panic with {a}".$0 }"#,
758 r#"fn main() { panic!("Panic with {a}") }"#,
759 );
760 check_edit(
761 "println",
762 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,
763 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
764 );
765 check_edit(
766 "loge",
767 r#"fn main() { "{2+2}".$0 }"#,
768 r#"fn main() { log::error!("{}", 2+2) }"#,
769 );
770 check_edit(
771 "logt",
772 r#"fn main() { "{2+2}".$0 }"#,
773 r#"fn main() { log::trace!("{}", 2+2) }"#,
774 );
775 check_edit(
776 "logd",
777 r#"fn main() { "{2+2}".$0 }"#,
778 r#"fn main() { log::debug!("{}", 2+2) }"#,
779 );
780 check_edit("logi", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::info!("{}", 2+2) }"#);
781 check_edit("logw", r#"fn main() { "{2+2}".$0 }"#, r#"fn main() { log::warn!("{}", 2+2) }"#);
782 check_edit(
783 "loge",
784 r#"fn main() { "{2+2}".$0 }"#,
785 r#"fn main() { log::error!("{}", 2+2) }"#,
786 );
787 }
788
789 #[test]
790 fn postfix_custom_snippets_completion_for_references() {
791 // https://github.com/rust-lang/rust-analyzer/issues/7929
792
793 let snippet = Snippet::new(
794 &[],
795 &["ok".into()],
796 &["Ok(${receiver})".into()],
797 "",
798 &[],
799 crate::SnippetScope::Expr,
800 )
801 .unwrap();
802
803 check_edit_with_config(
804 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
805 "ok",
806 r#"fn main() { &&42.o$0 }"#,
807 r#"fn main() { Ok(&&42) }"#,
808 );
809
810 check_edit_with_config(
811 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },
812 "ok",
813 r#"fn main() { &&42.$0 }"#,
814 r#"fn main() { Ok(&&42) }"#,
815 );
816
817 check_edit_with_config(
818 CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },
819 "ok",
820 r#"
821 struct A {
822 a: i32,
823 }
824
825 fn main() {
826 let a = A {a :1};
827 &a.a.$0
828 }
829 "#,
830 r#"
831 struct A {
832 a: i32,
833 }
834
835 fn main() {
836 let a = A {a :1};
837 Ok(&a.a)
838 }
839 "#,
840 );
841 }
842
843 #[test]
844 fn no_postfix_completions_in_if_block_that_has_an_else() {
845 check(
846 r#"
847 fn test() {
848 if true {}.$0 else {}
849 }
850 "#,
851 expect![[r#""#]],
852 );
853 }
854 }