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 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};2425use 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};3132pub(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 }4041 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_literal48 }49 DotAccessKind::Method => false,50 },51 ),52 _ => return,53 };54 let expr_ctx = &dot_access.ctx;55 let receiver_accessor = receiver_accessor(dot_receiver);5657 let receiver_text =58 get_receiver_text(&ctx.sema, dot_receiver, receiver_is_ambiguous_float_literal);5960 let cap = match ctx.config.snippet_cap {61 Some(it) => it,62 None => return,63 };6465 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 };7576 let cfg = ctx.config.find_path_config(ctx.is_nightly);7778 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 }9293 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);9697 // The rest of the postfix completions create an expression that moves an argument,98 // so it's better to consider references now to avoid breaking the compilation99100 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 };108109 if !ctx.config.snippets.is_empty() {110 add_custom_postfix_completions(acc, ctx, &postfix_snippet, &receiver_text);111 }112113 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); // fixme116 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);119120 let try_enum = TryEnum::from_ty(&ctx.sema, receiver_ty);121 let is_in_cond = is_in_condition(&dot_receiver_including_refs);122 if let Some(parent) = dot_receiver_including_refs.syntax().parent() {123 let placeholder = suggest_receiver_name(dot_receiver, "0", &ctx.sema);124 match &try_enum {125 Some(try_enum) if is_in_cond => match try_enum {126 TryEnum::Result => {127 postfix_snippet(128 "let",129 "let Ok(_)",130 &format!("let Ok({placeholder}) = {receiver_text}"),131 )132 .add_to(acc, ctx.db);133 postfix_snippet(134 "letm",135 "let Ok(mut _)",136 &format!("let Ok(mut {placeholder}) = {receiver_text}"),137 )138 .add_to(acc, ctx.db);139 }140 TryEnum::Option => {141 postfix_snippet(142 "let",143 "let Some(_)",144 &format!("let Some({placeholder}) = {receiver_text}"),145 )146 .add_to(acc, ctx.db);147 postfix_snippet(148 "letm",149 "let Some(mut _)",150 &format!("let Some(mut {placeholder}) = {receiver_text}"),151 )152 .add_to(acc, ctx.db);153 }154 },155 _ if is_in_cond => {156 postfix_snippet("let", "let", &format!("let $1 = {receiver_text}"))157 .add_to(acc, ctx.db);158 }159 _ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => {160 postfix_snippet("let", "let", &format!("let $0 = {receiver_text}{semi}"))161 .add_to(acc, ctx.db);162 postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text}{semi}"))163 .add_to(acc, ctx.db);164 }165 _ if matches!(parent.kind(), MATCH_ARM | CLOSURE_EXPR) => {166 postfix_snippet(167 "let",168 "let",169 &format!("{{\n let $1 = {receiver_text};\n $0\n}}"),170 )171 .add_to(acc, ctx.db);172 postfix_snippet(173 "letm",174 "let mut",175 &format!("{{\n let mut $1 = {receiver_text};\n $0\n}}"),176 )177 .add_to(acc, ctx.db);178 }179 _ => (),180 }181 }182183 if !is_in_cond {184 match try_enum {185 Some(try_enum) => match try_enum {186 TryEnum::Result => {187 postfix_snippet(188 "match",189 "match expr {}",190 &format!("match {receiver_text} {{\n Ok(${{1:_}}) => {{$2}},\n Err(${{3:_}}) => {{$0}},\n}}"),191 )192 .add_to(acc, ctx.db);193 }194 TryEnum::Option => {195 postfix_snippet(196 "match",197 "match expr {}",198 &format!(199 "match {receiver_text} {{\n Some(${{1:_}}) => {{$2}},\n None => {{$0}},\n}}"200 ),201 )202 .add_to(acc, ctx.db);203 }204 },205 None => {206 postfix_snippet(207 "match",208 "match expr {}",209 &format!("match {receiver_text} {{\n ${{1:_}} => {{$0}},\n}}"),210 )211 .add_to(acc, ctx.db);212 }213 }214 if let Some(try_enum) = &try_enum {215 let placeholder = suggest_receiver_name(dot_receiver, "1", &ctx.sema);216 match try_enum {217 TryEnum::Result => {218 postfix_snippet(219 "ifl",220 "if let Ok {}",221 &format!("if let Ok({placeholder}) = {receiver_text} {{\n $0\n}}"),222 )223 .add_to(acc, ctx.db);224225 postfix_snippet(226 "lete",227 "let Ok else {}",228 &format!(229 "let Ok({placeholder}) = {receiver_text} else {{\n $2\n}};\n$0"230 ),231 )232 .add_to(acc, ctx.db);233234 postfix_snippet(235 "while",236 "while let Ok {}",237 &format!("while let Ok({placeholder}) = {receiver_text} {{\n $0\n}}"),238 )239 .add_to(acc, ctx.db);240 }241 TryEnum::Option => {242 postfix_snippet(243 "ifl",244 "if let Some {}",245 &format!("if let Some({placeholder}) = {receiver_text} {{\n $0\n}}"),246 )247 .add_to(acc, ctx.db);248249 postfix_snippet(250 "lete",251 "let Some else {}",252 &format!(253 "let Some({placeholder}) = {receiver_text} else {{\n $2\n}};\n$0"254 ),255 )256 .add_to(acc, ctx.db);257258 postfix_snippet(259 "while",260 "while let Some {}",261 &format!("while let Some({placeholder}) = {receiver_text} {{\n $0\n}}"),262 )263 .add_to(acc, ctx.db);264 }265 }266 } else if receiver_ty.is_bool() || receiver_ty.is_unknown() {267 postfix_snippet("if", "if expr {}", &format!("if {receiver_text} {{\n $0\n}}"))268 .add_to(acc, ctx.db);269 postfix_snippet(270 "while",271 "while expr {}",272 &format!("while {receiver_text} {{\n $0\n}}"),273 )274 .add_to(acc, ctx.db);275 } else if let Some(trait_) = ctx.famous_defs().core_iter_IntoIterator()276 && receiver_ty.impls_trait(ctx.db, trait_, &[])277 {278 postfix_snippet(279 "for",280 "for ele in expr {}",281 &format!("for ele in {receiver_text} {{\n $0\n}}"),282 )283 .add_to(acc, ctx.db);284 }285 }286287 if receiver_ty.is_bool() || receiver_ty.is_unknown() {288 postfix_snippet("not", "!expr", &format!("!{receiver_text}")).add_to(acc, ctx.db);289 }290291 let block_should_be_wrapped = if let ast::Expr::BlockExpr(block) = dot_receiver {292 block.modifier().is_some() || !block.is_standalone()293 } else {294 true295 };296 {297 let (open_brace, close_brace) =298 if block_should_be_wrapped { ("{ ", " }") } else { ("", "") };299 // FIXME: Why add parentheses300 let (open_paren, close_paren) = if is_in_cond { ("(", ")") } else { ("", "") };301 let unsafe_completion_string =302 format!("{open_paren}unsafe {open_brace}{receiver_text}{close_brace}{close_paren}");303 postfix_snippet("unsafe", "unsafe {}", &unsafe_completion_string).add_to(acc, ctx.db);304305 let const_completion_string =306 format!("{open_paren}const {open_brace}{receiver_text}{close_brace}{close_paren}");307 postfix_snippet("const", "const {}", &const_completion_string).add_to(acc, ctx.db);308 }309310 if let ast::Expr::Literal(literal) = dot_receiver.clone()311 && let Some(literal_text) = ast::String::cast(literal.token())312 {313 add_format_like_completions(acc, ctx, dot_receiver, cap, &literal_text, semi);314 }315316 postfix_snippet("return", "return expr", &format!("return {receiver_text}{semi}"))317 .add_to(acc, ctx.db);318319 if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {320 postfix_snippet("break", "break expr", &format!("break {receiver_text}{semi}"))321 .add_to(acc, ctx.db);322 }323}324325fn suggest_receiver_name(326 receiver: &ast::Expr,327 n: &str,328 sema: &Semantics<'_, RootDatabase>,329) -> SmolStr {330 let placeholder = |name| format_smolstr!("${{{n}:{name}}}");331332 match receiver {333 ast::Expr::PathExpr(path) => {334 if let Some(name) = path.path().and_then(|it| it.as_single_name_ref()) {335 return placeholder(name.text().as_str());336 }337 }338 ast::Expr::RefExpr(it) => {339 if let Some(receiver) = it.expr() {340 return suggest_receiver_name(&receiver, n, sema);341 }342 }343 _ => {}344 }345346 let name = NameGenerator::new_with_names([].into_iter()).try_for_variable(receiver, sema);347 match name {348 Some(name) => placeholder(&name),349 None => format_smolstr!("${n}"),350 }351}352353fn get_receiver_text(354 sema: &Semantics<'_, RootDatabase>,355 receiver: &ast::Expr,356 receiver_is_ambiguous_float_literal: bool,357) -> String {358 // Do not just call `receiver.to_string()`, as that will mess up whitespaces inside macros.359 let Some(mut range) = sema.original_range_opt(receiver.syntax()) else {360 return receiver.to_string();361 };362 if receiver_is_ambiguous_float_literal {363 range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))364 }365 let file_text = sema.db.file_text(range.file_id.file_id(sema.db));366 let text = file_text.text(sema.db);367 let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.start())]);368 let mut text = stdx::dedent_by(indent_spaces, &text[range.range]);369370 // The receiver texts should be interpreted as-is, as they are expected to be371 // normal Rust expressions.372 escape_snippet_bits(&mut text);373 return text;374375 fn indent_of_tail_line(text: &str) -> usize {376 let tail_line = text.rsplit_once('\n').map_or(text, |(_, s)| s);377 let trimmed = tail_line.trim_start_matches(' ');378 tail_line.len() - trimmed.len()379 }380}381382/// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs.383///384/// Note that we don't need to escape the other characters that can be escaped,385/// because they wouldn't be treated as snippet-specific constructs without '$'.386fn escape_snippet_bits(text: &mut String) {387 stdx::replace(text, '\\', "\\\\");388 stdx::replace(text, '$', "\\$");389}390391fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr {392 receiver393 .syntax()394 .parent()395 .and_then(ast::Expr::cast)396 .filter(|it| {397 matches!(398 it,399 ast::Expr::FieldExpr(_) | ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_)400 )401 })402 .unwrap_or_else(|| receiver.clone())403}404405/// Given an `initial_element`, tries to expand it to include deref(s), not(s), and then references.406/// Returns the expanded expressions, and the added prefix as a string407///408/// For example, if called with the `42` in `&&mut *42`, would return `(&&mut *42, "&&mut *")`.409fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {410 let mut resulting_element = initial_element.clone();411 let mut prefix = String::new();412413 while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)414 && parent.op_kind() == Some(ast::UnaryOp::Deref)415 {416 resulting_element = ast::Expr::from(parent);417 prefix.insert(0, '*');418 }419420 while let Some(parent) = resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)421 && parent.op_kind() == Some(ast::UnaryOp::Not)422 {423 resulting_element = ast::Expr::from(parent);424 prefix.insert(0, '!');425 }426427 while let Some(parent_ref_element) =428 resulting_element.syntax().parent().and_then(ast::RefExpr::cast)429 {430 let last_child_or_token = parent_ref_element.syntax().last_child_or_token();431 prefix.insert_str(432 0,433 parent_ref_element434 .syntax()435 .children_with_tokens()436 .filter(|it| Some(it) != last_child_or_token.as_ref())437 .format("")438 .to_smolstr()439 .as_str(),440 );441 resulting_element = ast::Expr::from(parent_ref_element);442 }443444 (resulting_element, prefix)445}446447fn build_postfix_snippet_builder<'ctx>(448 ctx: &'ctx CompletionContext<'_>,449 cap: SnippetCap,450 receiver: &'ctx ast::Expr,451) -> Option<impl Fn(&str, &str, &str) -> Builder + 'ctx> {452 let receiver_range = ctx.sema.original_range_opt(receiver.syntax())?.range;453 if ctx.source_range().end() < receiver_range.start() {454 // This shouldn't happen, yet it does. I assume this might be due to an incorrect token455 // mapping.456 never!();457 return None;458 }459 let delete_range = TextRange::new(receiver_range.start(), ctx.source_range().end());460461 // Wrapping impl Fn in an option ruins lifetime inference for the parameters in a way that462 // can't be annotated for the closure, hence fix it by constructing it without the Option first463 fn build<'ctx>(464 ctx: &'ctx CompletionContext<'_>,465 cap: SnippetCap,466 delete_range: TextRange,467 ) -> impl Fn(&str, &str, &str) -> Builder + 'ctx {468 move |label, detail, snippet| {469 let edit = TextEdit::replace(delete_range, snippet.to_owned());470 let mut item = CompletionItem::new(471 CompletionItemKind::Snippet,472 ctx.source_range(),473 label,474 ctx.edition,475 );476 item.detail(detail).snippet_edit(cap, edit);477 let postfix_match = if ctx.original_token.text() == label {478 cov_mark::hit!(postfix_exact_match_is_high_priority);479 Some(CompletionRelevancePostfixMatch::Exact)480 } else {481 cov_mark::hit!(postfix_inexact_match_is_low_priority);482 Some(CompletionRelevancePostfixMatch::NonExact)483 };484 let relevance = CompletionRelevance { postfix_match, ..Default::default() };485 item.set_relevance(relevance);486 item487 }488 }489 Some(build(ctx, cap, delete_range))490}491492fn add_custom_postfix_completions(493 acc: &mut Completions,494 ctx: &CompletionContext<'_>,495 postfix_snippet: impl Fn(&str, &str, &str) -> Builder,496 receiver_text: &str,497) -> Option<()> {498 ImportScope::find_insert_use_container(&ctx.token.parent()?, &ctx.sema)?;499 ctx.config.postfix_snippets().filter(|(_, snip)| snip.scope == SnippetScope::Expr).for_each(500 |(trigger, snippet)| {501 let imports = match snippet.imports(ctx) {502 Some(imports) => imports,503 None => return,504 };505 let body = snippet.postfix_snippet(receiver_text);506 let mut builder =507 postfix_snippet(trigger, snippet.description.as_deref().unwrap_or_default(), &body);508 builder.documentation(Documentation::new_owned(format!("```rust\n{body}\n```")));509 for import in imports.into_iter() {510 builder.add_import(import);511 }512 builder.add_to(acc, ctx.db);513 },514 );515 None516}517518pub(crate) fn is_in_condition(it: &ast::Expr) -> bool {519 it.syntax()520 .parent()521 .and_then(|parent| {522 Some(match_ast! { match parent {523 ast::IfExpr(expr) => expr.condition()? == *it,524 ast::WhileExpr(expr) => expr.condition()? == *it,525 ast::MatchGuard(guard) => guard.condition()? == *it,526 ast::BinExpr(bin_expr) => (bin_expr.op_token()?.kind() == T![&&])527 .then(|| is_in_condition(&bin_expr.into()))?,528 ast::Expr(expr) => (expr.syntax().text_range().start() == it.syntax().text_range().start())529 .then(|| is_in_condition(&expr))?,530 _ => return None,531 } })532 })533 .unwrap_or(false)534}535536#[cfg(test)]537mod tests {538 use expect_test::expect;539540 use crate::{541 CompletionConfig, Snippet,542 tests::{TEST_CONFIG, check, check_edit, check_edit_with_config},543 };544545 #[test]546 fn postfix_completion_works_for_trivial_path_expression() {547 check(548 r#"549fn main() {550 let bar = true;551 bar.$0552}553"#,554 expect![[r#"555 sn box Box::new(expr)556 sn call function(expr)557 sn const const {}558 sn dbg dbg!(expr)559 sn dbgr dbg!(&expr)560 sn deref *expr561 sn if if expr {}562 sn let let563 sn letm let mut564 sn match match expr {}565 sn not !expr566 sn ref &expr567 sn refm &mut expr568 sn return return expr569 sn unsafe unsafe {}570 sn while while expr {}571 "#]],572 );573 }574575 #[test]576 fn postfix_completion_works_for_function_calln() {577 check(578 r#"579fn foo(elt: bool) -> bool {580 !elt581}582583fn main() {584 let bar = true;585 foo(bar.$0)586}587"#,588 expect![[r#"589 sn box Box::new(expr)590 sn call function(expr)591 sn const const {}592 sn dbg dbg!(expr)593 sn dbgr dbg!(&expr)594 sn deref *expr595 sn if if expr {}596 sn match match expr {}597 sn not !expr598 sn ref &expr599 sn refm &mut expr600 sn return return expr601 sn unsafe unsafe {}602 sn while while expr {}603 "#]],604 );605 }606607 #[test]608 fn postfix_completion_works_in_if_condition() {609 check(610 r#"611fn foo(cond: bool) {612 if cond.$0613}614"#,615 expect![[r#"616 sn box Box::new(expr)617 sn call function(expr)618 sn const const {}619 sn dbg dbg!(expr)620 sn dbgr dbg!(&expr)621 sn deref *expr622 sn let let623 sn not !expr624 sn ref &expr625 sn refm &mut expr626 sn return return expr627 sn unsafe unsafe {}628 "#]],629 );630 }631632 #[test]633 fn postfix_type_filtering() {634 check(635 r#"636fn main() {637 let bar: u8 = 12;638 bar.$0639}640"#,641 expect![[r#"642 sn box Box::new(expr)643 sn call function(expr)644 sn const const {}645 sn dbg dbg!(expr)646 sn dbgr dbg!(&expr)647 sn deref *expr648 sn let let649 sn letm let mut650 sn match match expr {}651 sn ref &expr652 sn refm &mut expr653 sn return return expr654 sn unsafe unsafe {}655 "#]],656 )657 }658659 #[test]660 fn let_middle_block() {661 check_edit(662 "let",663 r#"664fn main() {665 baz.l$0666 res667}668"#,669 r#"670fn main() {671 let $0 = baz;672 res673}674"#,675 );676677 check(678 r#"679fn main() {680 baz.l$0681 res682}683"#,684 expect![[r#"685 sn box Box::new(expr)686 sn call function(expr)687 sn const const {}688 sn dbg dbg!(expr)689 sn dbgr dbg!(&expr)690 sn deref *expr691 sn if if expr {}692 sn let let693 sn letm let mut694 sn match match expr {}695 sn not !expr696 sn ref &expr697 sn refm &mut expr698 sn return return expr699 sn unsafe unsafe {}700 sn while while expr {}701 "#]],702 );703 check(704 r#"705fn main() {706 &baz.l$0707 res708}709"#,710 expect![[r#"711 sn box Box::new(expr)712 sn call function(expr)713 sn const const {}714 sn dbg dbg!(expr)715 sn dbgr dbg!(&expr)716 sn deref *expr717 sn if if expr {}718 sn let let719 sn letm let mut720 sn match match expr {}721 sn not !expr722 sn ref &expr723 sn refm &mut expr724 sn return return expr725 sn unsafe unsafe {}726 sn while while expr {}727 "#]],728 );729 }730731 #[test]732 fn let_tail_block() {733 check_edit(734 "let",735 r#"736fn main() {737 baz.l$0738}739"#,740 r#"741fn main() {742 let $0 = baz;743}744"#,745 );746747 check(748 r#"749fn main() {750 baz.l$0751}752"#,753 expect![[r#"754 sn box Box::new(expr)755 sn call function(expr)756 sn const const {}757 sn dbg dbg!(expr)758 sn dbgr dbg!(&expr)759 sn deref *expr760 sn if if expr {}761 sn let let762 sn letm let mut763 sn match match expr {}764 sn not !expr765 sn ref &expr766 sn refm &mut expr767 sn return return expr768 sn unsafe unsafe {}769 sn while while expr {}770 "#]],771 );772773 check(774 r#"775fn main() {776 &baz.l$0777}778"#,779 expect![[r#"780 sn box Box::new(expr)781 sn call function(expr)782 sn const const {}783 sn dbg dbg!(expr)784 sn dbgr dbg!(&expr)785 sn deref *expr786 sn if if expr {}787 sn let let788 sn letm let mut789 sn match match expr {}790 sn not !expr791 sn ref &expr792 sn refm &mut expr793 sn return return expr794 sn unsafe unsafe {}795 sn while while expr {}796 "#]],797 );798 }799800 #[test]801 fn let_before_semicolon() {802 check_edit(803 "let",804 r#"805fn main() {806 baz.l$0;807}808"#,809 r#"810fn main() {811 let $0 = baz;812}813"#,814 );815 }816817 #[test]818 fn option_iflet() {819 check_edit(820 "ifl",821 r#"822//- minicore: option823fn main() {824 let bar = Some(true);825 bar.$0826}827"#,828 r#"829fn main() {830 let bar = Some(true);831 if let Some(${1:bar}) = bar {832 $0833}834}835"#,836 );837 }838839 #[test]840 fn option_iflet_cond() {841 check(842 r#"843//- minicore: option844fn main() {845 let bar = Some(true);846 if bar.$0847}848"#,849 expect![[r#"850 me and(…) fn(self, Option<U>) -> Option<U>851 me as_ref() const fn(&self) -> Option<&T>852 me ok_or(…) const fn(self, E) -> Result<T, E>853 me unwrap() const fn(self) -> T854 me unwrap_or(…) fn(self, T) -> T855 sn box Box::new(expr)856 sn call function(expr)857 sn const const {}858 sn dbg dbg!(expr)859 sn dbgr dbg!(&expr)860 sn deref *expr861 sn let let Some(_)862 sn letm let Some(mut _)863 sn ref &expr864 sn refm &mut expr865 sn return return expr866 sn unsafe unsafe {}867 "#]],868 );869 check_edit(870 "let",871 r#"872//- minicore: option873fn main() {874 let bar = Some(true);875 if bar.$0876}877"#,878 r#"879fn main() {880 let bar = Some(true);881 if let Some(${0:bar}) = bar882}883"#,884 );885 check_edit(886 "let",887 r#"888//- minicore: option889fn main() {890 let bar = Some(true);891 if true && bar.$0892}893"#,894 r#"895fn main() {896 let bar = Some(true);897 if true && let Some(${0:bar}) = bar898}899"#,900 );901 check_edit(902 "let",903 r#"904//- minicore: option905fn main() {906 let bar = Some(true);907 if true && true && bar.$0908}909"#,910 r#"911fn main() {912 let bar = Some(true);913 if true && true && let Some(${0:bar}) = bar914}915"#,916 );917 }918919 #[test]920 fn iflet_fallback_cond() {921 check_edit(922 "let",923 r#"924fn main() {925 let bar = 2;926 if bar.$0927}928"#,929 r#"930fn main() {931 let bar = 2;932 if let $1 = bar933}934"#,935 );936 }937938 #[test]939 fn match_arm_let_block() {940 check(941 r#"942fn main() {943 match 2 {944 bar => bar.$0945 }946}947"#,948 expect![[r#"949 sn box Box::new(expr)950 sn call function(expr)951 sn const const {}952 sn dbg dbg!(expr)953 sn dbgr dbg!(&expr)954 sn deref *expr955 sn let let956 sn letm let mut957 sn match match expr {}958 sn ref &expr959 sn refm &mut expr960 sn return return expr961 sn unsafe unsafe {}962 "#]],963 );964 check(965 r#"966fn main() {967 match 2 {968 bar => &bar.l$0969 }970}971"#,972 expect![[r#"973 sn box Box::new(expr)974 sn call function(expr)975 sn const const {}976 sn dbg dbg!(expr)977 sn dbgr dbg!(&expr)978 sn deref *expr979 sn let let980 sn letm let mut981 sn match match expr {}982 sn ref &expr983 sn refm &mut expr984 sn return return expr985 sn unsafe unsafe {}986 "#]],987 );988 check_edit(989 "let",990 r#"991fn main() {992 match 2 {993 bar => bar.$0994 }995}996"#,997 r#"998fn main() {999 match 2 {1000 bar => {1001 let $1 = bar;1002 $01003}1004 }1005}1006"#,1007 );1008 }10091010 #[test]1011 fn closure_let_block() {1012 check_edit(1013 "let",1014 r#"1015fn main() {1016 let bar = 2;1017 let f = || bar.$0;1018}1019"#,1020 r#"1021fn main() {1022 let bar = 2;1023 let f = || {1024 let $1 = bar;1025 $01026};1027}1028"#,1029 );1030 }10311032 #[test]1033 fn option_letelse() {1034 check_edit(1035 "lete",1036 r#"1037//- minicore: option1038fn main() {1039 let bar = Some(true);1040 bar.$01041}1042"#,1043 r#"1044fn main() {1045 let bar = Some(true);1046 let Some(${1:bar}) = bar else {1047 $21048};1049$01050}1051"#,1052 );1053 }10541055 #[test]1056 fn result_match() {1057 check_edit(1058 "match",1059 r#"1060//- minicore: result1061fn main() {1062 let bar = Ok(true);1063 bar.$01064}1065"#,1066 r#"1067fn main() {1068 let bar = Ok(true);1069 match bar {1070 Ok(${1:_}) => {$2},1071 Err(${3:_}) => {$0},1072}1073}1074"#,1075 );1076 }10771078 #[test]1079 fn postfix_completion_works_for_ambiguous_float_literal() {1080 check_edit("refm", r#"fn main() { 42.$0 }"#, r#"fn main() { &mut 42 }"#)1081 }10821083 #[test]1084 fn works_in_simple_macro() {1085 check_edit(1086 "dbg",1087 r#"1088macro_rules! m { ($e:expr) => { $e } }1089fn main() {1090 let bar: u8 = 12;1091 m!(bar.d$0)1092}1093"#,1094 r#"1095macro_rules! m { ($e:expr) => { $e } }1096fn main() {1097 let bar: u8 = 12;1098 m!(dbg!(bar))1099}1100"#,1101 );1102 }11031104 #[test]1105 fn postfix_completion_for_references() {1106 check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);1107 check_edit("dbg", r#"fn main() { &&*"hello".$0 }"#, r#"fn main() { dbg!(&&*"hello") }"#);1108 check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);1109 check_edit(1110 "ifl",1111 r#"1112//- minicore: option1113fn main() {1114 let bar = &Some(true);1115 bar.$01116}1117"#,1118 r#"1119fn main() {1120 let bar = &Some(true);1121 if let Some(${1:bar}) = bar {1122 $01123}1124}1125"#,1126 )1127 }11281129 #[test]1130 fn postfix_completion_for_nots() {1131 check_edit(1132 "if",1133 r#"1134fn main() {1135 let is_foo = true;1136 !is_foo.$01137}1138"#,1139 r#"1140fn main() {1141 let is_foo = true;1142 if !is_foo {1143 $01144}1145}1146"#,1147 )1148 }11491150 #[test]1151 fn postfix_completion_for_unsafe() {1152 postfix_completion_for_block("unsafe");1153 }11541155 #[test]1156 fn postfix_completion_for_const() {1157 postfix_completion_for_block("const");1158 }11591160 fn postfix_completion_for_block(kind: &str) {1161 check_edit(kind, r#"fn main() { foo.$0 }"#, &format!("fn main() {{ {kind} {{ foo }} }}"));1162 check_edit(1163 kind,1164 r#"fn main() { { foo }.$0 }"#,1165 &format!("fn main() {{ {kind} {{ foo }} }}"),1166 );1167 check_edit(1168 kind,1169 r#"fn main() { if x { foo }.$0 }"#,1170 &format!("fn main() {{ {kind} {{ if x {{ foo }} }} }}"),1171 );1172 check_edit(1173 kind,1174 r#"fn main() { loop { foo }.$0 }"#,1175 &format!("fn main() {{ {kind} {{ loop {{ foo }} }} }}"),1176 );1177 check_edit(1178 kind,1179 r#"fn main() { if true {}.$0 }"#,1180 &format!("fn main() {{ {kind} {{ if true {{}} }} }}"),1181 );1182 check_edit(1183 kind,1184 r#"fn main() { while true {}.$0 }"#,1185 &format!("fn main() {{ {kind} {{ while true {{}} }} }}"),1186 );1187 check_edit(1188 kind,1189 r#"fn main() { for i in 0..10 {}.$0 }"#,1190 &format!("fn main() {{ {kind} {{ for i in 0..10 {{}} }} }}"),1191 );1192 check_edit(1193 kind,1194 r#"fn main() { let x = if true {1} else {2}.$0 }"#,1195 &format!("fn main() {{ let x = {kind} {{ if true {{1}} else {{2}} }} }}"),1196 );11971198 if kind == "const" {1199 check_edit(1200 kind,1201 r#"fn main() { unsafe {1}.$0 }"#,1202 &format!("fn main() {{ {kind} {{ unsafe {{1}} }} }}"),1203 );1204 } else {1205 check_edit(1206 kind,1207 r#"fn main() { const {1}.$0 }"#,1208 &format!("fn main() {{ {kind} {{ const {{1}} }} }}"),1209 );1210 }12111212 // completion will not be triggered1213 check_edit(1214 kind,1215 r#"fn main() { let x = true else {panic!()}.$0}"#,1216 &format!("fn main() {{ let x = true else {{panic!()}}.{kind} $0}}"),1217 );1218 }12191220 #[test]1221 fn custom_postfix_completion() {1222 let config = CompletionConfig {1223 snippets: vec![1224 Snippet::new(1225 &[],1226 &["break".into()],1227 &["ControlFlow::Break(${receiver})".into()],1228 "",1229 &["core::ops::ControlFlow".into()],1230 crate::SnippetScope::Expr,1231 )1232 .unwrap(),1233 ],1234 ..TEST_CONFIG1235 };12361237 check_edit_with_config(1238 config.clone(),1239 "break",1240 r#"1241//- minicore: try1242fn main() { 42.$0 }1243"#,1244 r#"1245use core::ops::ControlFlow;12461247fn main() { ControlFlow::Break(42) }1248"#,1249 );12501251 // The receiver texts should be escaped, see comments in `get_receiver_text()`1252 // for detail.1253 //1254 // Note that the last argument is what *lsp clients would see* rather than1255 // what users would see. Unescaping happens thereafter.1256 check_edit_with_config(1257 config.clone(),1258 "break",1259 r#"1260//- minicore: try1261fn main() { '\\'.$0 }1262"#,1263 r#"1264use core::ops::ControlFlow;12651266fn main() { ControlFlow::Break('\\\\') }1267"#,1268 );12691270 check_edit_with_config(1271 config,1272 "break",1273 r#"1274//- minicore: try1275fn main() {1276 match true {1277 true => "${1:placeholder}",1278 false => "\$",1279 }.$01280}1281"#,1282 r#"1283use core::ops::ControlFlow;12841285fn main() {1286 ControlFlow::Break(match true {1287 true => "\${1:placeholder}",1288 false => "\\\$",1289})1290}1291"#,1292 );1293 }12941295 #[test]1296 fn postfix_completion_for_format_like_strings() {1297 check_edit(1298 "format",1299 r#"fn main() { "{some_var:?}".$0 }"#,1300 r#"fn main() { format!("{some_var:?}") }"#,1301 );1302 check_edit(1303 "panic",1304 r#"fn main() { "Panic with {a}".$0 }"#,1305 r#"fn main() { panic!("Panic with {a}"); }"#,1306 );1307 check_edit(1308 "println",1309 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".$0 }"#,1310 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }); }"#,1311 );1312 check_edit(1313 "loge",1314 r#"fn main() { "{2+2}".$0 }"#,1315 r#"fn main() { log::error!("{}", 2+2); }"#,1316 );1317 check_edit(1318 "logt",1319 r#"fn main() { "{2+2}".$0 }"#,1320 r#"fn main() { log::trace!("{}", 2+2); }"#,1321 );1322 check_edit(1323 "logd",1324 r#"fn main() { "{2+2}".$0 }"#,1325 r#"fn main() { log::debug!("{}", 2+2); }"#,1326 );1327 check_edit(1328 "logi",1329 r#"fn main() { "{2+2}".$0 }"#,1330 r#"fn main() { log::info!("{}", 2+2); }"#,1331 );1332 check_edit(1333 "logw",1334 r#"fn main() { "{2+2}".$0 }"#,1335 r#"fn main() { log::warn!("{}", 2+2); }"#,1336 );1337 check_edit(1338 "loge",1339 r#"fn main() { "{2+2}".$0 }"#,1340 r#"fn main() { log::error!("{}", 2+2); }"#,1341 );1342 }13431344 #[test]1345 fn postfix_custom_snippets_completion_for_references() {1346 // https://github.com/rust-lang/rust-analyzer/issues/792913471348 let snippet = Snippet::new(1349 &[],1350 &["ok".into()],1351 &["Ok(${receiver})".into()],1352 "",1353 &[],1354 crate::SnippetScope::Expr,1355 )1356 .unwrap();13571358 check_edit_with_config(1359 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1360 "ok",1361 r#"fn main() { &&42.o$0 }"#,1362 r#"fn main() { Ok(&&42) }"#,1363 );13641365 check_edit_with_config(1366 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1367 "ok",1368 r#"fn main() { &&42.$0 }"#,1369 r#"fn main() { Ok(&&42) }"#,1370 );13711372 check_edit_with_config(1373 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1374 "ok",1375 r#"fn main() { &raw mut 42.$0 }"#,1376 r#"fn main() { Ok(&raw mut 42) }"#,1377 );13781379 check_edit_with_config(1380 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1381 "ok",1382 r#"fn main() { &raw const 42.$0 }"#,1383 r#"fn main() { Ok(&raw const 42) }"#,1384 );13851386 check_edit_with_config(1387 CompletionConfig { snippets: vec![snippet], ..TEST_CONFIG },1388 "ok",1389 r#"1390struct A {1391 a: i32,1392}13931394fn main() {1395 let a = A {a :1};1396 &a.a.$01397}1398 "#,1399 r#"1400struct A {1401 a: i32,1402}14031404fn main() {1405 let a = A {a :1};1406 Ok(&a.a)1407}1408 "#,1409 );1410 }14111412 #[test]1413 fn postfix_custom_snippets_completion_for_reference_expr() {1414 // https://github.com/rust-lang/rust-analyzer/issues/210351415 let snippet = Snippet::new(1416 &[],1417 &["group".into()],1418 &["(${receiver})".into()],1419 "",1420 &[],1421 crate::SnippetScope::Expr,1422 )1423 .unwrap();14241425 check_edit_with_config(1426 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1427 "group",1428 r#"fn main() { &[1, 2, 3].g$0 }"#,1429 r#"fn main() { (&[1, 2, 3]) }"#,1430 );14311432 check_edit_with_config(1433 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1434 "group",1435 r#"fn main() { &&foo(a, b, 1+1).$0 }"#,1436 r#"fn main() { (&&foo(a, b, 1+1)) }"#,1437 );14381439 check_edit_with_config(1440 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1441 "group",1442 r#"fn main() { &mut Foo { a: 1, b: 2, c: 3 }.$0 }"#,1443 r#"fn main() { (&mut Foo { a: 1, b: 2, c: 3 }) }"#,1444 );14451446 check_edit_with_config(1447 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1448 "group",1449 r#"fn main() { &raw mut Foo::new().$0 }"#,1450 r#"fn main() { (&raw mut Foo::new()) }"#,1451 );14521453 check_edit_with_config(1454 CompletionConfig { snippets: vec![snippet.clone()], ..TEST_CONFIG },1455 "group",1456 r#"fn main() { &raw const Foo::bar::SOME_CONST.$0 }"#,1457 r#"fn main() { (&raw const Foo::bar::SOME_CONST) }"#,1458 );1459 }14601461 #[test]1462 fn no_postfix_completions_in_if_block_that_has_an_else() {1463 check(1464 r#"1465fn test() {1466 if true {}.$0 else {}1467}1468"#,1469 expect![[r#""#]],1470 );1471 }14721473 #[test]1474 fn mut_ref_consuming() {1475 check_edit(1476 "call",1477 r#"1478fn main() {1479 let mut x = &mut 2;1480 &mut x.$0;1481}1482"#,1483 r#"1484fn main() {1485 let mut x = &mut 2;1486 ${1}(&mut x);1487}1488"#,1489 );1490 }14911492 #[test]1493 fn deref_consuming() {1494 check_edit(1495 "call",1496 r#"1497fn main() {1498 let mut x = &mut 2;1499 &mut *x.$0;1500}1501"#,1502 r#"1503fn main() {1504 let mut x = &mut 2;1505 ${1}(&mut *x);1506}1507"#,1508 );1509 }15101511 #[test]1512 fn inside_macro() {1513 check_edit(1514 "box",1515 r#"1516macro_rules! assert {1517 ( $it:expr $(,)? ) => { $it };1518}15191520fn foo() {1521 let a = true;1522 assert!(if a == false { true } else { false }.$0);1523}1524 "#,1525 r#"1526macro_rules! assert {1527 ( $it:expr $(,)? ) => { $it };1528}15291530fn foo() {1531 let a = true;1532 assert!(Box::new(if a == false { true } else { false }));1533}1534 "#,1535 );1536 }15371538 #[test]1539 fn snippet_dedent() {1540 check_edit(1541 "let",1542 r#"1543//- minicore: option1544fn foo(x: Option<i32>, y: Option<i32>) {1545 let _f = || {1546 x1547 .and(y)1548 .map(|it| it+2)1549 .$01550 };1551}1552"#,1553 r#"1554fn foo(x: Option<i32>, y: Option<i32>) {1555 let _f = || {1556 let $0 = x1557 .and(y)1558 .map(|it| it+2);1559 };1560}1561"#,1562 );1563 }1564}