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, EXPR_STMT, MATCH_ARM, STMT_LIST},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 matches!(parent.kind(), STMT_LIST | EXPR_STMT) => {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 );775 }776777 #[test]778 fn let_tail_block() {779 check_edit(780 "let",781 r#"782fn main() {783 baz.l$0784}785"#,786 r#"787fn main() {788 let $0 = baz;789}790"#,791 );792793 check(794 r#"795fn main() {796 baz.l$0797}798"#,799 expect![[r#"800 sn box Box::new(expr)801 sn call function(expr)802 sn const const {}803 sn dbg dbg!(expr)804 sn dbgr dbg!(&expr)805 sn deref *expr806 sn if if expr {}807 sn let let808 sn letm let mut809 sn match match expr {}810 sn not !expr811 sn ref &expr812 sn refm &mut expr813 sn return return expr814 sn unsafe unsafe {}815 sn while while expr {}816 "#]],817 );818819 check(820 r#"821fn main() {822 &baz.l$0823}824"#,825 expect![[r#"826 sn box Box::new(expr)827 sn call function(expr)828 sn const const {}829 sn dbg dbg!(expr)830 sn dbgr dbg!(&expr)831 sn deref *expr832 sn if if expr {}833 sn let let834 sn letm let mut835 sn match match expr {}836 sn not !expr837 sn ref &expr838 sn refm &mut expr839 sn return return expr840 sn unsafe unsafe {}841 sn while while expr {}842 "#]],843 );844 }845846 #[test]847 fn let_before_semicolon() {848 check_edit(849 "let",850 r#"851fn main() {852 baz.l$0;853}854"#,855 r#"856fn main() {857 let $0 = baz;858}859"#,860 );861 }862863 #[test]864 fn option_iflet() {865 check_edit(866 "ifl",867 r#"868//- minicore: option869fn main() {870 let bar = Some(true);871 bar.$0872}873"#,874 r#"875fn main() {876 let bar = Some(true);877 if let Some(${1:bar}) = bar {878 $0879}880}881"#,882 );883 }884885 #[test]886 fn option_iflet_cond() {887 check(888 r#"889//- minicore: option890fn main() {891 let bar = Some(true);892 if bar.$0893}894"#,895 expect![[r#"896 me and(…) fn(self, Option<U>) -> Option<U>897 me as_ref() const fn(&self) -> Option<&T>898 me ok_or(…) const fn(self, E) -> Result<T, E>899 me unwrap() const fn(self) -> T900 me unwrap_or(…) fn(self, T) -> T901 sn box Box::new(expr)902 sn call function(expr)903 sn const const {}904 sn dbg dbg!(expr)905 sn dbgr dbg!(&expr)906 sn deref *expr907 sn let let Some(_)908 sn letm let Some(mut _)909 sn ref &expr910 sn refm &mut expr911 sn return return expr912 sn unsafe unsafe {}913 "#]],914 );915 check_edit(916 "let",917 r#"918//- minicore: option919fn main() {920 let bar = Some(true);921 if bar.$0922}923"#,924 r#"925fn main() {926 let bar = Some(true);927 if let Some(${0:bar}) = bar928}929"#,930 );931 check_edit(932 "let",933 r#"934//- minicore: option935fn main() {936 let bar = Some(true);937 if true && bar.$0938}939"#,940 r#"941fn main() {942 let bar = Some(true);943 if true && let Some(${0:bar}) = bar944}945"#,946 );947 check_edit(948 "let",949 r#"950//- minicore: option951fn main() {952 let bar = Some(true);953 if true && true && bar.$0954}955"#,956 r#"957fn main() {958 let bar = Some(true);959 if true && true && let Some(${0:bar}) = bar960}961"#,962 );963 }964965 #[test]966 fn iflet_fallback_cond() {967 check_edit(968 "let",969 r#"970fn main() {971 let bar = 2;972 if bar.$0973}974"#,975 r#"976fn main() {977 let bar = 2;978 if let $1 = bar979}980"#,981 );982 }983984 #[test]985 fn match_arm_let_block() {986 check(987 r#"988fn main() {989 match 2 {990 bar => bar.$0991 }992}993"#,994 expect![[r#"995 sn box Box::new(expr)996 sn call function(expr)997 sn const const {}998 sn dbg dbg!(expr)999 sn dbgr dbg!(&expr)1000 sn deref *expr1001 sn let let1002 sn letm let mut1003 sn match match expr {}1004 sn ref &expr1005 sn refm &mut expr1006 sn return return expr1007 sn unsafe unsafe {}1008 "#]],1009 );1010 check(1011 r#"1012fn main() {1013 match 2 {1014 bar => &bar.l$01015 }1016}1017"#,1018 expect![[r#"1019 sn box Box::new(expr)1020 sn call function(expr)1021 sn const const {}1022 sn dbg dbg!(expr)1023 sn dbgr dbg!(&expr)1024 sn deref *expr1025 sn let let1026 sn letm let mut1027 sn match match expr {}1028 sn ref &expr1029 sn refm &mut expr1030 sn return return expr1031 sn unsafe unsafe {}1032 "#]],1033 );1034 check_edit(1035 "let",1036 r#"1037fn main() {1038 match 2 {1039 bar => bar.$01040 }1041}1042"#,1043 r#"1044fn main() {1045 match 2 {1046 bar => {1047 let $1 = bar;1048 $01049}1050 }1051}1052"#,1053 );1054 }10551056 #[test]1057 fn closure_let_block() {1058 check_edit(1059 "let",1060 r#"1061fn main() {1062 let bar = 2;1063 let f = || bar.$0;1064}1065"#,1066 r#"1067fn main() {1068 let bar = 2;1069 let f = || {1070 let $1 = bar;1071 $01072};1073}1074"#,1075 );1076 }10771078 #[test]1079 fn option_letelse() {1080 check_edit(1081 "lete",1082 r#"1083//- minicore: option1084fn main() {1085 let bar = Some(true);1086 bar.$01087}1088"#,1089 r#"1090fn main() {1091 let bar = Some(true);1092 let Some(${1:bar}) = bar else {1093 $21094};$01095}1096"#,1097 );10981099 check_edit(1100 "lete",1101 r#"1102//- minicore: option1103fn main() {1104 let bar = Some(true);1105 bar.$01106 other();1107}1108"#,1109 r#"1110fn main() {1111 let bar = Some(true);1112 let Some(${1:bar}) = bar else {1113 $21114};$01115 other();1116}1117"#,1118 );1119 }11201121 #[test]1122 fn result_match() {1123 check_edit(1124 "match",1125 r#"1126//- minicore: result1127fn main() {1128 let bar = Ok(true);1129 bar.$01130}1131"#,1132 r#"1133fn main() {1134 let bar = Ok(true);1135 match bar {1136 Ok(${1:_}) => {$2},1137 Err(${3:_}) => {$0},1138}1139}1140"#,1141 );1142 }11431144 #[test]1145 fn postfix_completion_works_for_ambiguous_float_literal() {1146 check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)1147 }11481149 #[test]1150 fn works_in_simple_macro() {1151 check_edit(1152 "dbg",1153 r#"1154macro_rules! m { ($e:expr) => { $e } }1155fn main() {1156 let bar: u8 = 12;1157 m!(bar.d$0)1158}1159"#,1160 r#"1161macro_rules! m { ($e:expr) => { $e } }1162fn main() {1163 let bar: u8 = 12;1164 m!(dbg!(bar))1165}1166"#,1167 );1168 }11691170 #[test]1171 fn postfix_completion_for_references() {1172 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);1173 check_edit("dbg", r#"fn main() { &&*"hello".$0 }"#, r#"fn main() { dbg!(&&*"hello") }"#);1174 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);1175 check_edit(1176 "ifl",1177 r#"1178//- minicore: option1179fn main() {1180 let bar = &Some(true);1181 bar.$01182}1183"#,1184 r#"1185fn main() {1186 let bar = &Some(true);1187 if let Some(${1:bar}) = bar {1188 $01189}1190}1191"#,1192 )1193 }11941195 #[test]1196 fn postfix_completion_for_nots() {1197 check_edit(1198 "if",1199 r#"1200fn main() {1201 let is_foo = true;1202 !is_foo.$01203}1204"#,1205 r#"1206fn main() {1207 let is_foo = true;1208 if !is_foo {1209 $01210}1211}1212"#,1213 )1214 }12151216 #[test]1217 fn postfix_completion_if_else_in_value() {1218 check_edit(1219 "if",1220 r#"1221fn main() {1222 let s = cond.is_some().$0;1223}1224"#,1225 r#"1226fn main() {1227 let s = if cond.is_some() {1228 $11229} else {1230 $01231};1232}1233"#,1234 );12351236 check_edit(1237 "ifl",1238 r#"1239//- minicore: option1240fn main() {1241 let cond = Some("x");1242 let s = cond.$0;1243}1244"#,1245 r#"1246fn main() {1247 let cond = Some("x");1248 let s = if let Some(${1:cond}) = cond {1249 $21250} else {1251 $01252};1253}1254"#,1255 );12561257 check_edit(1258 "if",1259 r#"1260fn main() {1261 2 + true.$0;1262}1263"#,1264 r#"1265fn main() {1266 2 + if true {1267 $11268} else {1269 $01270};1271}1272"#,1273 );1274 }12751276 #[test]1277 fn postfix_completion_for_unsafe() {1278 postfix_completion_for_block("unsafe");1279 }12801281 #[test]1282 fn postfix_completion_for_const() {1283 postfix_completion_for_block("const");1284 }12851286 fn postfix_completion_for_block(kind: &str) {1287 check_edit(kind, r#"fn main() { foo.$0 }"#, &format!("fn main() {{ {kind} {{ foo }} }}"));1288 check_edit(1289 kind,1290 r#"fn main() { { foo }.$0 }"#,1291 &format!("fn main() {{ {kind} {{ foo }} }}"),1292 );1293 check_edit(1294 kind,1295 r#"fn main() { if x { foo }.$0 }"#,1296 &format!("fn main() {{ {kind} {{ if x {{ foo }} }} }}"),1297 );1298 check_edit(1299 kind,1300 r#"fn main() { loop { foo }.$0 }"#,1301 &format!("fn main() {{ {kind} {{ loop {{ foo }} }} }}"),1302 );1303 check_edit(1304 kind,1305 r#"fn main() { if true {}.$0 }"#,1306 &format!("fn main() {{ {kind} {{ if true {{}} }} }}"),1307 );1308 check_edit(1309 kind,1310 r#"fn main() { while true {}.$0 }"#,1311 &format!("fn main() {{ {kind} {{ while true {{}} }} }}"),1312 );1313 check_edit(1314 kind,1315 r#"1316//- minicore: iterator1317fn main() { for i in 0..10 {}.$0 }"#,1318 &format!("fn main() {{ {kind} {{ for i in 0..10 {{}} }} }}"),1319 );1320 check_edit(1321 kind,1322 r#"fn main() { let x = if true {1} else {2}.$0 }"#,1323 &format!("fn main() {{ let x = {kind} {{ if true {{1}} else {{2}} }} }}"),1324 );13251326 if kind == "const" {1327 check_edit(1328 kind,1329 r#"fn main() { unsafe {1}.$0 }"#,1330 &format!("fn main() {{ {kind} {{ unsafe {{1}} }} }}"),1331 );1332 } else {1333 check_edit(1334 kind,1335 r#"fn main() { const {1}.$0 }"#,1336 &format!("fn main() {{ {kind} {{ const {{1}} }} }}"),1337 );1338 }13391340 // completion will not be triggered1341 check_edit(1342 kind,1343 r#"fn main() { let x = true else {panic!()}.$0}"#,1344 &format!("fn main() {{ let x = true else {{panic!()}}.{kind} $0}}"),1345 );1346 }13471348 #[test]1349 fn custom_postfix_completion() {1350 let config = CompletionConfig {1351 snippets: vec![1352 Snippet::new(1353 &[],1354 &["break".into()],1355 &["ControlFlow::Break(${receiver})".into()],1356 "",1357 &["core::ops::ControlFlow".into()],1358 crate::SnippetScope::Expr,1359 )1360 .unwrap(),1361 ],1362 ..TEST_CONFIG1363 };13641365 check_edit_with_config(1366 config.clone(),1367 "break",1368 r#"1369//- minicore: try1370fn main() { 42.$0 }1371"#,1372 r#"1373use core::ops::ControlFlow;13741375fn main() { ControlFlow::Break(42) }1376"#,1377 );13781379 // The receiver texts should be escaped, see comments in `get_receiver_text()`1380 // for detail.1381 //1382 // Note that the last argument is what *lsp clients would see* rather than1383 // what users would see. Unescaping happens thereafter.1384 check_edit_with_config(1385 config.clone(),1386 "break",1387 r#"1388//- minicore: try1389fn main() { '\\'.$0 }1390"#,1391 r#"1392use core::ops::ControlFlow;13931394fn main() { ControlFlow::Break('\\\\') }1395"#,1396 );13971398 check_edit_with_config(1399 config,1400 "break",1401 r#"1402//- minicore: try1403fn main() {1404 match true {1405 true => "${1:placeholder}",1406 false => "\$",1407 }.$01408}1409"#,1410 r#"1411use core::ops::ControlFlow;14121413fn main() {1414 ControlFlow::Break(match true {1415 true => "\${1:placeholder}",1416 false => "\\\$",1417})1418}1419"#,1420 );1421 }14221423 #[test]1424 fn postfix_completion_for_format_like_strings() {1425 check_edit(1426 "format",1427 r#"fn main() { "{some_var:?}".$0 }"#,1428 r#"fn main() { format!("{some_var:?}") }"#,1429 );1430 check_edit(1431 "panic",1432 r#"fn main() { "Panic with {a}".$0 }"#,1433 r#"fn main() { panic!("Panic with {a}"); }"#,1434 );1435 check_edit(1436 "println",1437 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,1438 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }); }"#,1439 );1440 check_edit(1441 "loge",1442 r#"fn main() { "{2+2}".$0 }"#,1443 r#"fn main() { log::error!("{}", 2+2); }"#,1444 );1445 check_edit(1446 "logt",1447 r#"fn main() { "{2+2}".$0 }"#,1448 r#"fn main() { log::trace!("{}", 2+2); }"#,1449 );1450 check_edit(1451 "logd",1452 r#"fn main() { "{2+2}".$0 }"#,1453 r#"fn main() { log::debug!("{}", 2+2); }"#,1454 );1455 check_edit(1456 "logi",1457 r#"fn main() { "{2+2}".$0 }"#,1458 r#"fn main() { log::info!("{}", 2+2); }"#,1459 );1460 check_edit(1461 "logw",1462 r#"fn main() { "{2+2}".$0 }"#,1463 r#"fn main() { log::warn!("{}", 2+2); }"#,1464 );1465 check_edit(1466 "loge",1467 r#"fn main() { "{2+2}".$0 }"#,1468 r#"fn main() { log::error!("{}", 2+2); }"#,1469 );1470 }14711472 #[test]1473 fn postfix_custom_snippets_completion_for_references() {1474 // https://github.com/rust-lang/rust-analyzer/issues/792914751476 let snippet = Snippet::new(1477 &[],1478 &["ok".into()],1479 &["Ok(${receiver})".into()],1480 "",1481 &[],1482 crate::SnippetScope::Expr,1483 )1484 .unwrap();14851486 check_edit_with_config(1487 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1488 "ok",1489 r#"fn main() { &&42.o$0 }"#,1490 r#"fn main() { Ok(&&42) }"#,1491 );14921493 check_edit_with_config(1494 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1495 "ok",1496 r#"fn main() { &&42.$0 }"#,1497 r#"fn main() { Ok(&&42) }"#,1498 );14991500 check_edit_with_config(1501 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1502 "ok",1503 r#"fn main() { &raw mut 42.$0 }"#,1504 r#"fn main() { Ok(&raw mut 42) }"#,1505 );15061507 check_edit_with_config(1508 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1509 "ok",1510 r#"fn main() { &raw const 42.$0 }"#,1511 r#"fn main() { Ok(&raw const 42) }"#,1512 );15131514 check_edit_with_config(1515 CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },1516 "ok",1517 r#"1518struct A {1519 a: i32,1520}15211522fn main() {1523 let a = A {a :1};1524 &a.a.$01525}1526 "#,1527 r#"1528struct A {1529 a: i32,1530}15311532fn main() {1533 let a = A {a :1};1534 Ok(&a.a)1535}1536 "#,1537 );1538 }15391540 #[test]1541 fn postfix_custom_snippets_completion_for_reference_expr() {1542 // https://github.com/rust-lang/rust-analyzer/issues/210351543 let snippet = Snippet::new(1544 &[],1545 &["group".into()],1546 &["(${receiver})".into()],1547 "",1548 &[],1549 crate::SnippetScope::Expr,1550 )1551 .unwrap();15521553 check_edit_with_config(1554 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1555 "group",1556 r#"fn main() { &[1, 2, 3].g$0 }"#,1557 r#"fn main() { (&[1, 2, 3]) }"#,1558 );15591560 check_edit_with_config(1561 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1562 "group",1563 r#"fn main() { &&foo(a, b, 1+1).$0 }"#,1564 r#"fn main() { (&&foo(a, b, 1+1)) }"#,1565 );15661567 check_edit_with_config(1568 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1569 "group",1570 r#"fn main() { &mut Foo { a: 1, b: 2, c: 3 }.$0 }"#,1571 r#"fn main() { (&mut Foo { a: 1, b: 2, c: 3 }) }"#,1572 );15731574 check_edit_with_config(1575 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1576 "group",1577 r#"fn main() { &raw mut Foo::new().$0 }"#,1578 r#"fn main() { (&raw mut Foo::new()) }"#,1579 );15801581 check_edit_with_config(1582 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1583 "group",1584 r#"fn main() { &raw const Foo::bar::SOME_CONST.$0 }"#,1585 r#"fn main() { (&raw const Foo::bar::SOME_CONST) }"#,1586 );15871588 check_edit_with_config(1589 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1590 "group",1591 r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }1592fn main() { id!(&raw const Foo::bar::SOME_CONST.$0) }"#,1593 r#"macro_rules! id { ($($t:tt)*) => ($($t)*); }1594fn main() { id!((&raw const Foo::bar::SOME_CONST)) }"#,1595 );1596 }15971598 #[test]1599 fn no_postfix_completions_in_if_block_that_has_an_else() {1600 check(1601 r#"1602fn test() {1603 if true {}.$0 else {}1604}1605"#,1606 expect![[r#""#]],1607 );1608 }16091610 #[test]1611 fn mut_ref_consuming() {1612 check_edit(1613 "call",1614 r#"1615fn main() {1616 let mut x = &mut 2;1617 &mut x.$0;1618}1619"#,1620 r#"1621fn main() {1622 let mut x = &mut 2;1623 ${1}(&mut x);1624}1625"#,1626 );1627 }16281629 #[test]1630 fn deref_consuming() {1631 check_edit(1632 "call",1633 r#"1634fn main() {1635 let mut x = &mut 2;1636 &mut *x.$0;1637}1638"#,1639 r#"1640fn main() {1641 let mut x = &mut 2;1642 ${1}(&mut *x);1643}1644"#,1645 );1646 }16471648 #[test]1649 fn inside_macro() {1650 check_edit(1651 "box",1652 r#"1653macro_rules! assert {1654 ( $it:expr $(,)? ) => { $it };1655}16561657fn foo() {1658 let a = true;1659 assert!(if a == false { true } else { false }.$0);1660}1661 "#,1662 r#"1663macro_rules! assert {1664 ( $it:expr $(,)? ) => { $it };1665}16661667fn foo() {1668 let a = true;1669 assert!(Box::new(if a == false { true } else { false }));1670}1671 "#,1672 );1673 }16741675 #[test]1676 fn snippet_dedent() {1677 check_edit(1678 "let",1679 r#"1680//- minicore: option1681fn foo(x: Option<i32>, y: Option<i32>) {1682 let _f = || {1683 x1684 .and(y)1685 .map(|it| {1686 it+21687 })1688 .$01689 };1690}1691"#,1692 r#"1693fn foo(x: Option<i32>, y: Option<i32>) {1694 let _f = || {1695 let $0 = x1696.and(y)1697.map(|it| {1698 it+21699});1700 };1701}1702"#,1703 );1704 }17051706 #[test]1707 fn postfix_new() {1708 check_edit(1709 "new",1710 r#"1711struct OtherThing;1712struct RefCell<T>(T);1713impl<T> RefCell<T> {1714 fn new(t: T) -> Self { RefCell(t) }1715}17161717fn main() {1718 let other_thing = OtherThing;1719 let thing: RefCell<OtherThing> = other_thing.$0;1720}1721"#,1722 r#"1723struct OtherThing;1724struct RefCell<T>(T);1725impl<T> RefCell<T> {1726 fn new(t: T) -> Self { RefCell(t) }1727}17281729fn main() {1730 let other_thing = OtherThing;1731 let thing: RefCell<OtherThing> = RefCell::new(other_thing$0);1732}1733"#,1734 );17351736 check_edit(1737 "new",1738 r#"1739mod foo {1740 pub struct OtherThing;1741 pub struct RefCell<T>(T);1742 impl<T> RefCell<T> {1743 pub fn new(t: T) -> Self { RefCell(t) }1744 }1745}17461747fn main() {1748 let thing: foo::RefCell<foo::OtherThing> = foo::OtherThing.$0;1749}1750"#,1751 r#"1752mod foo {1753 pub struct OtherThing;1754 pub struct RefCell<T>(T);1755 impl<T> RefCell<T> {1756 pub fn new(t: T) -> Self { RefCell(t) }1757 }1758}17591760fn main() {1761 let thing: foo::RefCell<foo::OtherThing> = foo::RefCell::new(foo::OtherThing$0);1762}1763"#,1764 );1765 }1766}