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