Repository: rust-analyzer/rust-analyzer
Pull request #4155 (2020-04-26 08:54:26)

Precompute expected type during completion


bors r+
🤖


Patch:
diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs
index 56cd086c68..f559f2b970 100644
--- a/crates/ra_ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs
@@ -4,7 +4,7 @@ use hir::ScopeDef;
 use test_utils::tested_by;
 
 use crate::completion::{CompletionContext, Completions};
-use hir::{Adt, ModuleDef};
+use hir::{Adt, ModuleDef, Type};
 use ra_syntax::AstNode;
 
 pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionContext) {
@@ -15,7 +15,9 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
         return;
     }
 
-    complete_enum_variants(acc, ctx);
+    if let Some(ty) = &ctx.expected_type {
+        complete_enum_variants(acc, ctx, ty);
+    }
 
     if ctx.is_pat_binding_or_const {
         return;
@@ -34,26 +36,24 @@ pub(super) fn complete_unqualified_path(acc: &mut Completions, ctx: &CompletionC
     });
 }
 
-fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext) {
-    if let Some(ty) = ctx.expected_type_of(&ctx.token.parent()) {
-        if let Some(Adt::Enum(enum_data)) = ty.as_adt() {
-            let variants = enum_data.variants(ctx.db);
-
-            let module = if let Some(module) = ctx.scope().module() {
-                // Compute path from the completion site if available.
-                module
-            } else {
-                // Otherwise fall back to the enum's definition site.
-                enum_data.module(ctx.db)
-            };
-
-            for variant in variants {
-                if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
-                    // Variants with trivial paths are already added by the existing completion logic,
-                    // so we should avoid adding these twice
-                    if path.segments.len() > 1 {
-                        acc.add_enum_variant(ctx, variant, Some(path.to_string()));
-                    }
+fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &Type) {
+    if let Some(Adt::Enum(enum_data)) = ty.as_adt() {
+        let variants = enum_data.variants(ctx.db);
+
+        let module = if let Some(module) = ctx.scope().module() {
+            // Compute path from the completion site if available.
+            module
+        } else {
+            // Otherwise fall back to the enum's definition site.
+            enum_data.module(ctx.db)
+        };
+
+        for variant in variants {
+            if let Some(path) = module.find_use_path(ctx.db, ModuleDef::from(variant)) {
+                // Variants with trivial paths are already added by the existing completion logic,
+                // so we should avoid adding these twice
+                if path.segments.len() > 1 {
+                    acc.add_enum_variant(ctx, variant, Some(path.to_string()));
                 }
             }
         }
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 5f2797e418..118fceb2e7 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -5,7 +5,7 @@ use ra_db::SourceDatabase;
 use ra_ide_db::RootDatabase;
 use ra_syntax::{
     algo::{find_covering_element, find_node_at_offset},
-    ast, AstNode,
+    ast, match_ast, AstNode,
     SyntaxKind::*,
     SyntaxNode, SyntaxToken, TextRange, TextSize,
 };
@@ -26,6 +26,7 @@ pub(crate) struct CompletionContext<'a> {
     /// The token before the cursor, in the macro-expanded file.
     pub(super) token: SyntaxToken,
     pub(super) krate: Option<hir::Crate>,
+    pub(super) expected_type: Option<Type>,
     pub(super) name_ref_syntax: Option<ast::NameRef>,
     pub(super) function_syntax: Option<ast::FnDef>,
     pub(super) use_item_syntax: Option<ast::UseItem>,
@@ -93,6 +94,7 @@ impl<'a> CompletionContext<'a> {
             token,
             offset: position.offset,
             krate,
+            expected_type: None,
             name_ref_syntax: None,
             function_syntax: None,
             use_item_syntax: None,
@@ -175,23 +177,30 @@ impl<'a> CompletionContext<'a> {
         self.sema.scope_at_offset(&self.token.parent(), self.offset)
     }
 
-    pub(crate) fn expected_type_of(&self, node: &SyntaxNode) -> Option<Type> {
-        for ancestor in node.ancestors() {
-            if let Some(pat) = ast::Pat::cast(ancestor.clone()) {
-                return self.sema.type_of_pat(&pat);
-            } else if let Some(expr) = ast::Expr::cast(ancestor) {
-                return self.sema.type_of_expr(&expr);
-            }
-        }
-        None
-    }
-
     fn fill(
         &mut self,
         original_file: &SyntaxNode,
         file_with_fake_ident: SyntaxNode,
         offset: TextSize,
     ) {
+        // FIXME: this is wrong in at least two cases:
+        //  * when there's no token `foo(<|>)`
+        //  * when there is a token, but it happens to have type of it's own
+        self.expected_type = self
+            .token
+            .ancestors()
+            .find_map(|node| {
+                let ty = match_ast! {
+                    match node {
+                        ast::Pat(it) => self.sema.type_of_pat(&it),
+                        ast::Expr(it) => self.sema.type_of_expr(&it),
+                        _ => return None,
+                    }
+                };
+                Some(ty)
+            })
+            .flatten();
+
         // First, let's try to complete a reference to some declaration.
         if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(&file_with_fake_ident, offset) {
             // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`.
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index f5b0744612..77d3543760 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -351,7 +351,7 @@ impl Builder {
         }
 
         // Don't add parentheses if the expected type is some function reference.
-        if let Some(ty) = ctx.expected_type_of(&ctx.token.parent()) {
+        if let Some(ty) = &ctx.expected_type {
             if ty.is_fn() {
                 return self;
             }
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 10c25666a0..c2ece49f42 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -9,7 +9,6 @@ use crate::{world::WorldSnapshot, Result};
 ///
 /// We use it to cook up the set of cli args we need to pass to Cargo to
 /// build/test/run the target.
-#[derive(Clone)]
 pub(crate) struct CargoTargetSpec {
     pub(crate) package: String,
     pub(crate) target: String,
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 6caaf5f88d..e87e8db5d3 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -393,37 +393,28 @@ pub fn handle_runnables(
         }
         res.push(to_lsp_runnable(&world, file_id, runnable)?);
     }
-    // Add `cargo check` and `cargo test` for the whole package
+    let mut check_args = vec!["check".to_string()];
+    let label;
     match CargoTargetSpec::for_file(&world, file_id)? {
         Some(spec) => {
-            for &cmd in ["check", "test"].iter() {
-                res.push(req::Runnable {
-                    range: Default::default(),
-                    label: format!("cargo {} -p {}", cmd, spec.package),
-                    bin: "cargo".to_string(),
-                    args: {
-                        let mut args = vec![cmd.to_string()];
-                        spec.clone().push_to(&mut args);
-                        args
-                    },
-                    extra_args: Vec::new(),
-                    env: FxHashMap::default(),
-                    cwd: workspace_root.map(|root| root.to_owned()),
-                })
-            }
+            label = format!("cargo check -p {}", spec.package);
+            spec.push_to(&mut check_args);
         }
         None => {
-            res.push(req::Runnable {
-                range: Default::default(),
-                label: "cargo check --workspace".to_string(),
-                bin: "cargo".to_string(),
-                args: vec!["check".to_string(), "--workspace".to_string()],
-                extra_args: Vec::new(),
-                env: FxHashMap::default(),
-                cwd: workspace_root.map(|root| root.to_owned()),
-            });
+            label = "cargo check --all".to_string();
+            check_args.push("--all".to_string())
         }
     }
+    // Always add `cargo check`.
+    res.push(req::Runnable {
+        range: Default::default(),
+        label,
+        bin: "cargo".to_string(),
+        args: check_args,
+        extra_args: Vec::new(),
+        env: FxHashMap::default(),
+        cwd: workspace_root.map(|root| root.to_owned()),
+    });
     Ok(res)
 }
 


Test patch:
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index f6245ddd47..b31533e5ef 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -87,15 +87,24 @@ fn foo() {
             }
           },
           {
-            "args": ["check", "--workspace"],
+            "args": [
+              "check",
+              "--all"
+            ],
             "extraArgs": [],
             "bin": "cargo",
             "env": {},
             "cwd": null,
-            "label": "cargo check --workspace",
+            "label": "cargo check --all",
             "range": {
-              "end": { "character": 0, "line": 0 },
-              "start": { "character": 0, "line": 0 }
+              "end": {
+                "character": 0,
+                "line": 0
+              },
+              "start": {
+                "character": 0,
+                "line": 0
+              }
             }
           }
         ]),
@@ -136,42 +145,42 @@ fn main() {}
     server.request::<Runnables>(
         RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
         json!([
-            {
-              "args": [ "test", "--package", "foo", "--test", "spam" ],
-              "extraArgs": [ "test_eggs", "--exact", "--nocapture" ],
-              "bin": "cargo",
-              "env": { "RUST_BACKTRACE": "short" },
-              "label": "test test_eggs",
-              "range": {
-                "end": { "character": 17, "line": 1 },
-                "start": { "character": 0, "line": 0 }
-              },
-              "cwd": server.path().join("foo")
-            },
-            {
-              "args": [ "check", "--package", "foo", "--test", "spam" ],
-              "extraArgs": [],
-              "bin": "cargo",
-              "env": {},
-              "label": "cargo check -p foo",
-              "range": {
-                "end": { "character": 0, "line": 0 },
-                "start": { "character": 0, "line": 0 }
-              },
-              "cwd": server.path().join("foo")
+          {
+            "args": [ "test", "--package", "foo", "--test", "spam" ],
+            "extraArgs": [ "test_eggs", "--exact", "--nocapture" ],
+            "bin": "cargo",
+            "env": { "RUST_BACKTRACE": "short" },
+            "label": "test test_eggs",
+            "range": {
+              "end": { "character": 17, "line": 1 },
+              "start": { "character": 0, "line": 0 }
             },
-            {
-              "args": [ "test", "--package", "foo", "--test", "spam" ],
-              "extraArgs": [],
-              "bin": "cargo",
-              "env": {},
-              "label": "cargo test -p foo",
-              "range": {
-                "end": { "character": 0, "line": 0 },
-                "start": { "character": 0, "line": 0 }
+            "cwd": server.path().join("foo")
+          },
+          {
+            "args": [
+              "check",
+              "--package",
+              "foo",
+              "--test",
+              "spam"
+            ],
+            "extraArgs": [],
+            "bin": "cargo",
+            "env": {},
+            "cwd": server.path().join("foo"),
+            "label": "cargo check -p foo",
+            "range": {
+              "end": {
+                "character": 0,
+                "line": 0
               },
-              "cwd": server.path().join("foo")
+              "start": {
+                "character": 0,
+                "line": 0
+              }
             }
+          }
         ]),
     );
 }