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