]> git.lizzy.rs Git - rust.git/commitdiff
Consider references when applying postfix completions
authorKirill Bulatov <mail4score@gmail.com>
Mon, 23 Mar 2020 23:18:21 +0000 (01:18 +0200)
committerKirill Bulatov <mail4score@gmail.com>
Mon, 23 Mar 2020 23:56:06 +0000 (01:56 +0200)
crates/ra_ide/src/completion/complete_postfix.rs

index 0ba382165bd4dbd87f948093996e5d6cc4236ce4..0a00054b2395ad58181141284de3b21663ce4bd2 100644 (file)
@@ -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 @@ fn main() {
         "###
         );
     }
+
+    #[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",
+            },
+        ]
+        "###
+        );
+    }
 }