From 4e43df26b2650cdef4178d2493e70c0ce946e39c Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 24 Mar 2020 01:18:21 +0200 Subject: Consider references when applying postfix completions --- crates/ra_ide/src/completion/complete_postfix.rs | 140 ++++++++++++++++++++--- 1 file changed, 123 insertions(+), 17 deletions(-) diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 0ba382165..0a00054b2 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs @@ -1,6 +1,9 @@ //! FIXME: write short doc here -use ra_syntax::{ast::AstNode, TextRange, TextUnit}; +use ra_syntax::{ + ast::{self, AstNode}, + TextRange, TextUnit, +}; use ra_text_edit::TextEdit; use crate::{ @@ -21,13 +24,8 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { None => return, }; - let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal { - let text = dot_receiver.syntax().text(); - let without_dot = ..text.len() - TextUnit::of_char('.'); - text.slice(without_dot).to_string() - } else { - dot_receiver.syntax().text().to_string() - }; + let receiver_text = + get_receiver_text(dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); let receiver_ty = match ctx.sema.type_of_expr(&dot_receiver) { Some(it) => it, @@ -35,10 +33,17 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { }; if receiver_ty.is_bool() || receiver_ty.is_unknown() { - postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text)) - .add_to(acc); postfix_snippet( ctx, + &dot_receiver, + "if", + "if expr {}", + &format!("if {} {{$0}}", receiver_text), + ) + .add_to(acc); + postfix_snippet( + ctx, + &dot_receiver, "while", "while expr {}", &format!("while {} {{\n$0\n}}", receiver_text), @@ -46,28 +51,70 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { .add_to(acc); } - postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); + // !&&&42 is a compiler error, ergo process it before considering the references + postfix_snippet(ctx, &dot_receiver, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); - postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); - postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); + postfix_snippet(ctx, &dot_receiver, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); + postfix_snippet(ctx, &dot_receiver, "refm", "&mut expr", &format!("&mut {}", receiver_text)) + .add_to(acc); + + // The rest of the postfix completions create an expression that moves an argument, + // so it's better to consider references now to avoid breaking the compilation + let dot_receiver = include_references(dot_receiver); + let receiver_text = + get_receiver_text(&dot_receiver, ctx.dot_receiver_is_ambiguous_float_literal); postfix_snippet( ctx, + &dot_receiver, "match", "match expr {}", &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), ) .add_to(acc); - postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); + postfix_snippet( + ctx, + &dot_receiver, + "box", + "Box::new(expr)", + &format!("Box::new({})", receiver_text), + ) + .add_to(acc); - postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) + postfix_snippet(ctx, &dot_receiver, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)) .add_to(acc); } -fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { +fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { + if receiver_is_ambiguous_float_literal { + let text = receiver.syntax().text(); + let without_dot = ..text.len() - TextUnit::of_char('.'); + text.slice(without_dot).to_string() + } else { + receiver.to_string() + } +} + +fn include_references(initial_element: &ast::Expr) -> ast::Expr { + let mut resulting_element = initial_element.clone(); + while let Some(parent_ref_element) = + resulting_element.syntax().parent().and_then(ast::RefExpr::cast) + { + resulting_element = ast::Expr::from(parent_ref_element); + } + resulting_element +} + +fn postfix_snippet( + ctx: &CompletionContext, + receiver: &ast::Expr, + label: &str, + detail: &str, + snippet: &str, +) -> Builder { let edit = { - let receiver_syntax = ctx.dot_receiver.as_ref().expect("no receiver available").syntax(); + let receiver_syntax = receiver.syntax(); let receiver_range = ctx.sema.original_range(receiver_syntax).range; let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); TextEdit::replace(delete_range, snippet.to_string()) @@ -340,4 +387,63 @@ mod tests { "### ); } + + #[test] + fn postfix_completion_for_references() { + assert_debug_snapshot!( + do_postfix_completion( + r#" + fn main() { + &&&&42.<|> + } + "#, + ), + @r###" + [ + CompletionItem { + label: "box", + source_range: [56; 56), + delete: [49; 56), + insert: "Box::new(&&&&42)", + detail: "Box::new(expr)", + }, + CompletionItem { + label: "dbg", + source_range: [56; 56), + delete: [49; 56), + insert: "dbg!(&&&&42)", + detail: "dbg!(expr)", + }, + CompletionItem { + label: "match", + source_range: [56; 56), + delete: [49; 56), + insert: "match &&&&42 {\n ${1:_} => {$0\\},\n}", + detail: "match expr {}", + }, + CompletionItem { + label: "not", + source_range: [56; 56), + delete: [53; 56), + insert: "!42", + detail: "!expr", + }, + CompletionItem { + label: "ref", + source_range: [56; 56), + delete: [53; 56), + insert: "&42", + detail: "&expr", + }, + CompletionItem { + label: "refm", + source_range: [56; 56), + delete: [53; 56), + insert: "&mut 42", + detail: "&mut expr", + }, + ] + "### + ); + } } -- cgit v1.2.3