1//! Postfix completions, like `Ok(10).ifl$0` => `if let Ok() = Ok(10) { $0 }`.23mod format_like;45use base_db::SourceDatabase;6use hir::{ItemInNs, Semantics};7use ide_db::{8 RootDatabase, SnippetCap,9 documentation::{Documentation, HasDocs},10 imports::insert_use::ImportScope,11 source_change::SnippetEdit,12 syntax_helpers::suggest_name::NameGenerator,13 text_edit::TextEdit,14 ty_filter::TryEnum,15};16use itertools::{Either, Itertools};17use stdx::never;18use syntax::{19 SmolStr,20 SyntaxKind::{CLOSURE_EXPR, MATCH_ARM},21 T, TextRange, TextSize, ToSmolStr,22 ast::{self, AstNode, AstToken},23 format_smolstr, match_ast,24};2526use crate::{27 CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,28 completions::postfix::format_like::add_format_like_completions,29 context::{BreakableKind, CompletionContext, DotAccess, DotAccessKind},30 item::{Builder, CompletionRelevancePostfixMatch},31};3233pub(crate) fn complete_postfix(34 acc: &mut Completions,35 ctx: &CompletionContext<'_, '_>,36 dot_access: &DotAccess<'_>,37) {38 if !ctx.config.enable_postfix_completions {39 return;40 }4142 let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match dot_access {43 DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. } => (44 it,45 &ty.original,46 match *kind {47 DotAccessKind::Field { receiver_is_ambiguous_float_literal } => {48 receiver_is_ambiguous_float_literal49 }50 DotAccessKind::Method => false,51 },52 ),53 _ => return,54 };55 let expr_ctx = &dot_access.ctx;56 let receiver_accessor = receiver_accessor(dot_receiver);5758 let receiver_text =59 get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);6061 let cap = match ctx.config.snippet_cap {62 Some(it) => it,63 None => return,64 };6566 let postfix_snippet = match build_postfix_snippet_builder(ctx, cap, dot_receiver) {67 Some(it) => it,68 None => return,69 };70 let semi =71 if expr_ctx.in_block_expr && ctx.token.next_token().is_none_or(|it| it.kind() != T![;]) {72 ";"73 } else {74 ""75 };7677 let cfg = ctx.config.find_path_config(ctx.is_nightly);7879 if let Some(drop_trait) = ctx.famous_defs().core_ops_Drop()80 && receiver_ty.impls_trait(ctx.db, drop_trait, &[])81 && let Some(drop_fn) = ctx.famous_defs().core_mem_drop()82 && let Some(path) = ctx.module.find_path(ctx.db, ItemInNs::Values(drop_fn.into()), cfg)83 {84 cov_mark::hit!(postfix_drop_completion);85 let mut item = postfix_snippet(86 "drop",87 "fn drop(&mut self)",88 format!("{path}($0{receiver_text})", path = path.display(ctx.db, ctx.edition)),89 );90 item.set_documentation(drop_fn.docs(ctx.db));91 item.add_to(acc, ctx.db);92 }9394 postfix_snippet("ref", "&expr", format!("&{receiver_text}")).add_to(acc, ctx.db);95 postfix_snippet("refm", "&mut expr", format!("&mut {receiver_text}")).add_to(acc, ctx.db);96 postfix_snippet("deref", "*expr", format!("*{receiver_text}")).add_to(acc, ctx.db);9798 // The rest of the postfix completions create an expression that moves an argument,99 // so it's better to consider references now to avoid breaking the compilation100101 let (dot_receiver_including_refs, prefix) = include_references(&receiver_accessor);102 let mut receiver_text = receiver_text;103 receiver_text.insert_str(0, &prefix);104 let postfix_snippet =105 match build_postfix_snippet_builder(ctx, cap, &dot_receiver_including_refs) {106 Some(it) => it,107 None => return,108 };109110 if !ctx.config.snippets.is_empty() {111 add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);112 }113114 postfix_snippet("box", "Box::new(expr)", format!("Box::new({receiver_text})"))115 .add_to(acc, ctx.db);116 postfix_snippet("dbg", "dbg!(expr)", format!("dbg!({receiver_text})")).add_to(acc, ctx.db); // fixme117 postfix_snippet("dbgr", "dbg!(&expr)", format!("dbg!(&{receiver_text})")).add_to(acc, ctx.db);118 postfix_snippet("call", "function(expr)", format!("${{1}}({receiver_text})"))119 .add_to(acc, ctx.db);120121 if let Some(expected_ty) = ctx.expected_type.as_ref()122 && let Some(adt) = expected_ty.as_adt()123 {124 let is_valid_new = expected_ty125 .iterate_assoc_items(ctx.db, |item| {126 if let hir::AssocItem::Function(func) = item127 && func.name(ctx.db) == hir::sym::new128 && !func.has_self_param(ctx.db)129 {130 let params = func.params_without_self(ctx.db);131 if params.len() == 1 {132 return Some(());133 }134 }135 None136 })137 .is_some();138139 let adt = hir::ModuleDef::from(adt);140 if is_valid_new && let Some(path) = ctx.module.find_path(ctx.db, adt, cfg) {141 let ty_name = path.display(ctx.db, ctx.display_target.edition).to_smolstr();142143 postfix_snippet(144 "new",145 &format_smolstr!("{}::new(expr)", ty_name),146 format!("{}::new({}$0)", ty_name, receiver_text),147 )148 .add_to(acc, ctx.db);149 }150 }151152 let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty);153 let is_in_cond = is_in_condition(&dot_receiver_including_refs);154 let is_in_value = is_in_value(&dot_receiver_including_refs);155 if let Some(parent) = dot_receiver_including_refs.syntax().parent() {156 let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema);157 match &try_enum {158 Some(try_enum) if is_in_cond => match try_enum {159 TryEnum::Result => {160 postfix_snippet(161 "let",162 "let Ok(_)",163 format!("let Ok({placeholder}) = {receiver_text}"),164 )165 .add_to(acc, ctx.db);166 postfix_snippet(167 "letm",168 "let Ok(mut _)",169 format!("let Ok(mut {placeholder}) = {receiver_text}"),170 )171 .add_to(acc, ctx.db);172 }173 TryEnum::Option => {174 postfix_snippet(175 "let",176 "let Some(_)",177 format!("let Some({placeholder}) = {receiver_text}"),178 )179 .add_to(acc, ctx.db);180 postfix_snippet(181 "letm",182 "let Some(mut _)",183 format!("let Some(mut {placeholder}) = {receiver_text}"),184 )185 .add_to(acc, ctx.db);186 }187 },188 _ if is_in_cond => {189 postfix_snippet("let", "let", format!("let $1 = {receiver_text}"))190 .add_to(acc, ctx.db);191 }192 _ if expr_ctx.in_block_expr => {193 postfix_snippet("let", "let", format!("let $0 = {receiver_text}{semi}"))194 .add_to(acc, ctx.db);195 postfix_snippet("letm", "let mut", format!("let mut $0 = {receiver_text}{semi}"))196 .add_to(acc, ctx.db);197 }198 _ if matches!(parent.kind(), MATCH_ARM | CLOSURE_EXPR) => {199 postfix_snippet(200 "let",201 "let",202 format!("{{\n let $1 = {receiver_text};\n $0\n}}"),203 )204 .add_to(acc, ctx.db);205 postfix_snippet(206 "letm",207 "let mut",208 format!("{{\n let mut $1 = {receiver_text};\n $0\n}}"),209 )210 .add_to(acc, ctx.db);211 }212 _ => (),213 }214 }215216 if !is_in_cond {217 match try_enum {218 Some(try_enum) => match try_enum {219 TryEnum::Result => {220 postfix_snippet(221 "match",222 "match expr {}",223 format!("match {receiver_text} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}"),224 )225 .add_to(acc, ctx.db);226 }227 TryEnum::Option => {228 postfix_snippet(229 "match",230 "match expr {}",231 format!(232 "match {receiver_text} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}"233 ),234 )235 .add_to(acc, ctx.db);236 }237 },238 None => {239 postfix_snippet(240 "match",241 "match expr {}",242 format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),243 )244 .add_to(acc, ctx.db);245 }246 }247 if let Some(try_enum) = &try_enum {248 let placeholder = suggest_receiver_name(dot_receiver, "1", &ctx.sema);249 let if_then_snip =250 if is_in_value { "{\n $2\n} else {\n $0\n}" } else { "{\n $0\n}" };251 match try_enum {252 TryEnum::Result => {253 postfix_snippet(254 "ifl",255 "if let Ok {}",256 format!("if let Ok({placeholder}) = {receiver_text} {if_then_snip}"),257 )258 .add_to(acc, ctx.db);259260 postfix_snippet(261 "lete",262 "let Ok else {}",263 format!("let Ok({placeholder}) = {receiver_text} else {{\n $2\n}};$0"),264 )265 .add_to(acc, ctx.db);266267 postfix_snippet(268 "while",269 "while let Ok {}",270 format!("while let Ok({placeholder}) = {receiver_text} {{\n $0\n}}"),271 )272 .add_to(acc, ctx.db);273 }274 TryEnum::Option => {275 postfix_snippet(276 "ifl",277 "if let Some {}",278 format!("if let Some({placeholder}) = {receiver_text} {if_then_snip}"),279 )280 .add_to(acc, ctx.db);281282 postfix_snippet(283 "lete",284 "let Some else {}",285 format!("let Some({placeholder}) = {receiver_text} else {{\n $2\n}};$0"),286 )287 .add_to(acc, ctx.db);288289 postfix_snippet(290 "while",291 "while let Some {}",292 format!("while let Some({placeholder}) = {receiver_text} {{\n $0\n}}"),293 )294 .add_to(acc, ctx.db);295 }296 }297 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {298 let if_then_snip =299 if is_in_value { "{\n $1\n} else {\n $0\n}" } else { "{\n $0\n}" };300 postfix_snippet("if", "if expr {}", format!("if {receiver_text} {if_then_snip}"))301 .add_to(acc, ctx.db);302 postfix_snippet(303 "while",304 "while expr {}",305 format!("while {receiver_text} {{\n $0\n}}"),306 )307 .add_to(acc, ctx.db);308 } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator()309 && receiver_ty.impls_trait(ctx.db, trait_, &[])310 {311 postfix_snippet(312 "for",313 "for ele in expr {}",314 format!("for ele in {receiver_text} {{\n $0\n}}"),315 )316 .add_to(acc, ctx.db);317 }318 }319320 if receiver_ty.is_bool() || receiver_ty.is_unknown() {321 postfix_snippet("not", "!expr", format!("!{receiver_text}")).add_to(acc, ctx.db);322 }323324 let block_should_be_wrapped = if let ast::Expr::BlockExpr(block) = dot_receiver {325 block.modifier().is_some() || !block.is_standalone()326 } else {327 true328 };329 {330 let (open_brace, close_brace) =331 if block_should_be_wrapped { ("{ ", " }") } else { ("", "") };332 // FIXME: Why add parentheses333 let (open_paren, close_paren) = if is_in_cond { ("(", ")") } else { ("", "") };334 let unsafe_completion_string =335 format!("{open_paren}unsafe {open_brace}{receiver_text}{close_brace}{close_paren}");336 postfix_snippet("unsafe", "unsafe {}", unsafe_completion_string).add_to(acc, ctx.db);337338 let const_completion_string =339 format!("{open_paren}const {open_brace}{receiver_text}{close_brace}{close_paren}");340 postfix_snippet("const", "const {}", const_completion_string).add_to(acc, ctx.db);341 }342343 if let ast::Expr::Literal(literal) = dot_receiver.clone()344 && let Some(literal_text) = ast::String::cast(literal.token())345 {346 add_format_like_completions(acc, ctx, dot_receiver, cap, &literal_text, semi);347 }348349 postfix_snippet("return", "return expr", format!("return {receiver_text}{semi}"))350 .add_to(acc, ctx.db);351352 if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {353 postfix_snippet("break", "break expr", format!("break {receiver_text}{semi}"))354 .add_to(acc, ctx.db);355 }356}357358fn suggest_receiver_name(359 receiver: &ast::Expr,360 n: &str,361 sema: &Semantics<'_, RootDatabase>,362) -> SmolStr {363 let placeholder = |name| format_smolstr!("${{{n}:{name}}}");364365 match receiver {366 ast::Expr::PathExpr(path) => {367 if let Some(name) = path.path().and_then(|it| it.as_single_name_ref()) {368 return placeholder(name.text());369 }370 }371 ast::Expr::RefExpr(it) => {372 if let Some(receiver) = it.expr() {373 return suggest_receiver_name(&receiver, n, sema);374 }375 }376 _ => {}377 }378379 let name = NameGenerator::new_with_names([].into_iter()).try_for_variable(receiver, sema);380 match name {381 Some(name) => placeholder(&name),382 None => format_smolstr!("${n}"),383 }384}385386fn get_receiver_text(387 sema: &Semantics<'_, RootDatabase>,388 receiver: &ast::Expr,389 receiver_is_ambiguous_float_literal: bool,390) -> String {391 // Do not just call `receiver.to_string()`, as that will mess up whitespaces inside macros.392 let Some(mut range) = sema.original_range_opt(receiver.syntax()) else {393 return receiver.to_string();394 };395 if receiver_is_ambiguous_float_literal {396 range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))397 }398 let file_text = sema.db.file_text(range.file_id.file_id(sema.db));399 let text = file_text.text(sema.db);400 let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.end())]);401 let mut text = stdx::dedent_by(indent_spaces, &text[range.range]);402403 // The receiver texts should be interpreted as-is, as they are expected to be404 // normal Rust expressions.405 SnippetEdit::escape_snippet_bits(&mut text);406 return text;407408 fn indent_of_tail_line(text: &str) -> usize {409 let tail_line = text.rsplit_once('\n').map_or(text, |(_, s)| s);410 let trimmed = tail_line.trim_start_matches(' ');411 tail_line.len() - trimmed.len()412 }413}414415fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr {416 receiver417 .syntax()418 .parent()419 .and_then(ast::Expr::cast)420 .filter(|it| {421 matches!(422 it,423 ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_)424 )425 })426 .unwrap_or_else(|| receiver.clone())427}428429/// Given an `initial_element`, tries to expand it to include deref(s), not(s), and then references.430/// Returns the expanded expressions, and the added prefix as a string431///432/// For example, if called with the `42` in `&&mut *42`, would return `(&&mut *42, "&&mut *")`.433fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {434 let mut resulting_element = initial_element.clone();435 let mut prefix = String::new();436437 while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)438 && parent.op_kind() == Some(ast::UnaryOp::Deref)439 {440 resulting_element = ast::Expr::from(parent);441 prefix.insert(0, '*');442 }443444 while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)445 && parent.op_kind() == Some(ast::UnaryOp::Not)446 {447 resulting_element = ast::Expr::from(parent);448 prefix.insert(0, '!');449 }450451 while let Some(parent_ref_element) =452 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)453 {454 let last_child_or_token = parent_ref_element.syntax().last_child_or_token();455 prefix.insert_str(456 0,457 parent_ref_element458 .syntax()459 .children_with_tokens()460 .filter(|it| Some(it) != last_child_or_token.as_ref())461 .flat_map(|it| {462 let has_ws = it.next_sibling_or_token().is_some_and(|it| it.kind().is_trivia());463 let need_ws = !has_ws && it.kind().is_any_identifier();464 itertools::chain([Either::Left(it)], need_ws.then_some(Either::Right(" ")))465 })466 .format("")467 .to_smolstr()468 .as_str(),469 );470 resulting_element = ast::Expr::from(parent_ref_element);471 }472473 (resulting_element, prefix)474}475476fn build_postfix_snippet_builder<'ctx>(477 ctx: &'ctx CompletionContext<'_, '_>,478 cap: SnippetCap,479 receiver: &'ctx ast::Expr,480) -> Option<impl Fn(&str, &str, String) -> Builder + 'ctx> {481 let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;482 if ctx.source_range().end() < receiver_range.start() {483 // This shouldn't happen, yet it does. I assume this might be due to an incorrect token484 // mapping.485 never!();486 return None;487 }488 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());489490 // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that491 // can't be annotated for the closure, hence fix it by constructing it without the Option first492 fn build<'ctx>(493 ctx: &'ctx CompletionContext<'_, '_>,494 cap: SnippetCap,495 delete_range: TextRange,496 ) -> impl Fn(&str, &str, String) -> Builder + 'ctx {497 move |label, detail, snippet| {498 let edit = TextEdit::replace(delete_range, snippet);499 let mut item = CompletionItem::new(500 CompletionItemKind::Snippet,501 ctx.source_range(),502 label,503 ctx.edition,504 );505 item.detail(detail).snippet_edit(cap, edit);506 let postfix_match = if ctx.original_token.text() == label {507 cov_mark::hit!(postfix_exact_match_is_high_priority);508 Some(CompletionRelevancePostfixMatch::Exact)509 } else {510 cov_mark::hit!(postfix_inexact_match_is_low_priority);511 Some(CompletionRelevancePostfixMatch::NonExact)512 };513 let relevance = CompletionRelevance { postfix_match, ..Default::default() };514 item.set_relevance(relevance);515 item516 }517 }518 Some(build(ctx, cap, delete_range))519}520521fn add_custom_postfix_completions(522 acc: &mut Completions,523 ctx: &CompletionContext<'_, '_>,524 postfix_snippet: impl Fn(&str, &str, String) -> Builder,525 receiver_text: &str,526) -> Option<()> {527 ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;528 ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(529 |(trigger, snippet)| {530 let imports = match snippet.imports(ctx) {531 Some(imports) => imports,532 None => return,533 };534 let body = snippet.postfix_snippet(receiver_text);535 let document = Documentation::new_owned(format!("```rust\n{body}\n```"));536 let mut builder =537 postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), body);538 builder.documentation(document);539 for import in imports.into_iter() {540 builder.add_import(import);541 }542 builder.add_to(acc, ctx.db);543 },544 );545 None546}547548pub(crate) fn is_in_condition(it: &ast::Expr) -> bool {549 it.syntax()550 .parent()551 .and_then(|parent| {552 Some(match_ast! { match parent {553 ast::IfExpr(expr) => expr.condition()? == *it,554 ast::WhileExpr(expr) => expr.condition()? == *it,555 ast::MatchGuard(guard) => guard.condition()? == *it,556 ast::BinExpr(bin_expr) => (bin_expr.op_token()?.kind() == T![&&])557 .then(|| is_in_condition(&bin_expr.into()))?,558 ast::Expr(expr) => (expr.syntax().text_range().start() == it.syntax().text_range().start())559 .then(|| is_in_condition(&expr))?,560 _ => return None,561 } })562 })563 .unwrap_or(false)564}565566pub(crate) fn is_in_value(it: &ast::Expr) -> bool {567 let Some(node) = it.syntax().parent() else { return false };568 let kind = node.kind();569 ast::LetStmt::can_cast(kind)570 || ast::ArgList::can_cast(kind)571 || ast::ArrayExpr::can_cast(kind)572 || ast::ParenExpr::can_cast(kind)573 || ast::BreakExpr::can_cast(kind)574 || ast::ReturnExpr::can_cast(kind)575 || ast::PrefixExpr::can_cast(kind)576 || ast::FormatArgsArg::can_cast(kind)577 || ast::RecordExprField::can_cast(kind)578 || ast::BinExpr::cast(node.clone()).is_some_and(|expr| expr.rhs().as_ref() == Some(it))579 || ast::IndexExpr::cast(node).is_some_and(|expr| expr.index().as_ref() == Some(it))580}581582#[cfg(test)]583mod tests {584 use expect_test::expect;585586 use crate::{587 CompletionConfig, Snippet,588 tests::{TEST_CONFIG, check, check_edit, check_edit_with_config},589 };590591 #[test]592 fn postfix_completion_works_for_trivial_path_expression() {593 check(594 r#"595fn main() {596 let bar = true;597 bar.$0598}599"#,600 expect![[r#"601 sn box Box::new(expr)602 sn call function(expr)603 sn const const {}604 sn dbg dbg!(expr)605 sn dbgr dbg!(&expr)606 sn deref *expr607 sn if if expr {}608 sn let let609 sn letm let mut610 sn match match expr {}611 sn not !expr612 sn ref &expr613 sn refm &mut expr614 sn return return expr615 sn unsafe unsafe {}616 sn while while expr {}617 "#]],618 );619 }620621 #[test]622 fn postfix_completion_works_for_function_calln() {623 check(624 r#"625fn foo(elt: bool) -> bool {626 !elt627}628629fn main() {630 let bar = true;631 foo(bar.$0)632}633"#,634 expect![[r#"635 sn box Box::new(expr)636 sn call function(expr)637 sn const const {}638 sn dbg dbg!(expr)639 sn dbgr dbg!(&expr)640 sn deref *expr641 sn if if expr {}642 sn match match expr {}643 sn not !expr644 sn ref &expr645 sn refm &mut expr646 sn return return expr647 sn unsafe unsafe {}648 sn while while expr {}649 "#]],650 );651 }652653 #[test]654 fn postfix_completion_works_in_if_condition() {655 check(656 r#"657fn foo(cond: bool) {658 if cond.$0659}660"#,661 expect![[r#"662 sn box Box::new(expr)663 sn call function(expr)664 sn const const {}665 sn dbg dbg!(expr)666 sn dbgr dbg!(&expr)667 sn deref *expr668 sn let let669 sn not !expr670 sn ref &expr671 sn refm &mut expr672 sn return return expr673 sn unsafe unsafe {}674 "#]],675 );676 }677678 #[test]679 fn postfix_type_filtering() {680 check(681 r#"682fn main() {683 let bar: u8 = 12;684 bar.$0685}686"#,687 expect![[r#"688 sn box Box::new(expr)689 sn call function(expr)690 sn const const {}691 sn dbg dbg!(expr)692 sn dbgr dbg!(&expr)693 sn deref *expr694 sn let let695 sn letm let mut696 sn match match expr {}697 sn ref &expr698 sn refm &mut expr699 sn return return expr700 sn unsafe unsafe {}701 "#]],702 )703 }704705 #[test]706 fn let_middle_block() {707 check_edit(708 "let",709 r#"710fn main() {711 baz.l$0712 res713}714"#,715 r#"716fn main() {717 let $0 = baz;718 res719}720"#,721 );722723 check(724 r#"725fn main() {726 baz.l$0727 res728}729"#,730 expect![[r#"731 sn box Box::new(expr)732 sn call function(expr)733 sn const const {}734 sn dbg dbg!(expr)735 sn dbgr dbg!(&expr)736 sn deref *expr737 sn if if expr {}738 sn let let739 sn letm let mut740 sn match match expr {}741 sn not !expr742 sn ref &expr743 sn refm &mut expr744 sn return return expr745 sn unsafe unsafe {}746 sn while while expr {}747 "#]],748 );749 check(750 r#"751fn main() {752 &baz.l$0753 res754}755"#,756 expect![[r#"757 sn box Box::new(expr)758 sn call function(expr)759 sn const const {}760 sn dbg dbg!(expr)761 sn dbgr dbg!(&expr)762 sn deref *expr763 sn if if expr {}764 sn let let765 sn letm let mut766 sn match match expr {}767 sn not !expr768 sn ref &expr769 sn refm &mut expr770 sn return return expr771 sn unsafe unsafe {}772 sn while while expr {}773 "#]],774 );775776 check_edit(777 "let",778 r#"779fn foo() -> (i32, i32) {780 "a".let$0781 (2, 3).clone()782}783"#,784 r#"785fn foo() -> (i32, i32) {786 let $0 = "a";787 (2, 3).clone()788}789"#,790 );791792 check_edit(793 "let",794 r#"795fn foo() -> (i32, i32) {796 "a".l$0797 (2, 3).clone()798}799"#,800 r#"801fn foo() -> (i32, i32) {802 let $0 = "a";803 (2, 3).clone()804}805"#,806 );807 }808809 #[test]810 fn let_tail_block() {811 check_edit(812 "let",813 r#"814fn main() {815 baz.l$0816}817"#,818 r#"819fn main() {820 let $0 = baz;821}822"#,823 );824825 check(826 r#"827fn main() {828 baz.l$0829}830"#,831 expect![[r#"832 sn box Box::new(expr)833 sn call function(expr)834 sn const const {}835 sn dbg dbg!(expr)836 sn dbgr dbg!(&expr)837 sn deref *expr838 sn if if expr {}839 sn let let840 sn letm let mut841 sn match match expr {}842 sn not !expr843 sn ref &expr844 sn refm &mut expr845 sn return return expr846 sn unsafe unsafe {}847 sn while while expr {}848 "#]],849 );850851 check(852 r#"853fn main() {854 &baz.l$0855}856"#,857 expect![[r#"858 sn box Box::new(expr)859 sn call function(expr)860 sn const const {}861 sn dbg dbg!(expr)862 sn dbgr dbg!(&expr)863 sn deref *expr864 sn if if expr {}865 sn let let866 sn letm let mut867 sn match match expr {}868 sn not !expr869 sn ref &expr870 sn refm &mut expr871 sn return return expr872 sn unsafe unsafe {}873 sn while while expr {}874 "#]],875 );876 }877878 #[test]879 fn let_before_semicolon() {880 check_edit(881 "let",882 r#"883fn main() {884 baz.l$0;885}886"#,887 r#"888fn main() {889 let $0 = baz;890}891"#,892 );893 }894895 #[test]896 fn option_iflet() {897 check_edit(898 "ifl",899 r#"900//- minicore: option901fn main() {902 let bar = Some(true);903 bar.$0904}905"#,906 r#"907fn main() {908 let bar = Some(true);909 if let Some(${1:bar}) = bar {910 $0911}912}913"#,914 );915 }916917 #[test]918 fn option_iflet_cond() {919 check(920 r#"921//- minicore: option922fn main() {923 let bar = Some(true);924 if bar.$0925}926"#,927 expect![[r#"928 me and(…) fn(self, Option<U>) -> Option<U>929 me as_ref() const fn(&self) -> Option<&T>930 me ok_or(…) const fn(self, E) -> Result<T, E>931 me unwrap() const fn(self) -> T932 me unwrap_or(…) fn(self, T) -> T933 sn box Box::new(expr)934 sn call function(expr)935 sn const const {}936 sn dbg dbg!(expr)937 sn dbgr dbg!(&expr)938 sn deref *expr939 sn let let Some(_)940 sn letm let Some(mut _)941 sn ref &expr942 sn refm &mut expr943 sn return return expr944 sn unsafe unsafe {}945 "#]],946 );947 check_edit(948 "let",949 r#"950//- minicore: option951fn main() {952 let bar = Some(true);953 if bar.$0954}955"#,956 r#"957fn main() {958 let bar = Some(true);959 if let Some(${0:bar}) = bar960}961"#,962 );963 check_edit(964 "let",965 r#"966//- minicore: option967fn main() {968 let bar = Some(true);969 if true && bar.$0970}971"#,972 r#"973fn main() {974 let bar = Some(true);975 if true && let Some(${0:bar}) = bar976}977"#,978 );979 check_edit(980 "let",981 r#"982//- minicore: option983fn main() {984 let bar = Some(true);985 if true && true && bar.$0986}987"#,988 r#"989fn main() {990 let bar = Some(true);991 if true && true && let Some(${0:bar}) = bar992}993"#,994 );995 }996997 #[test]998 fn iflet_fallback_cond() {999 check_edit(1000 "let",1001 r#"1002fn main() {1003 let bar = 2;1004 if bar.$01005}1006"#,1007 r#"1008fn main() {1009 let bar = 2;1010 if let $1 = bar1011}1012"#,1013 );1014 }10151016 #[test]1017 fn match_arm_let_block() {1018 check(1019 r#"1020fn main() {1021 match 2 {1022 bar => bar.$01023 }1024}1025"#,1026 expect![[r#"1027 sn box Box::new(expr)1028 sn call function(expr)1029 sn const const {}1030 sn dbg dbg!(expr)1031 sn dbgr dbg!(&expr)1032 sn deref *expr1033 sn let let1034 sn letm let mut1035 sn match match expr {}1036 sn ref &expr1037 sn refm &mut expr1038 sn return return expr1039 sn unsafe unsafe {}1040 "#]],1041 );1042 check(1043 r#"1044fn main() {1045 match 2 {1046 bar => &bar.l$01047 }1048}1049"#,1050 expect![[r#"1051 sn box Box::new(expr)1052 sn call function(expr)1053 sn const const {}1054 sn dbg dbg!(expr)1055 sn dbgr dbg!(&expr)1056 sn deref *expr1057 sn let let1058 sn letm let mut1059 sn match match expr {}1060 sn ref &expr1061 sn refm &mut expr1062 sn return return expr1063 sn unsafe unsafe {}1064 "#]],1065 );1066 check_edit(1067 "let",1068 r#"1069fn main() {1070 match 2 {1071 bar => bar.$01072 }1073}1074"#,1075 r#"1076fn main() {1077 match 2 {1078 bar => {1079 let $1 = bar;1080 $01081}1082 }1083}1084"#,1085 );1086 }10871088 #[test]1089 fn closure_let_block() {1090 check_edit(1091 "let",1092 r#"1093fn main() {1094 let bar = 2;1095 let f = || bar.$0;1096}1097"#,1098 r#"1099fn main() {1100 let bar = 2;1101 let f = || {1102 let $1 = bar;1103 $01104};1105}1106"#,1107 );1108 }11091110 #[test]1111 fn option_letelse() {1112 check_edit(1113 "lete",1114 r#"1115//- minicore: option1116fn main() {1117 let bar = Some(true);1118 bar.$01119}1120"#,1121 r#"1122fn main() {1123 let bar = Some(true);1124 let Some(${1:bar}) = bar else {1125 $21126};$01127}1128"#,1129 );11301131 check_edit(1132 "lete",1133 r#"1134//- minicore: option1135fn main() {1136 let bar = Some(true);1137 bar.$01138 other();1139}1140"#,1141 r#"1142fn main() {1143 let bar = Some(true);1144 let Some(${1:bar}) = bar else {1145 $21146};$01147 other();1148}1149"#,1150 );1151 }11521153 #[test]1154 fn result_match() {1155 check_edit(1156 "match",1157 r#"1158//- minicore: result1159fn main() {1160 let bar = Ok(true);1161 bar.$01162}1163"#,1164 r#"1165fn main() {1166 let bar = Ok(true);1167 match bar {1168 Ok(${1:_}) => {$2},1169 Err(${3:_}) => {$0},1170}1171}1172"#,1173 );1174 }11751176 #[test]1177 fn postfix_completion_works_for_ambiguous_float_literal() {1178 check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)1179 }11801181 #[test]1182 fn works_in_simple_macro() {1183 check_edit(1184 "dbg",1185 r#"1186macro_rules! m { ($e:expr) => { $e } }1187fn main() {1188 let bar: u8 = 12;1189 m!(bar.d$0)1190}1191"#,1192 r#"1193macro_rules! m { ($e:expr) => { $e } }1194fn main() {1195 let bar: u8 = 12;1196 m!(dbg!(bar))1197}1198"#,1199 );1200 }12011202 #[test]1203 fn postfix_completion_for_references() {1204 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);1205 check_edit("dbg", r#"fn main() { &&*"hello".$0 }"#, r#"fn main() { dbg!(&&*"hello") }"#);1206 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);1207 check_edit(1208 "ifl",1209 r#"1210//- minicore: option1211fn main() {1212 let bar = &Some(true);1213 bar.$01214}1215"#,1216 r#"1217fn main() {1218 let bar = &Some(true);1219 if let Some(${1:bar}) = bar {1220 $01221}1222}1223"#,1224 )1225 }12261227 #[test]1228 fn postfix_completion_for_nots() {1229 check_edit(1230 "if",1231 r#"1232fn main() {1233 let is_foo = true;1234 !is_foo.$01235}1236"#,1237 r#"1238fn main() {1239 let is_foo = true;1240 if !is_foo {1241 $01242}1243}1244"#,1245 )1246 }12471248 #[test]1249 fn postfix_completion_if_else_in_value() {1250 check_edit(1251 "if",1252 r#"1253fn main() {1254 let s = cond.is_some().$0;1255}1256"#,1257 r#"1258fn main() {1259 let s = if cond.is_some() {1260 $11261} else {1262 $01263};1264}1265"#,1266 );12671268 check_edit(1269 "ifl",1270 r#"1271//- minicore: option1272fn main() {1273 let cond = Some("x");1274 let s = cond.$0;1275}1276"#,1277 r#"1278fn main() {1279 let cond = Some("x");1280 let s = if let Some(${1:cond}) = cond {1281 $21282} else {1283 $01284};1285}1286"#,1287 );12881289 check_edit(1290 "if",1291 r#"1292fn main() {1293 2 + true.$0;1294}1295"#,1296 r#"1297fn main() {1298 2 + if true {1299 $11300} else {1301 $01302};1303}1304"#,1305 );1306 }13071308 #[test]1309 fn postfix_completion_for_unsafe() {1310 postfix_completion_for_block("unsafe");1311 }13121313 #[test]1314 fn postfix_completion_for_const() {1315 postfix_completion_for_block("const");1316 }13171318 fn postfix_completion_for_block(kind: &str) {1319 check_edit(kind, r#"fn main() { foo.$0 }"#, &format!("fn main() {{ {kind} {{ foo }} }}"));1320 check_edit(1321 kind,1322 r#"fn main() { { foo }.$0 }"#,1323 &format!("fn main() {{ {kind} {{ foo }} }}"),1324 );1325 check_edit(1326 kind,1327 r#"fn main() { if x { foo }.$0 }"#,1328 &format!("fn main() {{ {kind} {{ if x {{ foo }} }} }}"),1329 );1330 check_edit(1331 kind,1332 r#"fn main() { loop { foo }.$0 }"#,1333 &format!("fn main() {{ {kind} {{ loop {{ foo }} }} }}"),1334 );1335 check_edit(1336 kind,1337 r#"fn main() { if true {}.$0 }"#,1338 &format!("fn main() {{ {kind} {{ if true {{}} }} }}"),1339 );1340 check_edit(1341 kind,1342 r#"fn main() { while true {}.$0 }"#,1343 &format!("fn main() {{ {kind} {{ while true {{}} }} }}"),1344 );1345 check_edit(1346 kind,1347 r#"1348//- minicore: iterator1349fn main() { for i in 0..10 {}.$0 }"#,1350 &format!("fn main() {{ {kind} {{ for i in 0..10 {{}} }} }}"),1351 );1352 check_edit(1353 kind,1354 r#"fn main() { let x = if true {1} else {2}.$0 }"#,1355 &format!("fn main() {{ let x = {kind} {{ if true {{1}} else {{2}} }} }}"),1356 );13571358 if kind == "const" {1359 check_edit(1360 kind,1361 r#"fn main() { unsafe {1}.$0 }"#,1362 &format!("fn main() {{ {kind} {{ unsafe {{1}} }} }}"),1363 );1364 } else {1365 check_edit(1366 kind,1367 r#"fn main() { const {1}.$0 }"#,1368 &format!("fn main() {{ {kind} {{ const {{1}} }} }}"),1369 );1370 }13711372 // completion will not be triggered1373 check_edit(1374 kind,1375 r#"fn main() { let x = true else {panic!()}.$0}"#,1376 &format!("fn main() {{ let x = true else {{panic!()}}.{kind} $0}}"),1377 );1378 }13791380 #[test]1381 fn custom_postfix_completion() {1382 let config = CompletionConfig {1383 snippets: vec![1384 Snippet::new(1385 &[],1386 &["break".into()],1387 &["ControlFlow::Break(${receiver})".into()],1388 "",1389 &["core::ops::ControlFlow".into()],1390 crate::SnippetScope::Expr,1391 )1392 .unwrap(),1393 ],1394 ..TEST_CONFIG1395 };13961397 check_edit_with_config(1398 config.clone(),1399 "break",1400 r#"1401//- minicore: try1402fn main() { 42.$0 }1403"#,1404 r#"1405use core::ops::ControlFlow;14061407fn main() { ControlFlow::Break(42) }1408"#,1409 );14101411 // The receiver texts should be escaped, see comments in `get_receiver_text()`1412 // for detail.1413 //1414 // Note that the last argument is what *lsp clients would see* rather than1415 // what users would see. Unescaping happens thereafter.1416 check_edit_with_config(1417 config.clone(),1418 "break",1419 r#"1420//- minicore: try1421fn main() { '\\'.$0 }1422"#,1423 r#"1424use core::ops::ControlFlow;14251426fn main() { ControlFlow::Break('\\\\') }1427"#,1428 );14291430 check_edit_with_config(1431 config,1432 "break",1433 r#"1434//- minicore: try1435fn main() {1436 match true {1437 true => "${1:placeholder}",1438 false => "\$",1439 }.$01440}1441"#,1442 r#"1443use core::ops::ControlFlow;14441445fn main() {1446 ControlFlow::Break(match true {1447 true => "\${1:placeholder}",1448 false => "\\\$",1449})1450}1451"#,1452 );1453 }14541455 #[test]1456 fn postfix_completion_for_format_like_strings() {1457 check_edit(1458 "format",1459 r#"fn main() { "{some_var:?}".$0 }"#,1460 r#"fn main() { format!("{some_var:?}") }"#,1461 );1462 check_edit(1463 "panic",1464 r#"fn main() { "Panic with {a}".$0 }"#,1465 r#"fn main() { panic!("Panic with {a}"); }"#,1466 );1467 check_edit(1468 "println",1469 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,1470 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }); }"#,1471 );1472 check_edit(1473 "loge",1474 r#"fn main() { "{2+2}".$0 }"#,1475 r#"fn main() { log::error!("{}", 2+2); }"#,1476 );1477 check_edit(1478 "logt",1479 r#"fn main() { "{2+2}".$0 }"#,1480 r#"fn main() { log::trace!("{}", 2+2); }"#,1481 );1482 check_edit(1483 "logd",1484 r#"fn main() { "{2+2}".$0 }"#,1485 r#"fn main() { log::debug!("{}", 2+2); }"#,1486 );1487 check_edit(1488 "logi",1489 r#"fn main() { "{2+2}".$0 }"#,1490 r#"fn main() { log::info!("{}", 2+2); }"#,1491 );1492 check_edit(1493 "logw",1494 r#"fn main() { "{2+2}".$0 }"#,1495 r#"fn main() { log::warn!("{}", 2+2); }"#,1496 );1497 check_edit(1498 "loge",1499 r#"fn main() { "{2+2}".$0 }"#,1500 r#"fn main() { log::error!("{}", 2+2); }"#,1501 );1502 }15031504 #[test]1505 fn postfix_custom_snippets_completion_for_references() {1506 // https://github.com/rust-lang/rust-analyzer/issues/792915071508 let snippet = Snippet::new(1509 &[],1510 &["ok".into()],1511 &["Ok(${receiver})".into()],1512 "",1513 &[],1514 crate::SnippetScope::Expr,1515 )1516 .unwrap();15171518 check_edit_with_config(1519 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1520 "ok",1521 r#"fn main() { &&42.o$0 }"#,1522 r#"fn main() { Ok(&&42) }"#,1523 );15241525 check_edit_with_config(1526 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1527 "ok",1528 r#"fn main() { &&42.$0 }"#,1529 r#"fn main() { Ok(&&42) }"#,1530 );15311532 check_edit_with_config(1533 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1534 "ok",1535 r#"fn main() { &raw mut 42.$0 }"#,1536 r#"fn main() { Ok(&raw mut 42) }"#,1537 );15381539 check_edit_with_config(1540 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1541 "ok",1542 r#"fn main() { &raw const 42.$0 }"#,1543 r#"fn main() { Ok(&raw const 42) }"#,1544 );15451546 check_edit_with_config(1547 CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },1548 "ok",1549 r#"1550struct A {1551 a: i32,1552}15531554fn main() {1555 let a = A {a :1};1556 &a.a.$01557}1558 "#,1559 r#"1560struct A {1561 a: i32,1562}15631564fn main() {1565 let a = A {a :1};1566 Ok(&a.a)1567}1568 "#,1569 );1570 }15711572 #[test]1573 fn postfix_custom_snippets_completion_for_reference_expr() {1574 // https://github.com/rust-lang/rust-analyzer/issues/210351575 let snippet = Snippet::new(1576 &[],1577 &["group".into()],1578 &["(${receiver})".into()],1579 "",1580 &[],1581 crate::SnippetScope::Expr,1582 )1583 .unwrap();15841585 check_edit_with_config(1586 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1587 "group",1588 r#"fn main() { &[1, 2, 3].g$0 }"#,1589 r#"fn main() { (&[1, 2, 3]) }"#,1590 );15911592 check_edit_with_config(1593 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1594 "group",1595 r#"fn main() { &&foo(a, b, 1+1).$0 }"#,1596 r#"fn main() { (&&foo(a, b, 1+1)) }"#,1597 );15981599 check_edit_with_config(1600 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1601 "group",1602 r#"fn main() { &mut Foo { a: 1, b: 2, c: 3 }.$0 }"#,1603 r#"fn main() { (&mut Foo { a: 1, b: 2, c: 3 }) }"#,1604 );16051606 check_edit_with_config(1607 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1608 "group",1609 r#"fn main() { &raw mut Foo::new().$0 }"#,1610 r#"fn main() { (&raw mut Foo::new()) }"#,1611 );16121613 check_edit_with_config(1614 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1615 "group",1616 r#"fn main() { &raw const Foo::bar::SOME_CONST.$0 }"#,1617 r#"fn main() { (&raw const Foo::bar::SOME_CONST) }"#,1618 );16191620 check_edit_with_config(1621 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1622 "group",1623 r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }1624fn main() { id!(&raw const Foo::bar::SOME_CONST.$0) }"#,1625 r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }1626fn main() { id!((&raw const Foo::bar::SOME_CONST)) }"#,1627 );1628 }16291630 #[test]1631 fn no_postfix_completions_in_if_block_that_has_an_else() {1632 check(1633 r#"1634fn test() {1635 if true {}.$0 else {}1636}1637"#,1638 expect![[r#""#]],1639 );1640 }16411642 #[test]1643 fn mut_ref_consuming() {1644 check_edit(1645 "call",1646 r#"1647fn main() {1648 let mut x = &mut 2;1649 &mut x.$0;1650}1651"#,1652 r#"1653fn main() {1654 let mut x = &mut 2;1655 ${1}(&mut x);1656}1657"#,1658 );1659 }16601661 #[test]1662 fn deref_consuming() {1663 check_edit(1664 "call",1665 r#"1666fn main() {1667 let mut x = &mut 2;1668 &mut *x.$0;1669}1670"#,1671 r#"1672fn main() {1673 let mut x = &mut 2;1674 ${1}(&mut *x);1675}1676"#,1677 );1678 }16791680 #[test]1681 fn inside_macro() {1682 check_edit(1683 "box",1684 r#"1685macro_rules! assert {1686 ( $it:expr $(,)? ) => { $it };1687}16881689fn foo() {1690 let a = true;1691 assert!(if a == false { true } else { false }.$0);1692}1693 "#,1694 r#"1695macro_rules! assert {1696 ( $it:expr $(,)? ) => { $it };1697}16981699fn foo() {1700 let a = true;1701 assert!(Box::new(if a == false { true } else { false }));1702}1703 "#,1704 );1705 }17061707 #[test]1708 fn snippet_dedent() {1709 check_edit(1710 "let",1711 r#"1712//- minicore: option1713fn foo(x: Option<i32>, y: Option<i32>) {1714 let _f = || {1715 x1716 .and(y)1717 .map(|it| {1718 it+21719 })1720 .$01721 };1722}1723"#,1724 r#"1725fn foo(x: Option<i32>, y: Option<i32>) {1726 let _f = || {1727 let $0 = x1728.and(y)1729.map(|it| {1730 it+21731});1732 };1733}1734"#,1735 );1736 }17371738 #[test]1739 fn postfix_new() {1740 check_edit(1741 "new",1742 r#"1743struct OtherThing;1744struct RefCell<T>(T);1745impl<T> RefCell<T> {1746 fn new(t: T) -> Self { RefCell(t) }1747}17481749fn main() {1750 let other_thing = OtherThing;1751 let thing: RefCell<OtherThing> = other_thing.$0;1752}1753"#,1754 r#"1755struct OtherThing;1756struct RefCell<T>(T);1757impl<T> RefCell<T> {1758 fn new(t: T) -> Self { RefCell(t) }1759}17601761fn main() {1762 let other_thing = OtherThing;1763 let thing: RefCell<OtherThing> = RefCell::new(other_thing$0);1764}1765"#,1766 );17671768 check_edit(1769 "new",1770 r#"1771mod foo {1772 pub struct OtherThing;1773 pub struct RefCell<T>(T);1774 impl<T> RefCell<T> {1775 pub fn new(t: T) -> Self { RefCell(t) }1776 }1777}17781779fn main() {1780 let thing: foo::RefCell<foo::OtherThing> = foo::OtherThing.$0;1781}1782"#,1783 r#"1784mod foo {1785 pub struct OtherThing;1786 pub struct RefCell<T>(T);1787 impl<T> RefCell<T> {1788 pub fn new(t: T) -> Self { RefCell(t) }1789 }1790}17911792fn main() {1793 let thing: foo::RefCell<foo::OtherThing> = foo::RefCell::new(foo::OtherThing$0);1794}1795"#,1796 );1797 }1798}