aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock25
-rw-r--r--crates/ra_assists/src/ast_transform.rs9
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs4
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs8
-rw-r--r--crates/ra_db/src/fixture.rs6
-rw-r--r--crates/ra_db/src/input.rs12
-rw-r--r--crates/ra_hir/Cargo.toml1
-rw-r--r--crates/ra_hir/src/code_model.rs56
-rw-r--r--crates/ra_hir/src/semantics.rs8
-rw-r--r--crates/ra_hir_def/src/body.rs12
-rw-r--r--crates/ra_hir_def/src/body/lower.rs48
-rw-r--r--crates/ra_hir_def/src/data.rs28
-rw-r--r--crates/ra_hir_def/src/nameres.rs9
-rw-r--r--crates/ra_hir_def/src/nameres/collector.rs48
-rw-r--r--crates/ra_hir_def/src/nameres/tests.rs22
-rw-r--r--crates/ra_hir_def/src/resolver.rs15
-rw-r--r--crates/ra_hir_def/src/visibility.rs33
-rw-r--r--crates/ra_hir_expand/src/lib.rs20
-rw-r--r--crates/ra_hir_ty/src/tests/macros.rs20
-rw-r--r--crates/ra_ide/src/call_info.rs14
-rw-r--r--crates/ra_ide/src/completion/complete_dot.rs32
-rw-r--r--crates/ra_ide/src/completion/complete_path.rs70
-rw-r--r--crates/ra_ide/src/completion/presentation.rs15
-rw-r--r--crates/ra_ide/src/display.rs37
-rw-r--r--crates/ra_ide/src/display/function_signature.rs4
-rw-r--r--crates/ra_ide/src/goto_definition.rs15
-rw-r--r--crates/ra_ide/src/hover.rs64
-rw-r--r--crates/ra_ide/src/lib.rs3
-rw-r--r--crates/ra_ide/src/mock_analysis.rs2
-rw-r--r--crates/ra_ide/src/ssr.rs36
-rw-r--r--crates/ra_ide/src/typing.rs40
-rw-r--r--crates/ra_parser/src/grammar/params.rs64
-rw-r--r--crates/ra_project_model/src/lib.rs7
-rw-r--r--crates/ra_syntax/src/ast/make.rs16
-rw-r--r--crates/ra_syntax/src/tests.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0032_fn_pointer_type.txt3
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.txt47
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0123_param_list_vararg.txt3
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.rs1
-rw-r--r--crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.txt44
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0051_parameter_attrs.txt3
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0063_trait_fn_patterns.rs (renamed from crates/ra_syntax/test_data/parser/inline/ok/0153_trait_fn_patterns.rs)1
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0063_trait_fn_patterns.txt (renamed from crates/ra_syntax/test_data/parser/inline/ok/0153_trait_fn_patterns.txt)47
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.rs5
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.txt133
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0064_impl_fn_params.rs (renamed from crates/ra_syntax/test_data/parser/inline/ok/0152_fn_patterns.rs)0
-rw-r--r--crates/ra_syntax/test_data/parser/ok/0064_impl_fn_params.txt (renamed from crates/ra_syntax/test_data/parser/inline/ok/0152_fn_patterns.txt)0
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs42
-rw-r--r--crates/rust-analyzer/src/config.rs4
-rw-r--r--crates/rust-analyzer/src/main_loop.rs74
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs33
-rw-r--r--crates/rust-analyzer/src/req.rs5
-rw-r--r--crates/rust-analyzer/src/world.rs1
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/main.rs15
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs15
-rw-r--r--crates/test_utils/src/lib.rs3
-rw-r--r--docs/user/readme.adoc19
-rw-r--r--editors/code/package-lock.json2
-rw-r--r--editors/code/package.json17
-rw-r--r--editors/code/src/client.ts1
-rw-r--r--editors/code/src/commands/runnables.ts22
-rw-r--r--editors/code/src/commands/server_version.ts3
-rw-r--r--editors/code/src/commands/ssr.ts16
-rw-r--r--editors/code/src/config.ts117
-rw-r--r--editors/code/src/installation/download_artifact.ts50
-rw-r--r--editors/code/src/installation/downloads.ts (renamed from editors/code/src/installation/download_file.ts)46
-rw-r--r--editors/code/src/installation/extension.ts144
-rw-r--r--editors/code/src/installation/fetch_artifact_release_info.ts3
-rw-r--r--editors/code/src/installation/interfaces.ts18
-rw-r--r--editors/code/src/installation/server.ts104
-rw-r--r--editors/code/src/main.ts10
-rw-r--r--editors/code/src/rust-analyzer-api.ts6
-rw-r--r--editors/code/src/util.ts69
-rw-r--r--xtask/src/lib.rs8
75 files changed, 1437 insertions, 504 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 330bdd1cb..efe8dd189 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,9 +11,9 @@ dependencies = [
11 11
12[[package]] 12[[package]]
13name = "anyhow" 13name = "anyhow"
14version = "1.0.26" 14version = "1.0.27"
15source = "registry+https://github.com/rust-lang/crates.io-index" 15source = "registry+https://github.com/rust-lang/crates.io-index"
16checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" 16checksum = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785"
17 17
18[[package]] 18[[package]]
19name = "anymap" 19name = "anymap"
@@ -58,9 +58,9 @@ dependencies = [
58 58
59[[package]] 59[[package]]
60name = "backtrace-sys" 60name = "backtrace-sys"
61version = "0.1.33" 61version = "0.1.34"
62source = "registry+https://github.com/rust-lang/crates.io-index" 62source = "registry+https://github.com/rust-lang/crates.io-index"
63checksum = "e17b52e737c40a7d75abca20b29a19a0eb7ba9fc72c5a72dd282a0a3c2c0dc35" 63checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69"
64dependencies = [ 64dependencies = [
65 "cc", 65 "cc",
66 "libc", 66 "libc",
@@ -80,9 +80,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
80 80
81[[package]] 81[[package]]
82name = "bstr" 82name = "bstr"
83version = "0.2.11" 83version = "0.2.12"
84source = "registry+https://github.com/rust-lang/crates.io-index" 84source = "registry+https://github.com/rust-lang/crates.io-index"
85checksum = "502ae1441a0a5adb8fbd38a5955a6416b9493e92b465de5e4a9bde6a539c2c48" 85checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41"
86dependencies = [ 86dependencies = [
87 "memchr", 87 "memchr",
88] 88]
@@ -940,6 +940,7 @@ dependencies = [
940name = "ra_hir" 940name = "ra_hir"
941version = "0.1.0" 941version = "0.1.0"
942dependencies = [ 942dependencies = [
943 "arrayvec",
943 "either", 944 "either",
944 "itertools", 945 "itertools",
945 "log", 946 "log",
@@ -1229,9 +1230,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
1229 1230
1230[[package]] 1231[[package]]
1231name = "regex" 1232name = "regex"
1232version = "1.3.4" 1233version = "1.3.5"
1233source = "registry+https://github.com/rust-lang/crates.io-index" 1234source = "registry+https://github.com/rust-lang/crates.io-index"
1234checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" 1235checksum = "8900ebc1363efa7ea1c399ccc32daed870b4002651e0bed86e72d501ebbe0048"
1235dependencies = [ 1236dependencies = [
1236 "aho-corasick", 1237 "aho-corasick",
1237 "memchr", 1238 "memchr",
@@ -1241,9 +1242,9 @@ dependencies = [
1241 1242
1242[[package]] 1243[[package]]
1243name = "regex-syntax" 1244name = "regex-syntax"
1244version = "0.6.16" 1245version = "0.6.17"
1245source = "registry+https://github.com/rust-lang/crates.io-index" 1246source = "registry+https://github.com/rust-lang/crates.io-index"
1246checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" 1247checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
1247 1248
1248[[package]] 1249[[package]]
1249name = "relative-path" 1250name = "relative-path"
@@ -1341,9 +1342,9 @@ dependencies = [
1341 1342
1342[[package]] 1343[[package]]
1343name = "ryu" 1344name = "ryu"
1344version = "1.0.2" 1345version = "1.0.3"
1345source = "registry+https://github.com/rust-lang/crates.io-index" 1346source = "registry+https://github.com/rust-lang/crates.io-index"
1346checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 1347checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76"
1347 1348
1348[[package]] 1349[[package]]
1349name = "salsa" 1350name = "salsa"
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
index 42856f0ca..45558c448 100644
--- a/crates/ra_assists/src/ast_transform.rs
+++ b/crates/ra_assists/src/ast_transform.rs
@@ -37,7 +37,6 @@ pub struct SubstituteTypeParams<'a> {
37impl<'a> SubstituteTypeParams<'a> { 37impl<'a> SubstituteTypeParams<'a> {
38 pub fn for_trait_impl( 38 pub fn for_trait_impl(
39 source_scope: &'a SemanticsScope<'a, RootDatabase>, 39 source_scope: &'a SemanticsScope<'a, RootDatabase>,
40 db: &'a RootDatabase,
41 // FIXME: there's implicit invariant that `trait_` and `source_scope` match... 40 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
42 trait_: hir::Trait, 41 trait_: hir::Trait,
43 impl_def: ast::ImplDef, 42 impl_def: ast::ImplDef,
@@ -45,7 +44,7 @@ impl<'a> SubstituteTypeParams<'a> {
45 let substs = get_syntactic_substs(impl_def).unwrap_or_default(); 44 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
46 let generic_def: hir::GenericDef = trait_.into(); 45 let generic_def: hir::GenericDef = trait_.into();
47 let substs_by_param: FxHashMap<_, _> = generic_def 46 let substs_by_param: FxHashMap<_, _> = generic_def
48 .params(db) 47 .params(source_scope.db)
49 .into_iter() 48 .into_iter()
50 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky 49 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
51 .skip(1) 50 .skip(1)
@@ -104,7 +103,6 @@ impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
104pub struct QualifyPaths<'a> { 103pub struct QualifyPaths<'a> {
105 target_scope: &'a SemanticsScope<'a, RootDatabase>, 104 target_scope: &'a SemanticsScope<'a, RootDatabase>,
106 source_scope: &'a SemanticsScope<'a, RootDatabase>, 105 source_scope: &'a SemanticsScope<'a, RootDatabase>,
107 db: &'a RootDatabase,
108 previous: Box<dyn AstTransform<'a> + 'a>, 106 previous: Box<dyn AstTransform<'a> + 'a>,
109} 107}
110 108
@@ -112,9 +110,8 @@ impl<'a> QualifyPaths<'a> {
112 pub fn new( 110 pub fn new(
113 target_scope: &'a SemanticsScope<'a, RootDatabase>, 111 target_scope: &'a SemanticsScope<'a, RootDatabase>,
114 source_scope: &'a SemanticsScope<'a, RootDatabase>, 112 source_scope: &'a SemanticsScope<'a, RootDatabase>,
115 db: &'a RootDatabase,
116 ) -> Self { 113 ) -> Self {
117 Self { target_scope, source_scope, db, previous: Box::new(NullTransformer) } 114 Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
118 } 115 }
119 116
120 fn get_substitution_inner( 117 fn get_substitution_inner(
@@ -132,7 +129,7 @@ impl<'a> QualifyPaths<'a> {
132 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; 129 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
133 match resolution { 130 match resolution {
134 PathResolution::Def(def) => { 131 PathResolution::Def(def) => {
135 let found_path = from.find_use_path(self.db, def)?; 132 let found_path = from.find_use_path(self.source_scope.db, def)?;
136 let mut path = path_to_ast(found_path); 133 let mut path = path_to_ast(found_path);
137 134
138 let type_args = p 135 let type_args = p
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
index 639180d37..e5920b6f6 100644
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
@@ -142,8 +142,8 @@ fn add_missing_impl_members_inner(
142 let n_existing_items = impl_item_list.impl_items().count(); 142 let n_existing_items = impl_item_list.impl_items().count();
143 let source_scope = sema.scope_for_def(trait_); 143 let source_scope = sema.scope_for_def(trait_);
144 let target_scope = sema.scope(impl_item_list.syntax()); 144 let target_scope = sema.scope(impl_item_list.syntax());
145 let ast_transform = QualifyPaths::new(&target_scope, &source_scope, sema.db) 145 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
146 .or(SubstituteTypeParams::for_trait_impl(&source_scope, sema.db, trait_, impl_node)); 146 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_node));
147 let items = missing_items 147 let items = missing_items
148 .into_iter() 148 .into_iter()
149 .map(|it| ast_transform::apply(&*ast_transform, it)) 149 .map(|it| ast_transform::apply(&*ast_transform, it))
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index e5d8c639d..97cf90ae4 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -2,7 +2,7 @@
2 2
3use std::iter; 3use std::iter;
4 4
5use hir::{db::HirDatabase, Adt, HasSource, Semantics}; 5use hir::{Adt, HasSource, Semantics};
6use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner}; 6use ra_syntax::ast::{self, edit::IndentLevel, make, AstNode, NameOwner};
7 7
8use crate::{Assist, AssistCtx, AssistId}; 8use crate::{Assist, AssistCtx, AssistId};
@@ -88,11 +88,7 @@ fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<
88 }) 88 })
89} 89}
90 90
91fn build_pat( 91fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
92 db: &impl HirDatabase,
93 module: hir::Module,
94 var: hir::EnumVariant,
95) -> Option<ast::Pat> {
96 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?); 92 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, var.into())?);
97 93
98 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though 94 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index 3dc86ca2d..3464f43df 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -64,7 +64,9 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId
64 crate_graph.add_crate_root( 64 crate_graph.add_crate_root(
65 file_id, 65 file_id,
66 meta.edition, 66 meta.edition,
67 meta.krate, 67 meta.krate.map(|name| {
68 CrateName::new(&name).expect("Fixture crate name should not contain dashes")
69 }),
68 meta.cfg, 70 meta.cfg,
69 meta.env, 71 meta.env,
70 Default::default(), 72 Default::default(),
@@ -124,7 +126,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
124 let crate_id = crate_graph.add_crate_root( 126 let crate_id = crate_graph.add_crate_root(
125 file_id, 127 file_id,
126 meta.edition, 128 meta.edition,
127 Some(krate.clone()), 129 Some(CrateName::new(&krate).unwrap()),
128 meta.cfg, 130 meta.cfg,
129 meta.env, 131 meta.env,
130 Default::default(), 132 Default::default(),
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs
index 06d40db96..bde843001 100644
--- a/crates/ra_db/src/input.rs
+++ b/crates/ra_db/src/input.rs
@@ -14,6 +14,7 @@ use rustc_hash::FxHashMap;
14use rustc_hash::FxHashSet; 14use rustc_hash::FxHashSet;
15 15
16use crate::{RelativePath, RelativePathBuf}; 16use crate::{RelativePath, RelativePathBuf};
17use fmt::Display;
17 18
18/// `FileId` is an integer which uniquely identifies a file. File paths are 19/// `FileId` is an integer which uniquely identifies a file. File paths are
19/// messy and system-dependent, so most of the code should work directly with 20/// messy and system-dependent, so most of the code should work directly with
@@ -83,6 +84,7 @@ pub struct CrateGraph {
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 84#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
84pub struct CrateId(pub u32); 85pub struct CrateId(pub u32);
85 86
87#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct CrateName(SmolStr); 88pub struct CrateName(SmolStr);
87 89
88impl CrateName { 90impl CrateName {
@@ -103,6 +105,12 @@ impl CrateName {
103 } 105 }
104} 106}
105 107
108impl Display for CrateName {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 write!(f, "{}", self.0)
111 }
112}
113
106#[derive(Debug, Clone, PartialEq, Eq)] 114#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct CrateData { 115pub struct CrateData {
108 pub root_file_id: FileId, 116 pub root_file_id: FileId,
@@ -110,7 +118,7 @@ pub struct CrateData {
110 /// The name to display to the end user. 118 /// The name to display to the end user.
111 /// This actual crate name can be different in a particular dependent crate 119 /// This actual crate name can be different in a particular dependent crate
112 /// or may even be missing for some cases, such as a dummy crate for the code snippet. 120 /// or may even be missing for some cases, such as a dummy crate for the code snippet.
113 pub display_name: Option<String>, 121 pub display_name: Option<CrateName>,
114 pub cfg_options: CfgOptions, 122 pub cfg_options: CfgOptions,
115 pub env: Env, 123 pub env: Env,
116 pub extern_source: ExternSource, 124 pub extern_source: ExternSource,
@@ -150,7 +158,7 @@ impl CrateGraph {
150 &mut self, 158 &mut self,
151 file_id: FileId, 159 file_id: FileId,
152 edition: Edition, 160 edition: Edition,
153 display_name: Option<String>, 161 display_name: Option<CrateName>,
154 cfg_options: CfgOptions, 162 cfg_options: CfgOptions,
155 env: Env, 163 env: Env,
156 extern_source: ExternSource, 164 extern_source: ExternSource,
diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml
index 266c4cff3..42193b492 100644
--- a/crates/ra_hir/Cargo.toml
+++ b/crates/ra_hir/Cargo.toml
@@ -11,6 +11,7 @@ doctest = false
11log = "0.4.8" 11log = "0.4.8"
12rustc-hash = "1.1.0" 12rustc-hash = "1.1.0"
13either = "1.5.3" 13either = "1.5.3"
14arrayvec = "0.5.1"
14 15
15itertools = "0.8.2" 16itertools = "0.8.2"
16 17
diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs
index 41d4e2ed3..ff041150b 100644
--- a/crates/ra_hir/src/code_model.rs
+++ b/crates/ra_hir/src/code_model.rs
@@ -1,6 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::sync::Arc; 2use std::sync::Arc;
3 3
4use arrayvec::ArrayVec;
4use either::Either; 5use either::Either;
5use hir_def::{ 6use hir_def::{
6 adt::StructKind, 7 adt::StructKind,
@@ -226,7 +227,9 @@ impl Module {
226 Some((name, def)) 227 Some((name, def))
227 } 228 }
228 }) 229 })
229 .map(|(name, def)| (name.clone(), def.into())) 230 .flat_map(|(name, def)| {
231 ScopeDef::all_items(def).into_iter().map(move |item| (name.clone(), item))
232 })
230 .collect() 233 .collect()
231 } 234 }
232 235
@@ -308,7 +311,11 @@ impl StructField {
308 self.parent.variant_data(db).fields()[self.id].name.clone() 311 self.parent.variant_data(db).fields()[self.id].name.clone()
309 } 312 }
310 313
311 pub fn ty(&self, db: &impl HirDatabase) -> Type { 314 /// Returns the type as in the signature of the struct (i.e., with
315 /// placeholder types for type parameters). This is good for showing
316 /// signature help, but not so good to actually get the type of the field
317 /// when you actually have a variable of the struct.
318 pub fn signature_ty(&self, db: &impl HirDatabase) -> Type {
312 let var_id = self.parent.into(); 319 let var_id = self.parent.into();
313 let generic_def_id: GenericDefId = match self.parent { 320 let generic_def_id: GenericDefId = match self.parent {
314 VariantDef::Struct(it) => it.id.into(), 321 VariantDef::Struct(it) => it.id.into(),
@@ -482,6 +489,10 @@ impl Adt {
482 let subst = db.generic_defaults(self.into()); 489 let subst = db.generic_defaults(self.into());
483 subst.iter().any(|ty| ty == &Ty::Unknown) 490 subst.iter().any(|ty| ty == &Ty::Unknown)
484 } 491 }
492
493 /// Turns this ADT into a type. Any type parameters of the ADT will be
494 /// turned into unknown types, which is good for e.g. finding the most
495 /// general set of completions, but will not look very nice when printed.
485 pub fn ty(self, db: &impl HirDatabase) -> Type { 496 pub fn ty(self, db: &impl HirDatabase) -> Type {
486 let id = AdtId::from(self); 497 let id = AdtId::from(self);
487 Type::from_def(db, id.module(db).krate, id) 498 Type::from_def(db, id.module(db).krate, id)
@@ -1028,7 +1039,7 @@ impl Type {
1028 krate: CrateId, 1039 krate: CrateId,
1029 def: impl HasResolver + Into<TyDefId> + Into<GenericDefId>, 1040 def: impl HasResolver + Into<TyDefId> + Into<GenericDefId>,
1030 ) -> Type { 1041 ) -> Type {
1031 let substs = Substs::type_params(db, def); 1042 let substs = Substs::build_for_def(db, def).fill_with_unknown().build();
1032 let ty = db.ty(def.into()).subst(&substs); 1043 let ty = db.ty(def.into()).subst(&substs);
1033 Type::new(db, krate, def, ty) 1044 Type::new(db, krate, def, ty)
1034 } 1045 }
@@ -1288,15 +1299,36 @@ pub enum ScopeDef {
1288 Unknown, 1299 Unknown,
1289} 1300}
1290 1301
1291impl From<PerNs> for ScopeDef { 1302impl ScopeDef {
1292 fn from(def: PerNs) -> Self { 1303 pub fn all_items(def: PerNs) -> ArrayVec<[Self; 3]> {
1293 def.take_types() 1304 let mut items = ArrayVec::new();
1294 .or_else(|| def.take_values()) 1305
1295 .map(|module_def_id| ScopeDef::ModuleDef(module_def_id.into())) 1306 match (def.take_types(), def.take_values()) {
1296 .or_else(|| { 1307 (Some(m1), None) => items.push(ScopeDef::ModuleDef(m1.into())),
1297 def.take_macros().map(|macro_def_id| ScopeDef::MacroDef(macro_def_id.into())) 1308 (None, Some(m2)) => items.push(ScopeDef::ModuleDef(m2.into())),
1298 }) 1309 (Some(m1), Some(m2)) => {
1299 .unwrap_or(ScopeDef::Unknown) 1310 // Some items, like unit structs and enum variants, are
1311 // returned as both a type and a value. Here we want
1312 // to de-duplicate them.
1313 if m1 != m2 {
1314 items.push(ScopeDef::ModuleDef(m1.into()));
1315 items.push(ScopeDef::ModuleDef(m2.into()));
1316 } else {
1317 items.push(ScopeDef::ModuleDef(m1.into()));
1318 }
1319 }
1320 (None, None) => {}
1321 };
1322
1323 if let Some(macro_def_id) = def.take_macros() {
1324 items.push(ScopeDef::MacroDef(macro_def_id.into()));
1325 }
1326
1327 if items.is_empty() {
1328 items.push(ScopeDef::Unknown);
1329 }
1330
1331 items
1300 } 1332 }
1301} 1333}
1302 1334
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 3782a9984..788bb3eb7 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -344,7 +344,13 @@ impl<'a, DB: HirDatabase> SemanticsScope<'a, DB> {
344 344
345 resolver.process_all_names(self.db, &mut |name, def| { 345 resolver.process_all_names(self.db, &mut |name, def| {
346 let def = match def { 346 let def = match def {
347 resolver::ScopeDef::PerNs(it) => it.into(), 347 resolver::ScopeDef::PerNs(it) => {
348 let items = ScopeDef::all_items(it);
349 for item in items {
350 f(name.clone(), item);
351 }
352 return;
353 }
348 resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()), 354 resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
349 resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()), 355 resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
350 resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(TypeParam { id }), 356 resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(TypeParam { id }),
diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs
index 57ba45b45..2bc405a59 100644
--- a/crates/ra_hir_def/src/body.rs
+++ b/crates/ra_hir_def/src/body.rs
@@ -47,13 +47,19 @@ impl Expander {
47 pub(crate) fn enter_expand<T: ast::AstNode, DB: DefDatabase>( 47 pub(crate) fn enter_expand<T: ast::AstNode, DB: DefDatabase>(
48 &mut self, 48 &mut self,
49 db: &DB, 49 db: &DB,
50 local_scope: Option<&ItemScope>,
50 macro_call: ast::MacroCall, 51 macro_call: ast::MacroCall,
51 ) -> Option<(Mark, T)> { 52 ) -> Option<(Mark, T)> {
52 let macro_call = InFile::new(self.current_file_id, &macro_call); 53 let macro_call = InFile::new(self.current_file_id, &macro_call);
53 54
54 if let Some(call_id) = 55 if let Some(call_id) = macro_call.as_call_id(db, |path| {
55 macro_call.as_call_id(db, |path| self.resolve_path_as_macro(db, &path)) 56 if let Some(local_scope) = local_scope {
56 { 57 if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) {
58 return Some(def);
59 }
60 }
61 self.resolve_path_as_macro(db, &path)
62 }) {
57 let file_id = call_id.as_file(); 63 let file_id = call_id.as_file();
58 if let Some(node) = db.parse_or_expand(file_id) { 64 if let Some(node) = db.parse_or_expand(file_id) {
59 if let Some(expr) = T::cast(node) { 65 if let Some(expr) = T::cast(node) {
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index ec1b0c2e7..54b5591d3 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -3,7 +3,10 @@
3 3
4use either::Either; 4use either::Either;
5 5
6use hir_expand::name::{name, AsName, Name}; 6use hir_expand::{
7 name::{name, AsName, Name},
8 MacroDefId, MacroDefKind,
9};
7use ra_arena::Arena; 10use ra_arena::Arena;
8use ra_syntax::{ 11use ra_syntax::{
9 ast::{ 12 ast::{
@@ -452,19 +455,30 @@ where
452 None => self.alloc_expr(Expr::Missing, syntax_ptr), 455 None => self.alloc_expr(Expr::Missing, syntax_ptr),
453 } 456 }
454 } 457 }
455 // FIXME expand to statements in statement position
456 ast::Expr::MacroCall(e) => { 458 ast::Expr::MacroCall(e) => {
457 let macro_call = self.expander.to_source(AstPtr::new(&e)); 459 if let Some(name) = is_macro_rules(&e) {
458 match self.expander.enter_expand(self.db, e) { 460 let mac = MacroDefId {
459 Some((mark, expansion)) => { 461 krate: Some(self.expander.module.krate),
460 self.source_map 462 ast_id: Some(self.expander.ast_id(&e)),
461 .expansions 463 kind: MacroDefKind::Declarative,
462 .insert(macro_call, self.expander.current_file_id); 464 };
463 let id = self.collect_expr(expansion); 465 self.body.item_scope.define_legacy_macro(name, mac);
464 self.expander.exit(self.db, mark); 466
465 id 467 // FIXME: do we still need to allocate this as missing ?
468 self.alloc_expr(Expr::Missing, syntax_ptr)
469 } else {
470 let macro_call = self.expander.to_source(AstPtr::new(&e));
471 match self.expander.enter_expand(self.db, Some(&self.body.item_scope), e) {
472 Some((mark, expansion)) => {
473 self.source_map
474 .expansions
475 .insert(macro_call, self.expander.current_file_id);
476 let id = self.collect_expr(expansion);
477 self.expander.exit(self.db, mark);
478 id
479 }
480 None => self.alloc_expr(Expr::Missing, syntax_ptr),
466 } 481 }
467 None => self.alloc_expr(Expr::Missing, syntax_ptr),
468 } 482 }
469 } 483 }
470 484
@@ -686,6 +700,16 @@ where
686 } 700 }
687} 701}
688 702
703fn is_macro_rules(m: &ast::MacroCall) -> Option<Name> {
704 let name = m.path()?.segment()?.name_ref()?.as_name();
705
706 if name == name![macro_rules] {
707 Some(m.name()?.as_name())
708 } else {
709 None
710 }
711}
712
689impl From<ast::BinOp> for BinaryOp { 713impl From<ast::BinOp> for BinaryOp {
690 fn from(ast_op: ast::BinOp) -> Self { 714 fn from(ast_op: ast::BinOp) -> Self {
691 match ast_op { 715 match ast_op {
diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs
index a72eb5369..c0b16b7fa 100644
--- a/crates/ra_hir_def/src/data.rs
+++ b/crates/ra_hir_def/src/data.rs
@@ -34,7 +34,8 @@ pub struct FunctionData {
34 34
35impl FunctionData { 35impl FunctionData {
36 pub(crate) fn fn_data_query(db: &impl DefDatabase, func: FunctionId) -> Arc<FunctionData> { 36 pub(crate) fn fn_data_query(db: &impl DefDatabase, func: FunctionId) -> Arc<FunctionData> {
37 let src = func.lookup(db).source(db); 37 let loc = func.lookup(db);
38 let src = loc.source(db);
38 let name = src.value.name().map(|n| n.as_name()).unwrap_or_else(Name::missing); 39 let name = src.value.name().map(|n| n.as_name()).unwrap_or_else(Name::missing);
39 let mut params = Vec::new(); 40 let mut params = Vec::new();
40 let mut has_self_param = false; 41 let mut has_self_param = false;
@@ -76,7 +77,9 @@ impl FunctionData {
76 ret_type 77 ret_type
77 }; 78 };
78 79
79 let visibility = RawVisibility::from_ast(db, src.map(|s| s.visibility())); 80 let vis_default = RawVisibility::default_for_container(loc.container);
81 let visibility =
82 RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility()));
80 83
81 let sig = FunctionData { name, params, ret_type, has_self_param, visibility }; 84 let sig = FunctionData { name, params, ret_type, has_self_param, visibility };
82 Arc::new(sig) 85 Arc::new(sig)
@@ -105,10 +108,13 @@ impl TypeAliasData {
105 db: &impl DefDatabase, 108 db: &impl DefDatabase,
106 typ: TypeAliasId, 109 typ: TypeAliasId,
107 ) -> Arc<TypeAliasData> { 110 ) -> Arc<TypeAliasData> {
108 let node = typ.lookup(db).source(db); 111 let loc = typ.lookup(db);
112 let node = loc.source(db);
109 let name = node.value.name().map_or_else(Name::missing, |n| n.as_name()); 113 let name = node.value.name().map_or_else(Name::missing, |n| n.as_name());
110 let type_ref = node.value.type_ref().map(TypeRef::from_ast); 114 let type_ref = node.value.type_ref().map(TypeRef::from_ast);
111 let visibility = RawVisibility::from_ast(db, node.map(|n| n.visibility())); 115 let vis_default = RawVisibility::default_for_container(loc.container);
116 let visibility =
117 RawVisibility::from_ast_with_default(db, vis_default, node.map(|n| n.visibility()));
112 Arc::new(TypeAliasData { name, type_ref, visibility }) 118 Arc::new(TypeAliasData { name, type_ref, visibility })
113 } 119 }
114} 120}
@@ -230,22 +236,26 @@ pub struct ConstData {
230 236
231impl ConstData { 237impl ConstData {
232 pub(crate) fn const_data_query(db: &impl DefDatabase, konst: ConstId) -> Arc<ConstData> { 238 pub(crate) fn const_data_query(db: &impl DefDatabase, konst: ConstId) -> Arc<ConstData> {
233 let node = konst.lookup(db).source(db); 239 let loc = konst.lookup(db);
234 Arc::new(ConstData::new(db, node)) 240 let node = loc.source(db);
241 let vis_default = RawVisibility::default_for_container(loc.container);
242 Arc::new(ConstData::new(db, vis_default, node))
235 } 243 }
236 244
237 pub(crate) fn static_data_query(db: &impl DefDatabase, konst: StaticId) -> Arc<ConstData> { 245 pub(crate) fn static_data_query(db: &impl DefDatabase, konst: StaticId) -> Arc<ConstData> {
238 let node = konst.lookup(db).source(db); 246 let node = konst.lookup(db).source(db);
239 Arc::new(ConstData::new(db, node)) 247 Arc::new(ConstData::new(db, RawVisibility::private(), node))
240 } 248 }
241 249
242 fn new<N: NameOwner + TypeAscriptionOwner + VisibilityOwner>( 250 fn new<N: NameOwner + TypeAscriptionOwner + VisibilityOwner>(
243 db: &impl DefDatabase, 251 db: &impl DefDatabase,
252 vis_default: RawVisibility,
244 node: InFile<N>, 253 node: InFile<N>,
245 ) -> ConstData { 254 ) -> ConstData {
246 let name = node.value.name().map(|n| n.as_name()); 255 let name = node.value.name().map(|n| n.as_name());
247 let type_ref = TypeRef::from_ast_opt(node.value.ascribed_type()); 256 let type_ref = TypeRef::from_ast_opt(node.value.ascribed_type());
248 let visibility = RawVisibility::from_ast(db, node.map(|n| n.visibility())); 257 let visibility =
258 RawVisibility::from_ast_with_default(db, vis_default, node.map(|n| n.visibility()));
249 ConstData { name, type_ref, visibility } 259 ConstData { name, type_ref, visibility }
250 } 260 }
251} 261}
@@ -280,7 +290,7 @@ fn collect_impl_items_in_macro(
280 return Vec::new(); 290 return Vec::new();
281 } 291 }
282 292
283 if let Some((mark, items)) = expander.enter_expand(db, m) { 293 if let Some((mark, items)) = expander.enter_expand(db, None, m) {
284 let items: InFile<ast::MacroItems> = expander.to_source(items); 294 let items: InFile<ast::MacroItems> = expander.to_source(items);
285 let mut res = collect_impl_items( 295 let mut res = collect_impl_items(
286 db, 296 db,
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs
index 81eac52ad..03515309e 100644
--- a/crates/ra_hir_def/src/nameres.rs
+++ b/crates/ra_hir_def/src/nameres.rs
@@ -177,8 +177,13 @@ pub struct ModuleData {
177 177
178impl CrateDefMap { 178impl CrateDefMap {
179 pub(crate) fn crate_def_map_query(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> { 179 pub(crate) fn crate_def_map_query(db: &impl DefDatabase, krate: CrateId) -> Arc<CrateDefMap> {
180 let _p = profile("crate_def_map_query") 180 let _p = profile("crate_def_map_query").detail(|| {
181 .detail(|| db.crate_graph()[krate].display_name.clone().unwrap_or_default()); 181 db.crate_graph()[krate]
182 .display_name
183 .as_ref()
184 .map(ToString::to_string)
185 .unwrap_or_default()
186 });
182 let def_map = { 187 let def_map = {
183 let edition = db.crate_graph()[krate].edition; 188 let edition = db.crate_graph()[krate].edition;
184 let mut modules: Arena<LocalModuleId, ModuleData> = Arena::default(); 189 let mut modules: Arena<LocalModuleId, ModuleData> = Arena::default();
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs
index d0459d9b0..db9838cb5 100644
--- a/crates/ra_hir_def/src/nameres/collector.rs
+++ b/crates/ra_hir_def/src/nameres/collector.rs
@@ -102,6 +102,7 @@ struct MacroDirective {
102 module_id: LocalModuleId, 102 module_id: LocalModuleId,
103 ast_id: AstIdWithPath<ast::MacroCall>, 103 ast_id: AstIdWithPath<ast::MacroCall>,
104 legacy: Option<MacroCallId>, 104 legacy: Option<MacroCallId>,
105 depth: usize,
105} 106}
106 107
107#[derive(Clone, Debug, Eq, PartialEq)] 108#[derive(Clone, Debug, Eq, PartialEq)]
@@ -134,6 +135,7 @@ where
134 self.def_map.modules[module_id].origin = ModuleOrigin::CrateRoot { definition: file_id }; 135 self.def_map.modules[module_id].origin = ModuleOrigin::CrateRoot { definition: file_id };
135 ModCollector { 136 ModCollector {
136 def_collector: &mut *self, 137 def_collector: &mut *self,
138 macro_depth: 0,
137 module_id, 139 module_id,
138 file_id: file_id.into(), 140 file_id: file_id.into(),
139 raw_items: &raw_items, 141 raw_items: &raw_items,
@@ -516,7 +518,7 @@ where
516 macros.retain(|directive| { 518 macros.retain(|directive| {
517 if let Some(call_id) = directive.legacy { 519 if let Some(call_id) = directive.legacy {
518 res = ReachedFixedPoint::No; 520 res = ReachedFixedPoint::No;
519 resolved.push((directive.module_id, call_id)); 521 resolved.push((directive.module_id, call_id, directive.depth));
520 return false; 522 return false;
521 } 523 }
522 524
@@ -530,7 +532,7 @@ where
530 ); 532 );
531 resolved_res.resolved_def.take_macros() 533 resolved_res.resolved_def.take_macros()
532 }) { 534 }) {
533 resolved.push((directive.module_id, call_id)); 535 resolved.push((directive.module_id, call_id, directive.depth));
534 res = ReachedFixedPoint::No; 536 res = ReachedFixedPoint::No;
535 return false; 537 return false;
536 } 538 }
@@ -541,7 +543,7 @@ where
541 if let Some(call_id) = 543 if let Some(call_id) =
542 directive.ast_id.as_call_id(self.db, |path| self.resolve_attribute_macro(&path)) 544 directive.ast_id.as_call_id(self.db, |path| self.resolve_attribute_macro(&path))
543 { 545 {
544 resolved.push((directive.module_id, call_id)); 546 resolved.push((directive.module_id, call_id, 0));
545 res = ReachedFixedPoint::No; 547 res = ReachedFixedPoint::No;
546 return false; 548 return false;
547 } 549 }
@@ -552,8 +554,12 @@ where
552 self.unexpanded_macros = macros; 554 self.unexpanded_macros = macros;
553 self.unexpanded_attribute_macros = attribute_macros; 555 self.unexpanded_attribute_macros = attribute_macros;
554 556
555 for (module_id, macro_call_id) in resolved { 557 for (module_id, macro_call_id, depth) in resolved {
556 self.collect_macro_expansion(module_id, macro_call_id); 558 if depth > 1024 {
559 log::debug!("Max macro expansion depth reached");
560 continue;
561 }
562 self.collect_macro_expansion(module_id, macro_call_id, depth);
557 } 563 }
558 564
559 res 565 res
@@ -573,12 +579,18 @@ where
573 None 579 None
574 } 580 }
575 581
576 fn collect_macro_expansion(&mut self, module_id: LocalModuleId, macro_call_id: MacroCallId) { 582 fn collect_macro_expansion(
583 &mut self,
584 module_id: LocalModuleId,
585 macro_call_id: MacroCallId,
586 depth: usize,
587 ) {
577 let file_id: HirFileId = macro_call_id.as_file(); 588 let file_id: HirFileId = macro_call_id.as_file();
578 let raw_items = self.db.raw_items(file_id); 589 let raw_items = self.db.raw_items(file_id);
579 let mod_dir = self.mod_dirs[&module_id].clone(); 590 let mod_dir = self.mod_dirs[&module_id].clone();
580 ModCollector { 591 ModCollector {
581 def_collector: &mut *self, 592 def_collector: &mut *self,
593 macro_depth: depth,
582 file_id, 594 file_id,
583 module_id, 595 module_id,
584 raw_items: &raw_items, 596 raw_items: &raw_items,
@@ -595,6 +607,7 @@ where
595/// Walks a single module, populating defs, imports and macros 607/// Walks a single module, populating defs, imports and macros
596struct ModCollector<'a, D> { 608struct ModCollector<'a, D> {
597 def_collector: D, 609 def_collector: D,
610 macro_depth: usize,
598 module_id: LocalModuleId, 611 module_id: LocalModuleId,
599 file_id: HirFileId, 612 file_id: HirFileId,
600 raw_items: &'a raw::RawItems, 613 raw_items: &'a raw::RawItems,
@@ -684,6 +697,7 @@ where
684 697
685 ModCollector { 698 ModCollector {
686 def_collector: &mut *self.def_collector, 699 def_collector: &mut *self.def_collector,
700 macro_depth: self.macro_depth,
687 module_id, 701 module_id,
688 file_id: self.file_id, 702 file_id: self.file_id,
689 raw_items: self.raw_items, 703 raw_items: self.raw_items,
@@ -713,6 +727,7 @@ where
713 let raw_items = self.def_collector.db.raw_items(file_id.into()); 727 let raw_items = self.def_collector.db.raw_items(file_id.into());
714 ModCollector { 728 ModCollector {
715 def_collector: &mut *self.def_collector, 729 def_collector: &mut *self.def_collector,
730 macro_depth: self.macro_depth,
716 module_id, 731 module_id,
717 file_id: file_id.into(), 732 file_id: file_id.into(),
718 raw_items: &raw_items, 733 raw_items: &raw_items,
@@ -887,6 +902,7 @@ where
887 module_id: self.module_id, 902 module_id: self.module_id,
888 ast_id, 903 ast_id,
889 legacy: Some(macro_call_id), 904 legacy: Some(macro_call_id),
905 depth: self.macro_depth + 1,
890 }); 906 });
891 907
892 return; 908 return;
@@ -902,6 +918,7 @@ where
902 module_id: self.module_id, 918 module_id: self.module_id,
903 ast_id, 919 ast_id,
904 legacy: None, 920 legacy: None,
921 depth: self.macro_depth + 1,
905 }); 922 });
906 } 923 }
907 924
@@ -971,13 +988,26 @@ mod tests {
971 } 988 }
972 989
973 #[test] 990 #[test]
974 fn test_macro_expand_will_stop() { 991 fn test_macro_expand_will_stop_1() {
992 do_resolve(
993 r#"
994 macro_rules! foo {
995 ($($ty:ty)*) => { foo!($($ty)*); }
996 }
997 foo!(KABOOM);
998 "#,
999 );
1000 }
1001
1002 #[ignore] // this test does succeed, but takes quite a while :/
1003 #[test]
1004 fn test_macro_expand_will_stop_2() {
975 do_resolve( 1005 do_resolve(
976 r#" 1006 r#"
977 macro_rules! foo { 1007 macro_rules! foo {
978 ($($ty:ty)*) => { foo!($($ty)*, $($ty)*); } 1008 ($($ty:ty)*) => { foo!($($ty)* $($ty)*); }
979 } 1009 }
980foo!(KABOOM); 1010 foo!(KABOOM);
981 "#, 1011 "#,
982 ); 1012 );
983 } 1013 }
diff --git a/crates/ra_hir_def/src/nameres/tests.rs b/crates/ra_hir_def/src/nameres/tests.rs
index dda5ed699..3f33a75b9 100644
--- a/crates/ra_hir_def/src/nameres/tests.rs
+++ b/crates/ra_hir_def/src/nameres/tests.rs
@@ -102,6 +102,28 @@ fn crate_def_map_super_super() {
102} 102}
103 103
104#[test] 104#[test]
105fn crate_def_map_fn_mod_same_name() {
106 let map = def_map(
107 "
108 //- /lib.rs
109 mod m {
110 pub mod z {}
111 pub fn z() {}
112 }
113 ",
114 );
115 assert_snapshot!(map, @r###"
116 â‹®crate
117 â‹®m: t
118 â‹®
119 â‹®crate::m
120 â‹®z: t v
121 â‹®
122 â‹®crate::m::z
123 "###)
124}
125
126#[test]
105fn bogus_paths() { 127fn bogus_paths() {
106 covers!(bogus_paths); 128 covers!(bogus_paths);
107 let map = def_map( 129 let map = def_map(
diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs
index 2734d51a0..123fae72a 100644
--- a/crates/ra_hir_def/src/resolver.rs
+++ b/crates/ra_hir_def/src/resolver.rs
@@ -381,6 +381,11 @@ impl Resolver {
381 db: &impl DefDatabase, 381 db: &impl DefDatabase,
382 path: &ModPath, 382 path: &ModPath,
383 ) -> Option<MacroDefId> { 383 ) -> Option<MacroDefId> {
384 // Search item scope legacy macro first
385 if let Some(def) = self.resolve_local_macro_def(path) {
386 return Some(def);
387 }
388
384 let (item_map, module) = self.module_scope()?; 389 let (item_map, module) = self.module_scope()?;
385 item_map.resolve_path(db, module, &path, BuiltinShadowMode::Other).0.take_macros() 390 item_map.resolve_path(db, module, &path, BuiltinShadowMode::Other).0.take_macros()
386 } 391 }
@@ -413,6 +418,16 @@ impl Resolver {
413 }) 418 })
414 } 419 }
415 420
421 fn resolve_local_macro_def(&self, path: &ModPath) -> Option<MacroDefId> {
422 let name = path.as_ident()?;
423 self.scopes.iter().rev().find_map(|scope| {
424 if let Scope::LocalItemsScope(body) = scope {
425 return body.item_scope.get_legacy_macro(name);
426 }
427 None
428 })
429 }
430
416 pub fn module(&self) -> Option<ModuleId> { 431 pub fn module(&self) -> Option<ModuleId> {
417 let (def_map, local_id) = self.module_scope()?; 432 let (def_map, local_id) = self.module_scope()?;
418 Some(ModuleId { krate: def_map.krate, local_id }) 433 Some(ModuleId { krate: def_map.krate, local_id })
diff --git a/crates/ra_hir_def/src/visibility.rs b/crates/ra_hir_def/src/visibility.rs
index d8296da4b..e0c59e905 100644
--- a/crates/ra_hir_def/src/visibility.rs
+++ b/crates/ra_hir_def/src/visibility.rs
@@ -6,7 +6,7 @@ use ra_syntax::ast;
6use crate::{ 6use crate::{
7 db::DefDatabase, 7 db::DefDatabase,
8 path::{ModPath, PathKind}, 8 path::{ModPath, PathKind},
9 ModuleId, 9 AssocContainerId, ModuleId,
10}; 10};
11 11
12/// Visibility of an item, not yet resolved. 12/// Visibility of an item, not yet resolved.
@@ -20,11 +20,30 @@ pub enum RawVisibility {
20} 20}
21 21
22impl RawVisibility { 22impl RawVisibility {
23 const fn private() -> RawVisibility { 23 pub(crate) const fn private() -> RawVisibility {
24 let path = ModPath { kind: PathKind::Super(0), segments: Vec::new() }; 24 let path = ModPath { kind: PathKind::Super(0), segments: Vec::new() };
25 RawVisibility::Module(path) 25 RawVisibility::Module(path)
26 } 26 }
27 27
28 pub(crate) fn default_for_container(container_id: AssocContainerId) -> Self {
29 match container_id {
30 AssocContainerId::TraitId(_) => RawVisibility::Public,
31 _ => RawVisibility::private(),
32 }
33 }
34
35 pub(crate) fn from_ast_with_default(
36 db: &impl DefDatabase,
37 default: RawVisibility,
38 node: InFile<Option<ast::Visibility>>,
39 ) -> RawVisibility {
40 Self::from_ast_with_hygiene_and_default(
41 node.value,
42 default,
43 &Hygiene::new(db, node.file_id),
44 )
45 }
46
28 pub(crate) fn from_ast( 47 pub(crate) fn from_ast(
29 db: &impl DefDatabase, 48 db: &impl DefDatabase,
30 node: InFile<Option<ast::Visibility>>, 49 node: InFile<Option<ast::Visibility>>,
@@ -36,8 +55,16 @@ impl RawVisibility {
36 node: Option<ast::Visibility>, 55 node: Option<ast::Visibility>,
37 hygiene: &Hygiene, 56 hygiene: &Hygiene,
38 ) -> RawVisibility { 57 ) -> RawVisibility {
58 Self::from_ast_with_hygiene_and_default(node, RawVisibility::private(), hygiene)
59 }
60
61 pub(crate) fn from_ast_with_hygiene_and_default(
62 node: Option<ast::Visibility>,
63 default: RawVisibility,
64 hygiene: &Hygiene,
65 ) -> RawVisibility {
39 let node = match node { 66 let node = match node {
40 None => return RawVisibility::private(), 67 None => return default,
41 Some(node) => node, 68 Some(node) => node,
42 }; 69 };
43 match node.kind() { 70 match node.kind() {
diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs
index 3fce73e8a..7b72eb7a0 100644
--- a/crates/ra_hir_expand/src/lib.rs
+++ b/crates/ra_hir_expand/src/lib.rs
@@ -17,7 +17,7 @@ pub mod eager;
17use std::hash::Hash; 17use std::hash::Hash;
18use std::sync::Arc; 18use std::sync::Arc;
19 19
20use ra_db::{salsa, CrateId, FileId}; 20use ra_db::{impl_intern_key, salsa, CrateId, FileId};
21use ra_syntax::{ 21use ra_syntax::{
22 algo, 22 algo,
23 ast::{self, AstNode}, 23 ast::{self, AstNode},
@@ -174,25 +174,11 @@ pub enum MacroCallId {
174 174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
176pub struct LazyMacroId(salsa::InternId); 176pub struct LazyMacroId(salsa::InternId);
177impl salsa::InternKey for LazyMacroId { 177impl_intern_key!(LazyMacroId);
178 fn from_intern_id(v: salsa::InternId) -> Self {
179 LazyMacroId(v)
180 }
181 fn as_intern_id(&self) -> salsa::InternId {
182 self.0
183 }
184}
185 178
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 179#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
187pub struct EagerMacroId(salsa::InternId); 180pub struct EagerMacroId(salsa::InternId);
188impl salsa::InternKey for EagerMacroId { 181impl_intern_key!(EagerMacroId);
189 fn from_intern_id(v: salsa::InternId) -> Self {
190 EagerMacroId(v)
191 }
192 fn as_intern_id(&self) -> salsa::InternId {
193 self.0
194 }
195}
196 182
197impl From<LazyMacroId> for MacroCallId { 183impl From<LazyMacroId> for MacroCallId {
198 fn from(it: LazyMacroId) -> Self { 184 fn from(it: LazyMacroId) -> Self {
diff --git a/crates/ra_hir_ty/src/tests/macros.rs b/crates/ra_hir_ty/src/tests/macros.rs
index 32457bbf7..3b7022ad5 100644
--- a/crates/ra_hir_ty/src/tests/macros.rs
+++ b/crates/ra_hir_ty/src/tests/macros.rs
@@ -363,6 +363,26 @@ fn main() {
363} 363}
364 364
365#[test] 365#[test]
366fn infer_local_macro() {
367 assert_snapshot!(
368 infer(r#"
369fn main() {
370 macro_rules! foo {
371 () => { 1usize }
372 }
373 let _a = foo!();
374}
375"#),
376 @r###"
377 ![0; 6) '1usize': usize
378 [11; 90) '{ ...!(); }': ()
379 [17; 66) 'macro_... }': {unknown}
380 [75; 77) '_a': usize
381 "###
382 );
383}
384
385#[test]
366fn infer_builtin_macros_line() { 386fn infer_builtin_macros_line() {
367 assert_snapshot!( 387 assert_snapshot!(
368 infer(r#" 388 infer(r#"
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs
index 2b35a3803..39d09a07f 100644
--- a/crates/ra_ide/src/call_info.rs
+++ b/crates/ra_ide/src/call_info.rs
@@ -544,6 +544,20 @@ fn main() {
544 } 544 }
545 545
546 #[test] 546 #[test]
547 fn generic_struct() {
548 let info = call_info(
549 r#"
550struct TS<T>(T);
551fn main() {
552 let s = TS(<|>);
553}"#,
554 );
555
556 assert_eq!(info.label(), "struct TS<T>(T) -> TS");
557 assert_eq!(info.active_parameter, Some(0));
558 }
559
560 #[test]
547 #[should_panic] 561 #[should_panic]
548 fn cant_call_named_structs() { 562 fn cant_call_named_structs() {
549 let _ = call_info( 563 let _ = call_info(
diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs
index 81e5037aa..f07611d88 100644
--- a/crates/ra_ide/src/completion/complete_dot.rs
+++ b/crates/ra_ide/src/completion/complete_dot.rs
@@ -402,6 +402,38 @@ mod tests {
402 } 402 }
403 403
404 #[test] 404 #[test]
405 fn completes_trait_method_from_other_module() {
406 assert_debug_snapshot!(
407 do_ref_completion(
408 r"
409 struct A {}
410 mod m {
411 pub trait Trait { fn the_method(&self); }
412 }
413 use m::Trait;
414 impl Trait for A {}
415 fn foo(a: A) {
416 a.<|>
417 }
418 ",
419 ),
420 @r###"
421 [
422 CompletionItem {
423 label: "the_method()",
424 source_range: [219; 219),
425 delete: [219; 219),
426 insert: "the_method()$0",
427 kind: Method,
428 lookup: "the_method",
429 detail: "fn the_method(&self)",
430 },
431 ]
432 "###
433 );
434 }
435
436 #[test]
405 fn test_no_non_self_method() { 437 fn test_no_non_self_method() {
406 assert_debug_snapshot!( 438 assert_debug_snapshot!(
407 do_ref_completion( 439 do_ref_completion(
diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs
index d588ee364..3db17f15f 100644
--- a/crates/ra_ide/src/completion/complete_path.rs
+++ b/crates/ra_ide/src/completion/complete_path.rs
@@ -967,4 +967,74 @@ mod tests {
967 ] 967 ]
968 "###); 968 "###);
969 } 969 }
970
971 #[test]
972 fn function_mod_share_name() {
973 assert_debug_snapshot!(
974 do_reference_completion(
975 r"
976 fn foo() {
977 self::m::<|>
978 }
979
980 mod m {
981 pub mod z {}
982 pub fn z() {}
983 }
984 ",
985 ),
986 @r###"
987 [
988 CompletionItem {
989 label: "z",
990 source_range: [57; 57),
991 delete: [57; 57),
992 insert: "z",
993 kind: Module,
994 },
995 CompletionItem {
996 label: "z()",
997 source_range: [57; 57),
998 delete: [57; 57),
999 insert: "z()$0",
1000 kind: Function,
1001 lookup: "z",
1002 detail: "pub fn z()",
1003 },
1004 ]
1005 "###
1006 );
1007 }
1008
1009 #[test]
1010 fn completes_hashmap_new() {
1011 assert_debug_snapshot!(
1012 do_reference_completion(
1013 r"
1014 struct RandomState;
1015 struct HashMap<K, V, S = RandomState> {}
1016
1017 impl<K, V> HashMap<K, V, RandomState> {
1018 pub fn new() -> HashMap<K, V, RandomState> { }
1019 }
1020 fn foo() {
1021 HashMap::<|>
1022 }
1023 "
1024 ),
1025 @r###"
1026 [
1027 CompletionItem {
1028 label: "new()",
1029 source_range: [292; 292),
1030 delete: [292; 292),
1031 insert: "new()$0",
1032 kind: Function,
1033 lookup: "new",
1034 detail: "pub fn new() -> HashMap<K, V, RandomState>",
1035 },
1036 ]
1037 "###
1038 );
1039 }
970} 1040}
diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs
index a4e9aefe2..253848602 100644
--- a/crates/ra_ide/src/completion/presentation.rs
+++ b/crates/ra_ide/src/completion/presentation.rs
@@ -1,6 +1,6 @@
1//! This modules takes care of rendering various definitions as completion items. 1//! This modules takes care of rendering various definitions as completion items.
2 2
3use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type}; 3use hir::{Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type};
4use join_to_string::join; 4use join_to_string::join;
5use ra_syntax::ast::NameOwner; 5use ra_syntax::ast::NameOwner;
6use test_utils::tested_by; 6use test_utils::tested_by;
@@ -9,7 +9,10 @@ use crate::completion::{
9 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, 9 CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions,
10}; 10};
11 11
12use crate::display::{const_label, macro_label, type_label, FunctionSignature}; 12use crate::{
13 display::{const_label, macro_label, type_label, FunctionSignature},
14 RootDatabase,
15};
13 16
14impl Completions { 17impl Completions {
15 pub(crate) fn add_field( 18 pub(crate) fn add_field(
@@ -273,8 +276,10 @@ impl Completions {
273 pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) { 276 pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) {
274 let is_deprecated = is_deprecated(variant, ctx.db); 277 let is_deprecated = is_deprecated(variant, ctx.db);
275 let name = variant.name(ctx.db); 278 let name = variant.name(ctx.db);
276 let detail_types = 279 let detail_types = variant
277 variant.fields(ctx.db).into_iter().map(|field| (field.name(ctx.db), field.ty(ctx.db))); 280 .fields(ctx.db)
281 .into_iter()
282 .map(|field| (field.name(ctx.db), field.signature_ty(ctx.db)));
278 let detail = match variant.kind(ctx.db) { 283 let detail = match variant.kind(ctx.db) {
279 StructKind::Tuple | StructKind::Unit => { 284 StructKind::Tuple | StructKind::Unit => {
280 join(detail_types.map(|(_, t)| t.display(ctx.db).to_string())) 285 join(detail_types.map(|(_, t)| t.display(ctx.db).to_string()))
@@ -298,7 +303,7 @@ impl Completions {
298 } 303 }
299} 304}
300 305
301fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool { 306fn is_deprecated(node: impl HasAttrs, db: &RootDatabase) -> bool {
302 node.attrs(db).by_key("deprecated").exists() 307 node.attrs(db).by_key("deprecated").exists()
303} 308}
304 309
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs
index eaeaaa2b4..c395057a7 100644
--- a/crates/ra_ide/src/display.rs
+++ b/crates/ra_ide/src/display.rs
@@ -6,6 +6,8 @@ mod navigation_target;
6mod structure; 6mod structure;
7mod short_label; 7mod short_label;
8 8
9use std::fmt::{Display, Write};
10
9use ra_syntax::{ 11use ra_syntax::{
10 ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, 12 ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner},
11 SyntaxKind::{ATTR, COMMENT}, 13 SyntaxKind::{ATTR, COMMENT},
@@ -67,24 +69,27 @@ pub(crate) fn macro_label(node: &ast::MacroCall) -> String {
67 format!("{}macro_rules! {}", vis, name) 69 format!("{}macro_rules! {}", vis, name)
68} 70}
69 71
70pub(crate) fn rust_code_markup<CODE: AsRef<str>>(val: CODE) -> String { 72pub(crate) fn rust_code_markup(code: &impl Display) -> String {
71 rust_code_markup_with_doc::<_, &str>(val, None, None) 73 rust_code_markup_with_doc(code, None, None)
72} 74}
73 75
74pub(crate) fn rust_code_markup_with_doc<CODE, DOC>( 76pub(crate) fn rust_code_markup_with_doc(
75 val: CODE, 77 code: &impl Display,
76 doc: Option<DOC>, 78 doc: Option<&str>,
77 mod_path: Option<String>, 79 mod_path: Option<&str>,
78) -> String 80) -> String {
79where 81 let mut markup = "```rust\n".to_owned();
80 CODE: AsRef<str>, 82
81 DOC: AsRef<str>, 83 if let Some(mod_path) = mod_path {
82{ 84 if !mod_path.is_empty() {
83 let mod_path = 85 write!(markup, "{}\n", mod_path).unwrap();
84 mod_path.filter(|path| !path.is_empty()).map(|path| path + "\n").unwrap_or_default(); 86 }
87 }
88 write!(markup, "{}\n```", code).unwrap();
89
85 if let Some(doc) = doc { 90 if let Some(doc) = doc {
86 format!("```rust\n{}{}\n```\n\n{}", mod_path, val.as_ref(), doc.as_ref()) 91 write!(markup, "\n\n{}", doc).unwrap();
87 } else {
88 format!("```rust\n{}{}\n```", mod_path, val.as_ref())
89 } 92 }
93
94 markup
90} 95}
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index 2c4c932de..ec1bbd5a0 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -64,7 +64,7 @@ impl FunctionSignature {
64 .fields(db) 64 .fields(db)
65 .into_iter() 65 .into_iter()
66 .map(|field: hir::StructField| { 66 .map(|field: hir::StructField| {
67 let ty = field.ty(db); 67 let ty = field.signature_ty(db);
68 format!("{}", ty.display(db)) 68 format!("{}", ty.display(db))
69 }) 69 })
70 .collect(); 70 .collect();
@@ -102,7 +102,7 @@ impl FunctionSignature {
102 .into_iter() 102 .into_iter()
103 .map(|field: hir::StructField| { 103 .map(|field: hir::StructField| {
104 let name = field.name(db); 104 let name = field.name(db);
105 let ty = field.ty(db); 105 let ty = field.signature_ty(db);
106 format!("{}: {}", name, ty.display(db)) 106 format!("{}: {}", name, ty.display(db))
107 }) 107 })
108 .collect(); 108 .collect();
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs
index a55a13ffc..a7be92ce3 100644
--- a/crates/ra_ide/src/goto_definition.rs
+++ b/crates/ra_ide/src/goto_definition.rs
@@ -788,6 +788,21 @@ mod tests {
788 } 788 }
789 789
790 #[test] 790 #[test]
791 fn goto_def_in_local_macro() {
792 check_goto(
793 "
794 //- /lib.rs
795 fn bar() {
796 macro_rules! foo { () => { () } }
797 <|>foo!();
798 }
799 ",
800 "foo MACRO_CALL FileId(1) [15; 48) [28; 31)",
801 "macro_rules! foo { () => { () } }|foo",
802 );
803 }
804
805 #[test]
791 fn goto_def_for_field_init_shorthand() { 806 fn goto_def_for_field_init_shorthand() {
792 covers!(ra_ide_db::goto_def_for_field_init_shorthand); 807 covers!(ra_ide_db::goto_def_for_field_init_shorthand);
793 check_goto( 808 check_goto(
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 0bbba4855..5b3760c18 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -1,4 +1,5 @@
1//! FIXME: write short doc here 1//! Logic for computing info that is displayed when the user hovers over any
2//! source code items (e.g. function call, struct field, variable symbol...)
2 3
3use hir::{ 4use hir::{
4 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, 5 Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef,
@@ -24,35 +25,20 @@ use itertools::Itertools;
24use std::iter::once; 25use std::iter::once;
25 26
26/// Contains the results when hovering over an item 27/// Contains the results when hovering over an item
27#[derive(Debug, Clone)] 28#[derive(Debug, Default)]
28pub struct HoverResult { 29pub struct HoverResult {
29 results: Vec<String>, 30 results: Vec<String>,
30 exact: bool,
31}
32
33impl Default for HoverResult {
34 fn default() -> Self {
35 HoverResult::new()
36 }
37} 31}
38 32
39impl HoverResult { 33impl HoverResult {
40 pub fn new() -> HoverResult { 34 pub fn new() -> HoverResult {
41 HoverResult { 35 Self::default()
42 results: Vec::new(),
43 // We assume exact by default
44 exact: true,
45 }
46 } 36 }
47 37
48 pub fn extend(&mut self, item: Option<String>) { 38 pub fn extend(&mut self, item: Option<String>) {
49 self.results.extend(item); 39 self.results.extend(item);
50 } 40 }
51 41
52 pub fn is_exact(&self) -> bool {
53 self.exact
54 }
55
56 pub fn is_empty(&self) -> bool { 42 pub fn is_empty(&self) -> bool {
57 self.results.is_empty() 43 self.results.is_empty()
58 } 44 }
@@ -72,20 +58,7 @@ impl HoverResult {
72 /// Returns the results converted into markup 58 /// Returns the results converted into markup
73 /// for displaying in a UI 59 /// for displaying in a UI
74 pub fn to_markup(&self) -> String { 60 pub fn to_markup(&self) -> String {
75 let mut markup = if !self.exact { 61 self.results.join("\n\n---\n")
76 let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits.");
77 if !self.results.is_empty() {
78 msg.push_str(" \nThese items were found instead:");
79 }
80 msg.push_str("\n\n---\n");
81 msg
82 } else {
83 String::new()
84 };
85
86 markup.push_str(&self.results.join("\n\n---\n"));
87
88 markup
89 } 62 }
90} 63}
91 64
@@ -94,10 +67,10 @@ fn hover_text(
94 desc: Option<String>, 67 desc: Option<String>,
95 mod_path: Option<String>, 68 mod_path: Option<String>,
96) -> Option<String> { 69) -> Option<String> {
97 match (desc, docs, mod_path) { 70 if let Some(desc) = desc {
98 (Some(desc), docs, mod_path) => Some(rust_code_markup_with_doc(desc, docs, mod_path)), 71 Some(rust_code_markup_with_doc(&desc, docs.as_deref(), mod_path.as_deref()))
99 (None, Some(docs), _) => Some(docs), 72 } else {
100 _ => None, 73 docs
101 } 74 }
102} 75}
103 76
@@ -121,7 +94,7 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String>
121 94
122fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { 95fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
123 let mod_path = def.module(db).map(|module| { 96 let mod_path = def.module(db).map(|module| {
124 once(db.crate_graph()[module.krate().into()].display_name.clone()) 97 once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string))
125 .chain( 98 .chain(
126 module 99 module
127 .path_to_root(db) 100 .path_to_root(db)
@@ -133,7 +106,7 @@ fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> {
133 .flatten() 106 .flatten()
134 .join("::") 107 .join("::")
135 }); 108 });
136 mod_path 109 mod_path // FIXME: replace dashes with underscores in crate display name
137} 110}
138 111
139fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { 112fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> {
@@ -170,9 +143,7 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin
170 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path), 143 ModuleDef::TypeAlias(it) => from_def_source(db, it, mod_path),
171 ModuleDef::BuiltinType(it) => Some(it.to_string()), 144 ModuleDef::BuiltinType(it) => Some(it.to_string()),
172 }, 145 },
173 Definition::Local(it) => { 146 Definition::Local(it) => Some(rust_code_markup(&it.ty(db).display_truncated(db, None))),
174 Some(rust_code_markup(it.ty(db).display_truncated(db, None).to_string()))
175 }
176 Definition::TypeParam(_) | Definition::SelfType(_) => { 147 Definition::TypeParam(_) | Definition::SelfType(_) => {
177 // FIXME: Hover for generic param 148 // FIXME: Hover for generic param
178 None 149 None
@@ -237,7 +208,7 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
237 } 208 }
238 }?; 209 }?;
239 210
240 res.extend(Some(rust_code_markup(ty.display_truncated(db, None).to_string()))); 211 res.extend(Some(rust_code_markup(&ty.display_truncated(db, None))));
241 let range = sema.original_range(&node).range; 212 let range = sema.original_range(&node).range;
242 Some(RangeInfo::new(range, res)) 213 Some(RangeInfo::new(range, res))
243} 214}
@@ -595,7 +566,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
595 ); 566 );
596 let hover = analysis.hover(position).unwrap().unwrap(); 567 let hover = analysis.hover(position).unwrap().unwrap();
597 assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); 568 assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing"));
598 assert_eq!(hover.info.is_exact(), true);
599 } 569 }
600 570
601 #[test] 571 #[test]
@@ -618,7 +588,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
618 ); 588 );
619 let hover = analysis.hover(position).unwrap().unwrap(); 589 let hover = analysis.hover(position).unwrap().unwrap();
620 assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); 590 assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32"));
621 assert_eq!(hover.info.is_exact(), true);
622 } 591 }
623 592
624 #[test] 593 #[test]
@@ -635,7 +604,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
635 ); 604 );
636 let hover = analysis.hover(position).unwrap().unwrap(); 605 let hover = analysis.hover(position).unwrap().unwrap();
637 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); 606 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
638 assert_eq!(hover.info.is_exact(), true);
639 607
640 /* FIXME: revive these tests 608 /* FIXME: revive these tests
641 let (analysis, position) = single_file_with_position( 609 let (analysis, position) = single_file_with_position(
@@ -651,7 +619,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
651 619
652 let hover = analysis.hover(position).unwrap().unwrap(); 620 let hover = analysis.hover(position).unwrap().unwrap();
653 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); 621 assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing"));
654 assert_eq!(hover.info.is_exact(), true);
655 622
656 let (analysis, position) = single_file_with_position( 623 let (analysis, position) = single_file_with_position(
657 " 624 "
@@ -665,7 +632,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
665 ); 632 );
666 let hover = analysis.hover(position).unwrap().unwrap(); 633 let hover = analysis.hover(position).unwrap().unwrap();
667 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); 634 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
668 assert_eq!(hover.info.is_exact(), true);
669 635
670 let (analysis, position) = single_file_with_position( 636 let (analysis, position) = single_file_with_position(
671 " 637 "
@@ -678,7 +644,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
678 ); 644 );
679 let hover = analysis.hover(position).unwrap().unwrap(); 645 let hover = analysis.hover(position).unwrap().unwrap();
680 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); 646 assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing"));
681 assert_eq!(hover.info.is_exact(), true);
682 */ 647 */
683 } 648 }
684 649
@@ -696,7 +661,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
696 ); 661 );
697 let hover = analysis.hover(position).unwrap().unwrap(); 662 let hover = analysis.hover(position).unwrap().unwrap();
698 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); 663 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
699 assert_eq!(hover.info.is_exact(), true);
700 } 664 }
701 665
702 #[test] 666 #[test]
@@ -714,7 +678,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
714 ); 678 );
715 let hover = analysis.hover(position).unwrap().unwrap(); 679 let hover = analysis.hover(position).unwrap().unwrap();
716 assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); 680 assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo"));
717 assert_eq!(hover.info.is_exact(), true);
718 } 681 }
719 682
720 #[test] 683 #[test]
@@ -726,7 +689,6 @@ fn func(foo: i32) { if true { <|>foo; }; }
726 ); 689 );
727 let hover = analysis.hover(position).unwrap().unwrap(); 690 let hover = analysis.hover(position).unwrap().unwrap();
728 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); 691 assert_eq!(trim_markup_opt(hover.info.first()), Some("i32"));
729 assert_eq!(hover.info.is_exact(), true);
730 } 692 }
731 693
732 #[test] 694 #[test]
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index e9af80b6c..5ab06c6cf 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -473,9 +473,10 @@ impl Analysis {
473 pub fn structural_search_replace( 473 pub fn structural_search_replace(
474 &self, 474 &self,
475 query: &str, 475 query: &str,
476 parse_only: bool,
476 ) -> Cancelable<Result<SourceChange, SsrError>> { 477 ) -> Cancelable<Result<SourceChange, SsrError>> {
477 self.with_db(|db| { 478 self.with_db(|db| {
478 let edits = ssr::parse_search_replace(query, db)?; 479 let edits = ssr::parse_search_replace(query, parse_only, db)?;
479 Ok(SourceChange::source_file_edits("ssr", edits)) 480 Ok(SourceChange::source_file_edits("ssr", edits))
480 }) 481 })
481 } 482 }
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index 25816cf6f..2cf77a31f 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -109,7 +109,7 @@ impl MockAnalysis {
109 let other_crate = crate_graph.add_crate_root( 109 let other_crate = crate_graph.add_crate_root(
110 file_id, 110 file_id,
111 Edition2018, 111 Edition2018,
112 Some(crate_name.to_owned()), 112 Some(CrateName::new(crate_name).unwrap()),
113 cfg_options, 113 cfg_options,
114 Env::default(), 114 Env::default(),
115 Default::default(), 115 Default::default(),
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index c011a2e74..1c9710a5d 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,8 +1,10 @@
1//! structural search replace 1//! structural search replace
2 2
3use crate::source_change::SourceFileEdit; 3use crate::source_change::SourceFileEdit;
4use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_ide_db::symbol_index::SymbolsDatabase;
4use ra_ide_db::RootDatabase; 6use ra_ide_db::RootDatabase;
5use ra_syntax::ast::make::expr_from_text; 7use ra_syntax::ast::make::try_expr_from_text;
6use ra_syntax::ast::{AstToken, Comment}; 8use ra_syntax::ast::{AstToken, Comment};
7use ra_syntax::{AstNode, SyntaxElement, SyntaxNode}; 9use ra_syntax::{AstNode, SyntaxElement, SyntaxNode};
8use ra_text_edit::{TextEdit, TextEditBuilder}; 10use ra_text_edit::{TextEdit, TextEditBuilder};
@@ -10,9 +12,6 @@ use rustc_hash::FxHashMap;
10use std::collections::HashMap; 12use std::collections::HashMap;
11use std::str::FromStr; 13use std::str::FromStr;
12 14
13pub use ra_db::{SourceDatabase, SourceDatabaseExt};
14use ra_ide_db::symbol_index::SymbolsDatabase;
15
16#[derive(Debug, PartialEq)] 15#[derive(Debug, PartialEq)]
17pub struct SsrError(String); 16pub struct SsrError(String);
18 17
@@ -26,14 +25,17 @@ impl std::error::Error for SsrError {}
26 25
27pub fn parse_search_replace( 26pub fn parse_search_replace(
28 query: &str, 27 query: &str,
28 parse_only: bool,
29 db: &RootDatabase, 29 db: &RootDatabase,
30) -> Result<Vec<SourceFileEdit>, SsrError> { 30) -> Result<Vec<SourceFileEdit>, SsrError> {
31 let mut edits = vec![]; 31 let mut edits = vec![];
32 let query: SsrQuery = query.parse()?; 32 let query: SsrQuery = query.parse()?;
33 if parse_only {
34 return Ok(edits);
35 }
33 for &root in db.local_roots().iter() { 36 for &root in db.local_roots().iter() {
34 let sr = db.source_root(root); 37 let sr = db.source_root(root);
35 for file_id in sr.walk() { 38 for file_id in sr.walk() {
36 dbg!(db.file_relative_path(file_id));
37 let matches = find(&query.pattern, db.parse(file_id).tree().syntax()); 39 let matches = find(&query.pattern, db.parse(file_id).tree().syntax());
38 if !matches.matches.is_empty() { 40 if !matches.matches.is_empty() {
39 edits.push(SourceFileEdit { file_id, edit: replace(&matches, &query.template) }); 41 edits.push(SourceFileEdit { file_id, edit: replace(&matches, &query.template) });
@@ -106,7 +108,10 @@ impl FromStr for SsrQuery {
106 template = replace_in_template(template, var, new_var); 108 template = replace_in_template(template, var, new_var);
107 } 109 }
108 110
109 let template = expr_from_text(&template).syntax().clone(); 111 let template = try_expr_from_text(&template)
112 .ok_or(SsrError("Template is not an expression".into()))?
113 .syntax()
114 .clone();
110 let mut placeholders = FxHashMap::default(); 115 let mut placeholders = FxHashMap::default();
111 116
112 traverse(&template, &mut |n| { 117 traverse(&template, &mut |n| {
@@ -118,7 +123,13 @@ impl FromStr for SsrQuery {
118 } 123 }
119 }); 124 });
120 125
121 let pattern = SsrPattern { pattern: expr_from_text(&pattern).syntax().clone(), vars }; 126 let pattern = SsrPattern {
127 pattern: try_expr_from_text(&pattern)
128 .ok_or(SsrError("Pattern is not an expression".into()))?
129 .syntax()
130 .clone(),
131 vars,
132 };
122 let template = SsrTemplate { template, placeholders }; 133 let template = SsrTemplate { template, placeholders };
123 Ok(SsrQuery { pattern, template }) 134 Ok(SsrQuery { pattern, template })
124 } 135 }
@@ -284,7 +295,6 @@ mod tests {
284 assert_eq!(result.pattern.vars[0].0, "__search_pattern_a"); 295 assert_eq!(result.pattern.vars[0].0, "__search_pattern_a");
285 assert_eq!(result.pattern.vars[1].0, "__search_pattern_b"); 296 assert_eq!(result.pattern.vars[1].0, "__search_pattern_b");
286 assert_eq!(&result.template.template.text(), "bar(__search_pattern_b, __search_pattern_a)"); 297 assert_eq!(&result.template.template.text(), "bar(__search_pattern_b, __search_pattern_a)");
287 dbg!(result.template.placeholders);
288 } 298 }
289 299
290 #[test] 300 #[test]
@@ -335,6 +345,16 @@ mod tests {
335 } 345 }
336 346
337 #[test] 347 #[test]
348 fn parser_invlid_pattern() {
349 assert_eq!(parse_error_text(" ==>> ()"), "Parse error: Pattern is not an expression");
350 }
351
352 #[test]
353 fn parser_invlid_template() {
354 assert_eq!(parse_error_text("() ==>> )"), "Parse error: Template is not an expression");
355 }
356
357 #[test]
338 fn parse_match_replace() { 358 fn parse_match_replace() {
339 let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap(); 359 let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap();
340 let input = "fn main() { foo(1+2); }"; 360 let input = "fn main() { foo(1+2); }";
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index 53c65f8bc..cb2cd2479 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -213,14 +213,14 @@ fn foo() {
213 type_char( 213 type_char(
214 '.', 214 '.',
215 r" 215 r"
216 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 216 fn main() {
217 self.child_impl(db, name) 217 xs.foo()
218 <|> 218 <|>
219 } 219 }
220 ", 220 ",
221 r" 221 r"
222 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 222 fn main() {
223 self.child_impl(db, name) 223 xs.foo()
224 . 224 .
225 } 225 }
226 ", 226 ",
@@ -228,8 +228,8 @@ fn foo() {
228 type_char_noop( 228 type_char_noop(
229 '.', 229 '.',
230 r" 230 r"
231 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 231 fn main() {
232 self.child_impl(db, name) 232 xs.foo()
233 <|> 233 <|>
234 } 234 }
235 ", 235 ",
@@ -241,14 +241,14 @@ fn foo() {
241 type_char( 241 type_char(
242 '.', 242 '.',
243 r" 243 r"
244 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 244 fn main() {
245 self.child_impl(db, name) 245 xs.foo()
246 <|>; 246 <|>;
247 } 247 }
248 ", 248 ",
249 r" 249 r"
250 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 250 fn main() {
251 self.child_impl(db, name) 251 xs.foo()
252 .; 252 .;
253 } 253 }
254 ", 254 ",
@@ -256,8 +256,8 @@ fn foo() {
256 type_char_noop( 256 type_char_noop(
257 '.', 257 '.',
258 r" 258 r"
259 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 259 fn main() {
260 self.child_impl(db, name) 260 xs.foo()
261 <|>; 261 <|>;
262 } 262 }
263 ", 263 ",
@@ -269,15 +269,15 @@ fn foo() {
269 type_char( 269 type_char(
270 '.', 270 '.',
271 r" 271 r"
272 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 272 fn main() {
273 self.child_impl(db, name) 273 xs.foo()
274 .first() 274 .first()
275 <|> 275 <|>
276 } 276 }
277 ", 277 ",
278 r" 278 r"
279 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 279 fn main() {
280 self.child_impl(db, name) 280 xs.foo()
281 .first() 281 .first()
282 . 282 .
283 } 283 }
@@ -286,8 +286,8 @@ fn foo() {
286 type_char_noop( 286 type_char_noop(
287 '.', 287 '.',
288 r" 288 r"
289 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 289 fn main() {
290 self.child_impl(db, name) 290 xs.foo()
291 .first() 291 .first()
292 <|> 292 <|>
293 } 293 }
@@ -334,7 +334,7 @@ fn foo() {
334 type_char_noop( 334 type_char_noop(
335 '.', 335 '.',
336 r" 336 r"
337 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 337 fn main() {
338 <|> 338 <|>
339 } 339 }
340 ", 340 ",
@@ -342,7 +342,7 @@ fn foo() {
342 type_char_noop( 342 type_char_noop(
343 '.', 343 '.',
344 r" 344 r"
345 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 345 fn main() {
346 <|> 346 <|>
347 } 347 }
348 ", 348 ",
diff --git a/crates/ra_parser/src/grammar/params.rs b/crates/ra_parser/src/grammar/params.rs
index 272661b1d..f0da173cc 100644
--- a/crates/ra_parser/src/grammar/params.rs
+++ b/crates/ra_parser/src/grammar/params.rs
@@ -56,21 +56,17 @@ fn list_(p: &mut Parser, flavor: Flavor) {
56 // fn f(#[attr1] pat: Type) {} 56 // fn f(#[attr1] pat: Type) {}
57 attributes::outer_attributes(p); 57 attributes::outer_attributes(p);
58 58
59 // test param_list_vararg
60 // extern "C" { fn printf(format: *const i8, ...) -> i32; }
61 match flavor {
62 FnDef | FnPointer if p.eat(T![...]) => break,
63 _ => (),
64 }
65
66 if !p.at_ts(VALUE_PARAMETER_FIRST) { 59 if !p.at_ts(VALUE_PARAMETER_FIRST) {
67 p.error("expected value parameter"); 60 p.error("expected value parameter");
68 break; 61 break;
69 } 62 }
70 value_parameter(p, flavor); 63 let param = value_parameter(p, flavor);
71 if !p.at(ket) { 64 if !p.at(ket) {
72 p.expect(T![,]); 65 p.expect(T![,]);
73 } 66 }
67 if let Variadic(true) = param {
68 break;
69 }
74 } 70 }
75 71
76 p.expect(ket); 72 p.expect(ket);
@@ -79,32 +75,25 @@ fn list_(p: &mut Parser, flavor: Flavor) {
79 75
80const VALUE_PARAMETER_FIRST: TokenSet = patterns::PATTERN_FIRST.union(types::TYPE_FIRST); 76const VALUE_PARAMETER_FIRST: TokenSet = patterns::PATTERN_FIRST.union(types::TYPE_FIRST);
81 77
82fn value_parameter(p: &mut Parser, flavor: Flavor) { 78struct Variadic(bool);
79
80fn value_parameter(p: &mut Parser, flavor: Flavor) -> Variadic {
81 let mut res = Variadic(false);
83 let m = p.start(); 82 let m = p.start();
84 match flavor { 83 match flavor {
85 // test trait_fn_placeholder_parameter 84 // test param_list_vararg
86 // trait Foo { 85 // extern "C" { fn printf(format: *const i8, ...) -> i32; }
87 // fn bar(_: u64, mut x: i32); 86 Flavor::FnDef | Flavor::FnPointer if p.eat(T![...]) => res = Variadic(true),
88 // }
89
90 // test trait_fn_patterns
91 // trait T {
92 // fn f1((a, b): (usize, usize)) {}
93 // fn f2(S { a, b }: S) {}
94 // fn f3(NewType(a): NewType) {}
95 // fn f4(&&a: &&usize) {}
96 // }
97 87
98 // test fn_patterns 88 // test fn_def_param
99 // impl U { 89 // fn foo((x, y): (i32, i32)) {}
100 // fn f1((a, b): (usize, usize)) {}
101 // fn f2(S { a, b }: S) {}
102 // fn f3(NewType(a): NewType) {}
103 // fn f4(&&a: &&usize) {}
104 // }
105 Flavor::FnDef => { 90 Flavor::FnDef => {
106 patterns::pattern(p); 91 patterns::pattern(p);
107 types::ascription(p); 92 if variadic_param(p) {
93 res = Variadic(true)
94 } else {
95 types::ascription(p);
96 }
108 } 97 }
109 // test value_parameters_no_patterns 98 // test value_parameters_no_patterns
110 // type F = Box<Fn(i32, &i32, &i32, ())>; 99 // type F = Box<Fn(i32, &i32, &i32, ())>;
@@ -120,7 +109,11 @@ fn value_parameter(p: &mut Parser, flavor: Flavor) {
120 Flavor::FnPointer => { 109 Flavor::FnPointer => {
121 if (p.at(IDENT) || p.at(UNDERSCORE)) && p.nth(1) == T![:] && !p.nth_at(1, T![::]) { 110 if (p.at(IDENT) || p.at(UNDERSCORE)) && p.nth(1) == T![:] && !p.nth_at(1, T![::]) {
122 patterns::pattern_single(p); 111 patterns::pattern_single(p);
123 types::ascription(p); 112 if variadic_param(p) {
113 res = Variadic(true)
114 } else {
115 types::ascription(p);
116 }
124 } else { 117 } else {
125 types::type_(p); 118 types::type_(p);
126 } 119 }
@@ -137,6 +130,17 @@ fn value_parameter(p: &mut Parser, flavor: Flavor) {
137 } 130 }
138 } 131 }
139 m.complete(p, PARAM); 132 m.complete(p, PARAM);
133 res
134}
135
136fn variadic_param(p: &mut Parser) -> bool {
137 if p.at(T![:]) && p.nth_at(1, T![...]) {
138 p.bump(T![:]);
139 p.bump(T![...]);
140 true
141 } else {
142 false
143 }
140} 144}
141 145
142// test self_param 146// test self_param
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index a6274709d..897874813 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -245,7 +245,10 @@ impl ProjectWorkspace {
245 let crate_id = crate_graph.add_crate_root( 245 let crate_id = crate_graph.add_crate_root(
246 file_id, 246 file_id,
247 Edition::Edition2018, 247 Edition::Edition2018,
248 Some(krate.name(&sysroot).to_string()), 248 Some(
249 CrateName::new(krate.name(&sysroot))
250 .expect("Sysroot crate names should not contain dashes"),
251 ),
249 cfg_options, 252 cfg_options,
250 env, 253 env,
251 extern_source, 254 extern_source,
@@ -296,7 +299,7 @@ impl ProjectWorkspace {
296 let crate_id = crate_graph.add_crate_root( 299 let crate_id = crate_graph.add_crate_root(
297 file_id, 300 file_id,
298 edition, 301 edition,
299 Some(pkg.name(&cargo).to_string()), 302 Some(CrateName::normalize_dashes(pkg.name(&cargo))),
300 cfg_options, 303 cfg_options,
301 env, 304 env,
302 extern_source, 305 extern_source,
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index ae8829807..9f6f1cc53 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -112,10 +112,14 @@ pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr {
112 let token = token(op); 112 let token = token(op);
113 expr_from_text(&format!("{}{}", token, expr.syntax())) 113 expr_from_text(&format!("{}{}", token, expr.syntax()))
114} 114}
115pub fn expr_from_text(text: &str) -> ast::Expr { 115fn expr_from_text(text: &str) -> ast::Expr {
116 ast_from_text(&format!("const C: () = {};", text)) 116 ast_from_text(&format!("const C: () = {};", text))
117} 117}
118 118
119pub fn try_expr_from_text(text: &str) -> Option<ast::Expr> {
120 try_ast_from_text(&format!("const C: () = {};", text))
121}
122
119pub fn bind_pat(name: ast::Name) -> ast::BindPat { 123pub fn bind_pat(name: ast::Name) -> ast::BindPat {
120 return from_text(name.text()); 124 return from_text(name.text());
121 125
@@ -239,6 +243,16 @@ fn ast_from_text<N: AstNode>(text: &str) -> N {
239 node 243 node
240} 244}
241 245
246fn try_ast_from_text<N: AstNode>(text: &str) -> Option<N> {
247 let parse = SourceFile::parse(text);
248 let node = parse.tree().syntax().descendants().find_map(N::cast)?;
249 let node = node.syntax().clone();
250 let node = unroot(node);
251 let node = N::cast(node).unwrap();
252 assert_eq!(node.syntax().text_range().start(), 0.into());
253 Some(node)
254}
255
242fn unroot(n: SyntaxNode) -> SyntaxNode { 256fn unroot(n: SyntaxNode) -> SyntaxNode {
243 SyntaxNode::new_root(n.green().clone()) 257 SyntaxNode::new_root(n.green().clone())
244} 258}
diff --git a/crates/ra_syntax/src/tests.rs b/crates/ra_syntax/src/tests.rs
index d331d541e..6a8cb6bb5 100644
--- a/crates/ra_syntax/src/tests.rs
+++ b/crates/ra_syntax/src/tests.rs
@@ -34,6 +34,7 @@ fn main() {
34 "##; 34 "##;
35 35
36 let parse = SourceFile::parse(code); 36 let parse = SourceFile::parse(code);
37 // eprintln!("{:#?}", parse.syntax_node());
37 assert!(parse.ok().is_ok()); 38 assert!(parse.ok().is_ok());
38} 39}
39 40
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0032_fn_pointer_type.txt b/crates/ra_syntax/test_data/parser/inline/ok/0032_fn_pointer_type.txt
index a0a8aea76..4c17f0db8 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0032_fn_pointer_type.txt
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0032_fn_pointer_type.txt
@@ -81,7 +81,8 @@ SOURCE_FILE@[0; 113)
81 WHITESPACE@[97; 98) " " 81 WHITESPACE@[97; 98) " "
82 COMMA@[98; 99) "," 82 COMMA@[98; 99) ","
83 WHITESPACE@[99; 100) " " 83 WHITESPACE@[99; 100) " "
84 DOTDOTDOT@[100; 103) "..." 84 PARAM@[100; 103)
85 DOTDOTDOT@[100; 103) "..."
85 WHITESPACE@[103; 104) " " 86 WHITESPACE@[103; 104) " "
86 R_PAREN@[104; 105) ")" 87 R_PAREN@[104; 105) ")"
87 WHITESPACE@[105; 106) " " 88 WHITESPACE@[105; 106) " "
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.rs b/crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.rs
deleted file mode 100644
index 472cb8803..000000000
--- a/crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.rs
+++ /dev/null
@@ -1,3 +0,0 @@
1trait Foo {
2 fn bar(_: u64, mut x: i32);
3}
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.txt b/crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.txt
deleted file mode 100644
index 158236c5a..000000000
--- a/crates/ra_syntax/test_data/parser/inline/ok/0116_trait_fn_placeholder_parameter.txt
+++ /dev/null
@@ -1,47 +0,0 @@
1SOURCE_FILE@[0; 46)
2 TRAIT_DEF@[0; 45)
3 TRAIT_KW@[0; 5) "trait"
4 WHITESPACE@[5; 6) " "
5 NAME@[6; 9)
6 IDENT@[6; 9) "Foo"
7 WHITESPACE@[9; 10) " "
8 ITEM_LIST@[10; 45)
9 L_CURLY@[10; 11) "{"
10 WHITESPACE@[11; 16) "\n "
11 FN_DEF@[16; 43)
12 FN_KW@[16; 18) "fn"
13 WHITESPACE@[18; 19) " "
14 NAME@[19; 22)
15 IDENT@[19; 22) "bar"
16 PARAM_LIST@[22; 42)
17 L_PAREN@[22; 23) "("
18 PARAM@[23; 29)
19 PLACEHOLDER_PAT@[23; 24)
20 UNDERSCORE@[23; 24) "_"
21 COLON@[24; 25) ":"
22 WHITESPACE@[25; 26) " "
23 PATH_TYPE@[26; 29)
24 PATH@[26; 29)
25 PATH_SEGMENT@[26; 29)
26 NAME_REF@[26; 29)
27 IDENT@[26; 29) "u64"
28 COMMA@[29; 30) ","
29 WHITESPACE@[30; 31) " "
30 PARAM@[31; 41)
31 BIND_PAT@[31; 36)
32 MUT_KW@[31; 34) "mut"
33 WHITESPACE@[34; 35) " "
34 NAME@[35; 36)
35 IDENT@[35; 36) "x"
36 COLON@[36; 37) ":"
37 WHITESPACE@[37; 38) " "
38 PATH_TYPE@[38; 41)
39 PATH@[38; 41)
40 PATH_SEGMENT@[38; 41)
41 NAME_REF@[38; 41)
42 IDENT@[38; 41) "i32"
43 R_PAREN@[41; 42) ")"
44 SEMI@[42; 43) ";"
45 WHITESPACE@[43; 44) "\n"
46 R_CURLY@[44; 45) "}"
47 WHITESPACE@[45; 46) "\n"
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0123_param_list_vararg.txt b/crates/ra_syntax/test_data/parser/inline/ok/0123_param_list_vararg.txt
index 836e8e55b..6c3b17868 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0123_param_list_vararg.txt
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0123_param_list_vararg.txt
@@ -32,7 +32,8 @@ SOURCE_FILE@[0; 57)
32 IDENT@[38; 40) "i8" 32 IDENT@[38; 40) "i8"
33 COMMA@[40; 41) "," 33 COMMA@[40; 41) ","
34 WHITESPACE@[41; 42) " " 34 WHITESPACE@[41; 42) " "
35 DOTDOTDOT@[42; 45) "..." 35 PARAM@[42; 45)
36 DOTDOTDOT@[42; 45) "..."
36 R_PAREN@[45; 46) ")" 37 R_PAREN@[45; 46) ")"
37 WHITESPACE@[46; 47) " " 38 WHITESPACE@[46; 47) " "
38 RET_TYPE@[47; 53) 39 RET_TYPE@[47; 53)
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.rs b/crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.rs
new file mode 100644
index 000000000..7b277c16b
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.rs
@@ -0,0 +1 @@
fn foo((x, y): (i32, i32)) {}
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.txt b/crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.txt
new file mode 100644
index 000000000..103e254a6
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/inline/ok/0156_fn_def_param.txt
@@ -0,0 +1,44 @@
1SOURCE_FILE@[0; 30)
2 FN_DEF@[0; 29)
3 FN_KW@[0; 2) "fn"
4 WHITESPACE@[2; 3) " "
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 26)
8 L_PAREN@[6; 7) "("
9 PARAM@[7; 25)
10 TUPLE_PAT@[7; 13)
11 L_PAREN@[7; 8) "("
12 BIND_PAT@[8; 9)
13 NAME@[8; 9)
14 IDENT@[8; 9) "x"
15 COMMA@[9; 10) ","
16 WHITESPACE@[10; 11) " "
17 BIND_PAT@[11; 12)
18 NAME@[11; 12)
19 IDENT@[11; 12) "y"
20 R_PAREN@[12; 13) ")"
21 COLON@[13; 14) ":"
22 WHITESPACE@[14; 15) " "
23 TUPLE_TYPE@[15; 25)
24 L_PAREN@[15; 16) "("
25 PATH_TYPE@[16; 19)
26 PATH@[16; 19)
27 PATH_SEGMENT@[16; 19)
28 NAME_REF@[16; 19)
29 IDENT@[16; 19) "i32"
30 COMMA@[19; 20) ","
31 WHITESPACE@[20; 21) " "
32 PATH_TYPE@[21; 24)
33 PATH@[21; 24)
34 PATH_SEGMENT@[21; 24)
35 NAME_REF@[21; 24)
36 IDENT@[21; 24) "i32"
37 R_PAREN@[24; 25) ")"
38 R_PAREN@[25; 26) ")"
39 WHITESPACE@[26; 27) " "
40 BLOCK_EXPR@[27; 29)
41 BLOCK@[27; 29)
42 L_CURLY@[27; 28) "{"
43 R_CURLY@[28; 29) "}"
44 WHITESPACE@[29; 30) "\n"
diff --git a/crates/ra_syntax/test_data/parser/ok/0051_parameter_attrs.txt b/crates/ra_syntax/test_data/parser/ok/0051_parameter_attrs.txt
index 719c99c17..254eafc36 100644
--- a/crates/ra_syntax/test_data/parser/ok/0051_parameter_attrs.txt
+++ b/crates/ra_syntax/test_data/parser/ok/0051_parameter_attrs.txt
@@ -118,7 +118,8 @@ SOURCE_FILE@[0; 519)
118 IDENT@[108; 112) "attr" 118 IDENT@[108; 112) "attr"
119 R_BRACK@[112; 113) "]" 119 R_BRACK@[112; 113) "]"
120 WHITESPACE@[113; 114) " " 120 WHITESPACE@[113; 114) " "
121 DOTDOTDOT@[114; 117) "..." 121 PARAM@[114; 117)
122 DOTDOTDOT@[114; 117) "..."
122 R_PAREN@[117; 118) ")" 123 R_PAREN@[117; 118) ")"
123 WHITESPACE@[118; 119) " " 124 WHITESPACE@[118; 119) " "
124 RET_TYPE@[119; 125) 125 RET_TYPE@[119; 125)
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0153_trait_fn_patterns.rs b/crates/ra_syntax/test_data/parser/ok/0063_trait_fn_patterns.rs
index a94bf378a..3b666af8e 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0153_trait_fn_patterns.rs
+++ b/crates/ra_syntax/test_data/parser/ok/0063_trait_fn_patterns.rs
@@ -3,4 +3,5 @@ trait T {
3 fn f2(S { a, b }: S) {} 3 fn f2(S { a, b }: S) {}
4 fn f3(NewType(a): NewType) {} 4 fn f3(NewType(a): NewType) {}
5 fn f4(&&a: &&usize) {} 5 fn f4(&&a: &&usize) {}
6 fn bar(_: u64, mut x: i32);
6} 7}
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0153_trait_fn_patterns.txt b/crates/ra_syntax/test_data/parser/ok/0063_trait_fn_patterns.txt
index b22df8dbe..eb2e3a503 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0153_trait_fn_patterns.txt
+++ b/crates/ra_syntax/test_data/parser/ok/0063_trait_fn_patterns.txt
@@ -1,11 +1,11 @@
1SOURCE_FILE@[0; 138) 1SOURCE_FILE@[0; 170)
2 TRAIT_DEF@[0; 137) 2 TRAIT_DEF@[0; 169)
3 TRAIT_KW@[0; 5) "trait" 3 TRAIT_KW@[0; 5) "trait"
4 WHITESPACE@[5; 6) " " 4 WHITESPACE@[5; 6) " "
5 NAME@[6; 7) 5 NAME@[6; 7)
6 IDENT@[6; 7) "T" 6 IDENT@[6; 7) "T"
7 WHITESPACE@[7; 8) " " 7 WHITESPACE@[7; 8) " "
8 ITEM_LIST@[8; 137) 8 ITEM_LIST@[8; 169)
9 L_CURLY@[8; 9) "{" 9 L_CURLY@[8; 9) "{"
10 WHITESPACE@[9; 14) "\n " 10 WHITESPACE@[9; 14) "\n "
11 FN_DEF@[14; 46) 11 FN_DEF@[14; 46)
@@ -156,6 +156,41 @@ SOURCE_FILE@[0; 138)
156 BLOCK@[133; 135) 156 BLOCK@[133; 135)
157 L_CURLY@[133; 134) "{" 157 L_CURLY@[133; 134) "{"
158 R_CURLY@[134; 135) "}" 158 R_CURLY@[134; 135) "}"
159 WHITESPACE@[135; 136) "\n" 159 WHITESPACE@[135; 140) "\n "
160 R_CURLY@[136; 137) "}" 160 FN_DEF@[140; 167)
161 WHITESPACE@[137; 138) "\n" 161 FN_KW@[140; 142) "fn"
162 WHITESPACE@[142; 143) " "
163 NAME@[143; 146)
164 IDENT@[143; 146) "bar"
165 PARAM_LIST@[146; 166)
166 L_PAREN@[146; 147) "("
167 PARAM@[147; 153)
168 PLACEHOLDER_PAT@[147; 148)
169 UNDERSCORE@[147; 148) "_"
170 COLON@[148; 149) ":"
171 WHITESPACE@[149; 150) " "
172 PATH_TYPE@[150; 153)
173 PATH@[150; 153)
174 PATH_SEGMENT@[150; 153)
175 NAME_REF@[150; 153)
176 IDENT@[150; 153) "u64"
177 COMMA@[153; 154) ","
178 WHITESPACE@[154; 155) " "
179 PARAM@[155; 165)
180 BIND_PAT@[155; 160)
181 MUT_KW@[155; 158) "mut"
182 WHITESPACE@[158; 159) " "
183 NAME@[159; 160)
184 IDENT@[159; 160) "x"
185 COLON@[160; 161) ":"
186 WHITESPACE@[161; 162) " "
187 PATH_TYPE@[162; 165)
188 PATH@[162; 165)
189 PATH_SEGMENT@[162; 165)
190 NAME_REF@[162; 165)
191 IDENT@[162; 165) "i32"
192 R_PAREN@[165; 166) ")"
193 SEMI@[166; 167) ";"
194 WHITESPACE@[167; 168) "\n"
195 R_CURLY@[168; 169) "}"
196 WHITESPACE@[169; 170) "\n"
diff --git a/crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.rs b/crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.rs
new file mode 100644
index 000000000..a16afbaf3
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.rs
@@ -0,0 +1,5 @@
1extern "C" {
2 fn a(_: *mut u8, ...,);
3 fn b(_: *mut u8, _: ...);
4 fn c(_: *mut u8, #[cfg(never)] [w, t, f]: ...,);
5}
diff --git a/crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.txt b/crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.txt
new file mode 100644
index 000000000..186f03626
--- /dev/null
+++ b/crates/ra_syntax/test_data/parser/ok/0063_variadic_fun.txt
@@ -0,0 +1,133 @@
1SOURCE_FILE@[0; 126)
2 EXTERN_BLOCK@[0; 125)
3 ABI@[0; 10)
4 EXTERN_KW@[0; 6) "extern"
5 WHITESPACE@[6; 7) " "
6 STRING@[7; 10) "\"C\""
7 WHITESPACE@[10; 11) " "
8 EXTERN_ITEM_LIST@[11; 125)
9 L_CURLY@[11; 12) "{"
10 WHITESPACE@[12; 17) "\n "
11 FN_DEF@[17; 40)
12 FN_KW@[17; 19) "fn"
13 WHITESPACE@[19; 20) " "
14 NAME@[20; 21)
15 IDENT@[20; 21) "a"
16 PARAM_LIST@[21; 39)
17 L_PAREN@[21; 22) "("
18 PARAM@[22; 32)
19 PLACEHOLDER_PAT@[22; 23)
20 UNDERSCORE@[22; 23) "_"
21 COLON@[23; 24) ":"
22 WHITESPACE@[24; 25) " "
23 POINTER_TYPE@[25; 32)
24 STAR@[25; 26) "*"
25 MUT_KW@[26; 29) "mut"
26 WHITESPACE@[29; 30) " "
27 PATH_TYPE@[30; 32)
28 PATH@[30; 32)
29 PATH_SEGMENT@[30; 32)
30 NAME_REF@[30; 32)
31 IDENT@[30; 32) "u8"
32 COMMA@[32; 33) ","
33 WHITESPACE@[33; 34) " "
34 PARAM@[34; 37)
35 DOTDOTDOT@[34; 37) "..."
36 COMMA@[37; 38) ","
37 R_PAREN@[38; 39) ")"
38 SEMI@[39; 40) ";"
39 WHITESPACE@[40; 45) "\n "
40 FN_DEF@[45; 70)
41 FN_KW@[45; 47) "fn"
42 WHITESPACE@[47; 48) " "
43 NAME@[48; 49)
44 IDENT@[48; 49) "b"
45 PARAM_LIST@[49; 69)
46 L_PAREN@[49; 50) "("
47 PARAM@[50; 60)
48 PLACEHOLDER_PAT@[50; 51)
49 UNDERSCORE@[50; 51) "_"
50 COLON@[51; 52) ":"
51 WHITESPACE@[52; 53) " "
52 POINTER_TYPE@[53; 60)
53 STAR@[53; 54) "*"
54 MUT_KW@[54; 57) "mut"
55 WHITESPACE@[57; 58) " "
56 PATH_TYPE@[58; 60)
57 PATH@[58; 60)
58 PATH_SEGMENT@[58; 60)
59 NAME_REF@[58; 60)
60 IDENT@[58; 60) "u8"
61 COMMA@[60; 61) ","
62 WHITESPACE@[61; 62) " "
63 PARAM@[62; 68)
64 PLACEHOLDER_PAT@[62; 63)
65 UNDERSCORE@[62; 63) "_"
66 COLON@[63; 64) ":"
67 WHITESPACE@[64; 65) " "
68 DOTDOTDOT@[65; 68) "..."
69 R_PAREN@[68; 69) ")"
70 SEMI@[69; 70) ";"
71 WHITESPACE@[70; 75) "\n "
72 FN_DEF@[75; 123)
73 FN_KW@[75; 77) "fn"
74 WHITESPACE@[77; 78) " "
75 NAME@[78; 79)
76 IDENT@[78; 79) "c"
77 PARAM_LIST@[79; 122)
78 L_PAREN@[79; 80) "("
79 PARAM@[80; 90)
80 PLACEHOLDER_PAT@[80; 81)
81 UNDERSCORE@[80; 81) "_"
82 COLON@[81; 82) ":"
83 WHITESPACE@[82; 83) " "
84 POINTER_TYPE@[83; 90)
85 STAR@[83; 84) "*"
86 MUT_KW@[84; 87) "mut"
87 WHITESPACE@[87; 88) " "
88 PATH_TYPE@[88; 90)
89 PATH@[88; 90)
90 PATH_SEGMENT@[88; 90)
91 NAME_REF@[88; 90)
92 IDENT@[88; 90) "u8"
93 COMMA@[90; 91) ","
94 WHITESPACE@[91; 92) " "
95 ATTR@[92; 105)
96 POUND@[92; 93) "#"
97 L_BRACK@[93; 94) "["
98 PATH@[94; 97)
99 PATH_SEGMENT@[94; 97)
100 NAME_REF@[94; 97)
101 IDENT@[94; 97) "cfg"
102 TOKEN_TREE@[97; 104)
103 L_PAREN@[97; 98) "("
104 IDENT@[98; 103) "never"
105 R_PAREN@[103; 104) ")"
106 R_BRACK@[104; 105) "]"
107 WHITESPACE@[105; 106) " "
108 PARAM@[106; 120)
109 SLICE_PAT@[106; 115)
110 L_BRACK@[106; 107) "["
111 BIND_PAT@[107; 108)
112 NAME@[107; 108)
113 IDENT@[107; 108) "w"
114 COMMA@[108; 109) ","
115 WHITESPACE@[109; 110) " "
116 BIND_PAT@[110; 111)
117 NAME@[110; 111)
118 IDENT@[110; 111) "t"
119 COMMA@[111; 112) ","
120 WHITESPACE@[112; 113) " "
121 BIND_PAT@[113; 114)
122 NAME@[113; 114)
123 IDENT@[113; 114) "f"
124 R_BRACK@[114; 115) "]"
125 COLON@[115; 116) ":"
126 WHITESPACE@[116; 117) " "
127 DOTDOTDOT@[117; 120) "..."
128 COMMA@[120; 121) ","
129 R_PAREN@[121; 122) ")"
130 SEMI@[122; 123) ";"
131 WHITESPACE@[123; 124) "\n"
132 R_CURLY@[124; 125) "}"
133 WHITESPACE@[125; 126) "\n"
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0152_fn_patterns.rs b/crates/ra_syntax/test_data/parser/ok/0064_impl_fn_params.rs
index b49e872d7..b49e872d7 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0152_fn_patterns.rs
+++ b/crates/ra_syntax/test_data/parser/ok/0064_impl_fn_params.rs
diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0152_fn_patterns.txt b/crates/ra_syntax/test_data/parser/ok/0064_impl_fn_params.txt
index b30030de3..b30030de3 100644
--- a/crates/ra_syntax/test_data/parser/inline/ok/0152_fn_patterns.txt
+++ b/crates/ra_syntax/test_data/parser/ok/0064_impl_fn_params.txt
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 53751aafb..321861b16 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -19,50 +19,48 @@ impl CargoTargetSpec {
19 pub(crate) fn runnable_args( 19 pub(crate) fn runnable_args(
20 spec: Option<CargoTargetSpec>, 20 spec: Option<CargoTargetSpec>,
21 kind: &RunnableKind, 21 kind: &RunnableKind,
22 ) -> Result<Vec<String>> { 22 ) -> Result<(Vec<String>, Vec<String>)> {
23 let mut res = Vec::new(); 23 let mut args = Vec::new();
24 let mut extra_args = Vec::new();
24 match kind { 25 match kind {
25 RunnableKind::Test { test_id } => { 26 RunnableKind::Test { test_id } => {
26 res.push("test".to_string()); 27 args.push("test".to_string());
27 if let Some(spec) = spec { 28 if let Some(spec) = spec {
28 spec.push_to(&mut res); 29 spec.push_to(&mut args);
29 } 30 }
30 res.push("--".to_string()); 31 extra_args.push(test_id.to_string());
31 res.push(test_id.to_string());
32 if let TestId::Path(_) = test_id { 32 if let TestId::Path(_) = test_id {
33 res.push("--exact".to_string()); 33 extra_args.push("--exact".to_string());
34 } 34 }
35 res.push("--nocapture".to_string()); 35 extra_args.push("--nocapture".to_string());
36 } 36 }
37 RunnableKind::TestMod { path } => { 37 RunnableKind::TestMod { path } => {
38 res.push("test".to_string()); 38 args.push("test".to_string());
39 if let Some(spec) = spec { 39 if let Some(spec) = spec {
40 spec.push_to(&mut res); 40 spec.push_to(&mut args);
41 } 41 }
42 res.push("--".to_string()); 42 extra_args.push(path.to_string());
43 res.push(path.to_string()); 43 extra_args.push("--nocapture".to_string());
44 res.push("--nocapture".to_string());
45 } 44 }
46 RunnableKind::Bench { test_id } => { 45 RunnableKind::Bench { test_id } => {
47 res.push("bench".to_string()); 46 args.push("bench".to_string());
48 if let Some(spec) = spec { 47 if let Some(spec) = spec {
49 spec.push_to(&mut res); 48 spec.push_to(&mut args);
50 } 49 }
51 res.push("--".to_string()); 50 extra_args.push(test_id.to_string());
52 res.push(test_id.to_string());
53 if let TestId::Path(_) = test_id { 51 if let TestId::Path(_) = test_id {
54 res.push("--exact".to_string()); 52 extra_args.push("--exact".to_string());
55 } 53 }
56 res.push("--nocapture".to_string()); 54 extra_args.push("--nocapture".to_string());
57 } 55 }
58 RunnableKind::Bin => { 56 RunnableKind::Bin => {
59 res.push("run".to_string()); 57 args.push("run".to_string());
60 if let Some(spec) = spec { 58 if let Some(spec) = spec {
61 spec.push_to(&mut res); 59 spec.push_to(&mut args);
62 } 60 }
63 } 61 }
64 } 62 }
65 Ok(res) 63 Ok((args, extra_args))
66 } 64 }
67 65
68 pub(crate) fn for_file( 66 pub(crate) fn for_file(
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 084e17b04..6b9a11a87 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -55,6 +55,9 @@ pub struct ServerConfig {
55 55
56 /// Cargo feature configurations. 56 /// Cargo feature configurations.
57 pub cargo_features: CargoFeatures, 57 pub cargo_features: CargoFeatures,
58
59 /// Enabled if the vscode_lldb extension is available.
60 pub vscode_lldb: bool,
58} 61}
59 62
60impl Default for ServerConfig { 63impl Default for ServerConfig {
@@ -76,6 +79,7 @@ impl Default for ServerConfig {
76 additional_out_dirs: FxHashMap::default(), 79 additional_out_dirs: FxHashMap::default(),
77 cargo_features: Default::default(), 80 cargo_features: Default::default(),
78 rustfmt_args: Vec::new(), 81 rustfmt_args: Vec::new(),
82 vscode_lldb: false,
79 } 83 }
80 } 84 }
81} 85}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 2b3b16d35..1fefc66aa 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -16,7 +16,10 @@ use std::{
16 16
17use crossbeam_channel::{select, unbounded, RecvError, Sender}; 17use crossbeam_channel::{select, unbounded, RecvError, Sender};
18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 18use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
19use lsp_types::{ClientCapabilities, NumberOrString}; 19use lsp_types::{
20 ClientCapabilities, NumberOrString, WorkDoneProgress, WorkDoneProgressBegin,
21 WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressReport,
22};
20use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask}; 23use ra_cargo_watch::{url_from_path_with_drive_lowercasing, CheckOptions, CheckTask};
21use ra_ide::{Canceled, FileId, InlayHintsOptions, LibraryData, SourceRootId}; 24use ra_ide::{Canceled, FileId, InlayHintsOptions, LibraryData, SourceRootId};
22use ra_prof::profile; 25use ra_prof::profile;
@@ -189,6 +192,7 @@ pub fn main_loop(
189 all_targets: config.cargo_watch_all_targets, 192 all_targets: config.cargo_watch_all_targets,
190 }, 193 },
191 rustfmt_args: config.rustfmt_args, 194 rustfmt_args: config.rustfmt_args,
195 vscode_lldb: config.vscode_lldb,
192 } 196 }
193 }; 197 };
194 198
@@ -329,6 +333,7 @@ struct LoopState {
329 in_flight_libraries: usize, 333 in_flight_libraries: usize,
330 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>, 334 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>,
331 workspace_loaded: bool, 335 workspace_loaded: bool,
336 roots_scanned_progress: Option<usize>,
332} 337}
333 338
334impl LoopState { 339impl LoopState {
@@ -428,17 +433,15 @@ fn loop_turn(
428 && loop_state.in_flight_libraries == 0 433 && loop_state.in_flight_libraries == 0
429 { 434 {
430 loop_state.workspace_loaded = true; 435 loop_state.workspace_loaded = true;
431 let n_packages: usize = world_state.workspaces.iter().map(|it| it.n_packages()).sum();
432 if world_state.feature_flags.get("notifications.workspace-loaded") {
433 let msg = format!("workspace loaded, {} rust packages", n_packages);
434 show_message(req::MessageType::Info, msg, &connection.sender);
435 }
436 world_state.check_watcher.update(); 436 world_state.check_watcher.update();
437 pool.execute({ 437 pool.execute({
438 let subs = loop_state.subscriptions.subscriptions(); 438 let subs = loop_state.subscriptions.subscriptions();
439 let snap = world_state.snapshot(); 439 let snap = world_state.snapshot();
440 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) 440 move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ())
441 }); 441 });
442 send_startup_progress(&connection.sender, loop_state, world_state);
443 } else if !loop_state.workspace_loaded {
444 send_startup_progress(&connection.sender, loop_state, world_state);
442 } 445 }
443 446
444 if state_changed { 447 if state_changed {
@@ -703,6 +706,65 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state:
703 } 706 }
704} 707}
705 708
709fn send_startup_progress(
710 sender: &Sender<Message>,
711 loop_state: &mut LoopState,
712 world_state: &WorldState,
713) {
714 if !world_state.feature_flags.get("notifications.workspace-loaded") {
715 return;
716 }
717
718 let total: usize = world_state.workspaces.iter().map(|it| it.n_packages()).sum();
719 let prev_progress = loop_state.roots_scanned_progress;
720 let progress = total - world_state.roots_to_scan;
721 loop_state.roots_scanned_progress = Some(progress);
722
723 match (prev_progress, loop_state.workspace_loaded) {
724 (None, false) => {
725 let work_done_progress_create = request_new::<req::WorkDoneProgressCreate>(
726 loop_state.next_request_id(),
727 WorkDoneProgressCreateParams {
728 token: req::ProgressToken::String("rustAnalyzer/startup".into()),
729 },
730 );
731 sender.send(work_done_progress_create.into()).unwrap();
732 send_startup_progress_notif(
733 sender,
734 WorkDoneProgress::Begin(WorkDoneProgressBegin {
735 title: "rust-analyzer".into(),
736 cancellable: None,
737 message: Some(format!("{}/{} packages", progress, total)),
738 percentage: Some(100.0 * progress as f64 / total as f64),
739 }),
740 );
741 }
742 (Some(prev), false) if progress != prev => send_startup_progress_notif(
743 sender,
744 WorkDoneProgress::Report(WorkDoneProgressReport {
745 cancellable: None,
746 message: Some(format!("{}/{} packages", progress, total)),
747 percentage: Some(100.0 * progress as f64 / total as f64),
748 }),
749 ),
750 (_, true) => send_startup_progress_notif(
751 sender,
752 WorkDoneProgress::End(WorkDoneProgressEnd {
753 message: Some(format!("rust-analyzer loaded, {} packages", progress)),
754 }),
755 ),
756 _ => {}
757 }
758}
759
760fn send_startup_progress_notif(sender: &Sender<Message>, work_done_progress: WorkDoneProgress) {
761 let notif = notification_new::<req::Progress>(req::ProgressParams {
762 token: req::ProgressToken::String("rustAnalyzer/startup".into()),
763 value: req::ProgressParamsValue::WorkDone(work_done_progress),
764 });
765 sender.send(notif.into()).unwrap();
766}
767
706struct PoolDispatcher<'a> { 768struct PoolDispatcher<'a> {
707 req: Option<Request>, 769 req: Option<Request>,
708 pool: &'a ThreadPool, 770 pool: &'a ThreadPool,
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index 6482f3b77..1cc2f6571 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -381,6 +381,7 @@ pub fn handle_runnables(
381 label, 381 label,
382 bin: "cargo".to_string(), 382 bin: "cargo".to_string(),
383 args: check_args, 383 args: check_args,
384 extra_args: Vec::new(),
384 env: FxHashMap::default(), 385 env: FxHashMap::default(),
385 cwd: workspace_root.map(|root| root.to_string_lossy().to_string()), 386 cwd: workspace_root.map(|root| root.to_string_lossy().to_string()),
386 }); 387 });
@@ -794,18 +795,35 @@ pub fn handle_code_lens(
794 RunnableKind::Bin => "Run", 795 RunnableKind::Bin => "Run",
795 } 796 }
796 .to_string(); 797 .to_string();
797 let r = to_lsp_runnable(&world, file_id, runnable)?; 798 let mut r = to_lsp_runnable(&world, file_id, runnable)?;
798 let lens = CodeLens { 799 let lens = CodeLens {
799 range: r.range, 800 range: r.range,
800 command: Some(Command { 801 command: Some(Command {
801 title, 802 title,
802 command: "rust-analyzer.runSingle".into(), 803 command: "rust-analyzer.runSingle".into(),
803 arguments: Some(vec![to_value(r).unwrap()]), 804 arguments: Some(vec![to_value(&r).unwrap()]),
804 }), 805 }),
805 data: None, 806 data: None,
806 }; 807 };
807
808 lenses.push(lens); 808 lenses.push(lens);
809
810 if world.options.vscode_lldb {
811 if r.args[0] == "run" {
812 r.args[0] = "build".into();
813 } else {
814 r.args.push("--no-run".into());
815 }
816 let debug_lens = CodeLens {
817 range: r.range,
818 command: Some(Command {
819 title: "Debug".into(),
820 command: "rust-analyzer.debugSingle".into(),
821 arguments: Some(vec![to_value(r).unwrap()]),
822 }),
823 data: None,
824 };
825 lenses.push(debug_lens);
826 }
809 } 827 }
810 828
811 // Handle impls 829 // Handle impls
@@ -914,7 +932,10 @@ pub fn handle_document_highlight(
914 932
915pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> { 933pub fn handle_ssr(world: WorldSnapshot, params: req::SsrParams) -> Result<req::SourceChange> {
916 let _p = profile("handle_ssr"); 934 let _p = profile("handle_ssr");
917 world.analysis().structural_search_replace(&params.arg)??.try_conv_with(&world) 935 world
936 .analysis()
937 .structural_search_replace(&params.query, params.parse_only)??
938 .try_conv_with(&world)
918} 939}
919 940
920pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> { 941pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result<DiagnosticTask> {
@@ -952,7 +973,7 @@ fn to_lsp_runnable(
952 runnable: Runnable, 973 runnable: Runnable,
953) -> Result<req::Runnable> { 974) -> Result<req::Runnable> {
954 let spec = CargoTargetSpec::for_file(world, file_id)?; 975 let spec = CargoTargetSpec::for_file(world, file_id)?;
955 let args = CargoTargetSpec::runnable_args(spec, &runnable.kind)?; 976 let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?;
956 let line_index = world.analysis().file_line_index(file_id)?; 977 let line_index = world.analysis().file_line_index(file_id)?;
957 let label = match &runnable.kind { 978 let label = match &runnable.kind {
958 RunnableKind::Test { test_id } => format!("test {}", test_id), 979 RunnableKind::Test { test_id } => format!("test {}", test_id),
@@ -965,6 +986,7 @@ fn to_lsp_runnable(
965 label, 986 label,
966 bin: "cargo".to_string(), 987 bin: "cargo".to_string(),
967 args, 988 args,
989 extra_args,
968 env: { 990 env: {
969 let mut m = FxHashMap::default(); 991 let mut m = FxHashMap::default();
970 m.insert("RUST_BACKTRACE".to_string(), "short".to_string()); 992 m.insert("RUST_BACKTRACE".to_string(), "short".to_string());
@@ -973,6 +995,7 @@ fn to_lsp_runnable(
973 cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()), 995 cwd: world.workspace_root_for(file_id).map(|root| root.to_string_lossy().to_string()),
974 }) 996 })
975} 997}
998
976fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> { 999fn highlight(world: &WorldSnapshot, file_id: FileId) -> Result<Vec<Decoration>> {
977 let line_index = world.analysis().file_line_index(file_id)?; 1000 let line_index = world.analysis().file_line_index(file_id)?;
978 let res = world 1001 let res = world
diff --git a/crates/rust-analyzer/src/req.rs b/crates/rust-analyzer/src/req.rs
index a3efe3b9f..9e27d3f1c 100644
--- a/crates/rust-analyzer/src/req.rs
+++ b/crates/rust-analyzer/src/req.rs
@@ -169,6 +169,7 @@ pub struct Runnable {
169 pub label: String, 169 pub label: String,
170 pub bin: String, 170 pub bin: String,
171 pub args: Vec<String>, 171 pub args: Vec<String>,
172 pub extra_args: Vec<String>,
172 pub env: FxHashMap<String, String>, 173 pub env: FxHashMap<String, String>,
173 pub cwd: Option<String>, 174 pub cwd: Option<String>,
174} 175}
@@ -217,6 +218,8 @@ impl Request for Ssr {
217} 218}
218 219
219#[derive(Debug, Deserialize, Serialize)] 220#[derive(Debug, Deserialize, Serialize)]
221#[serde(rename_all = "camelCase")]
220pub struct SsrParams { 222pub struct SsrParams {
221 pub arg: String, 223 pub query: String,
224 pub parse_only: bool,
222} 225}
diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/world.rs
index 058ce2af8..5743471bf 100644
--- a/crates/rust-analyzer/src/world.rs
+++ b/crates/rust-analyzer/src/world.rs
@@ -38,6 +38,7 @@ pub struct Options {
38 pub inlay_hints: InlayHintsOptions, 38 pub inlay_hints: InlayHintsOptions,
39 pub rustfmt_args: Vec<String>, 39 pub rustfmt_args: Vec<String>,
40 pub cargo_watch: CheckOptions, 40 pub cargo_watch: CheckOptions,
41 pub vscode_lldb: bool,
41} 42}
42 43
43/// `WorldState` is the primary mutable state of the language server 44/// `WorldState` is the primary mutable state of the language server
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs
index 970185dec..145429571 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/main.rs
@@ -75,7 +75,8 @@ fn foo() {
75 RunnablesParams { text_document: server.doc_id("lib.rs"), position: None }, 75 RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
76 json!([ 76 json!([
77 { 77 {
78 "args": [ "test", "--", "foo", "--nocapture" ], 78 "args": [ "test" ],
79 "extraArgs": [ "foo", "--nocapture" ],
79 "bin": "cargo", 80 "bin": "cargo",
80 "env": { "RUST_BACKTRACE": "short" }, 81 "env": { "RUST_BACKTRACE": "short" },
81 "cwd": null, 82 "cwd": null,
@@ -90,6 +91,7 @@ fn foo() {
90 "check", 91 "check",
91 "--all" 92 "--all"
92 ], 93 ],
94 "extraArgs": [],
93 "bin": "cargo", 95 "bin": "cargo",
94 "env": {}, 96 "env": {},
95 "cwd": null, 97 "cwd": null,
@@ -141,13 +143,11 @@ fn main() {}
141 143
142 server.wait_until_workspace_is_loaded(); 144 server.wait_until_workspace_is_loaded();
143 server.request::<Runnables>( 145 server.request::<Runnables>(
144 RunnablesParams { 146 RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
145 text_document: server.doc_id("foo/tests/spam.rs"),
146 position: None,
147 },
148 json!([ 147 json!([
149 { 148 {
150 "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--exact", "--nocapture" ], 149 "args": [ "test", "--package", "foo", "--test", "spam" ],
150 "extraArgs": [ "test_eggs", "--exact", "--nocapture" ],
151 "bin": "cargo", 151 "bin": "cargo",
152 "env": { "RUST_BACKTRACE": "short" }, 152 "env": { "RUST_BACKTRACE": "short" },
153 "label": "test test_eggs", 153 "label": "test test_eggs",
@@ -165,6 +165,7 @@ fn main() {}
165 "--test", 165 "--test",
166 "spam" 166 "spam"
167 ], 167 ],
168 "extraArgs": [],
168 "bin": "cargo", 169 "bin": "cargo",
169 "env": {}, 170 "env": {},
170 "cwd": server.path().join("foo"), 171 "cwd": server.path().join("foo"),
@@ -180,7 +181,7 @@ fn main() {}
180 } 181 }
181 } 182 }
182 } 183 }
183 ]) 184 ]),
184 ); 185 );
185} 186}
186 187
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index e28ae61fe..1d7062bdf 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -12,13 +12,14 @@ use lsp_types::{
12 notification::{DidOpenTextDocument, Exit}, 12 notification::{DidOpenTextDocument, Exit},
13 request::Shutdown, 13 request::Shutdown,
14 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, 14 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
15 TextDocumentIdentifier, TextDocumentItem, Url, 15 TextDocumentIdentifier, TextDocumentItem, Url, WorkDoneProgress,
16}; 16};
17use serde::Serialize; 17use serde::Serialize;
18use serde_json::{to_string_pretty, Value}; 18use serde_json::{to_string_pretty, Value};
19use tempfile::TempDir; 19use tempfile::TempDir;
20use test_utils::{find_mismatch, parse_fixture}; 20use test_utils::{find_mismatch, parse_fixture};
21 21
22use req::{ProgressParams, ProgressParamsValue};
22use rust_analyzer::{main_loop, req, ServerConfig}; 23use rust_analyzer::{main_loop, req, ServerConfig};
23 24
24pub struct Project<'a> { 25pub struct Project<'a> {
@@ -201,10 +202,14 @@ impl Server {
201 } 202 }
202 pub fn wait_until_workspace_is_loaded(&self) { 203 pub fn wait_until_workspace_is_loaded(&self) {
203 self.wait_for_message_cond(1, &|msg: &Message| match msg { 204 self.wait_for_message_cond(1, &|msg: &Message| match msg {
204 Message::Notification(n) if n.method == "window/showMessage" => { 205 Message::Notification(n) if n.method == "$/progress" => {
205 let msg = 206 match n.clone().extract::<ProgressParams>("$/progress").unwrap() {
206 n.clone().extract::<req::ShowMessageParams>("window/showMessage").unwrap(); 207 ProgressParams {
207 msg.message.starts_with("workspace loaded") 208 token: req::ProgressToken::String(ref token),
209 value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(_)),
210 } if token == "rustAnalyzer/startup" => true,
211 _ => false,
212 }
208 } 213 }
209 _ => false, 214 _ => false,
210 }) 215 })
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index a0d8f4d37..db03df1c4 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -397,6 +397,8 @@ pub fn skip_slow_tests() -> bool {
397 should_skip 397 should_skip
398} 398}
399 399
400const REWRITE: bool = false;
401
400/// Asserts that `expected` and `actual` strings are equal. If they differ only 402/// Asserts that `expected` and `actual` strings are equal. If they differ only
401/// in trailing or leading whitespace the test won't fail and 403/// in trailing or leading whitespace the test won't fail and
402/// the contents of `actual` will be written to the file located at `path`. 404/// the contents of `actual` will be written to the file located at `path`.
@@ -412,7 +414,6 @@ fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
412 fs::write(path, actual).unwrap(); 414 fs::write(path, actual).unwrap();
413 return; 415 return;
414 } 416 }
415 const REWRITE: bool = false;
416 if REWRITE { 417 if REWRITE {
417 println!("rewriting {}", pretty_path.display()); 418 println!("rewriting {}", pretty_path.display());
418 fs::write(path, actual).unwrap(); 419 fs::write(path, actual).unwrap();
diff --git a/docs/user/readme.adoc b/docs/user/readme.adoc
index 4e99dd0a6..2e6c6112f 100644
--- a/docs/user/readme.adoc
+++ b/docs/user/readme.adoc
@@ -65,6 +65,25 @@ Note that we only support the latest version of VS Code.
65 65
66The extension will be updated automatically as new versions become available. It will ask your permission to download the matching language server version binary if needed. 66The extension will be updated automatically as new versions become available. It will ask your permission to download the matching language server version binary if needed.
67 67
68===== Nightly
69
70We ship nightly releases for VS Code. To help us out with testing the newest code and follow the bleeding edge of our `master`, please use the following config:
71
72[source,json]
73----
74{ "rust-analyzer.updates.channel": "nightly" }
75----
76
77You will be prompted to install the `nightly` extension version. Just click `Download now` and from that moment you will get automatic updates each 24 hours.
78
79If you don't want to be asked for `Download now` every day when the new nightly version is released add the following to your `settings.json`:
80[source,json]
81----
82{ "rust-analyzer.updates.askBeforeDownload": false }
83----
84
85NOTE: Nightly extension should **only** be installed via the `Download now` action from VS Code.
86
68==== Building From Source 87==== Building From Source
69 88
70Alternatively, both the server and the plugin can be installed from source: 89Alternatively, both the server and the plugin can be installed from source:
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index b07964546..575dc7c4a 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "rust-analyzer", 2 "name": "rust-analyzer",
3 "version": "0.2.20200211-dev", 3 "version": "0.2.20200309-nightly",
4 "lockfileVersion": 1, 4 "lockfileVersion": 1,
5 "requires": true, 5 "requires": true,
6 "dependencies": { 6 "dependencies": {
diff --git a/editors/code/package.json b/editors/code/package.json
index 3aaae357a..faf10528d 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -6,7 +6,7 @@
6 "private": true, 6 "private": true,
7 "icon": "icon.png", 7 "icon": "icon.png",
8 "//": "The real version is in release.yaml, this one just needs to be bigger", 8 "//": "The real version is in release.yaml, this one just needs to be bigger",
9 "version": "0.2.20200211-dev", 9 "version": "0.2.20200309-nightly",
10 "publisher": "matklad", 10 "publisher": "matklad",
11 "repository": { 11 "repository": {
12 "url": "https://github.com/rust-analyzer/rust-analyzer.git", 12 "url": "https://github.com/rust-analyzer/rust-analyzer.git",
@@ -219,6 +219,19 @@
219 } 219 }
220 } 220 }
221 }, 221 },
222 "rust-analyzer.updates.channel": {
223 "type": "string",
224 "enum": [
225 "stable",
226 "nightly"
227 ],
228 "default": "stable",
229 "markdownEnumDescriptions": [
230 "`\"stable\"` updates are shipped weekly, they don't contain cutting-edge features from VSCode proposed APIs but have less bugs in general",
231 "`\"nightly\"` updates are shipped daily, they contain cutting-edge features and latest bug fixes. These releases help us get your feedback very quickly and speed up rust-analyzer development **drastically**"
232 ],
233 "markdownDescription": "Choose `\"nightly\"` updates to get the latest features and bug fixes every day. While `\"stable\"` releases occur weekly and don't contain cutting-edge features from VSCode proposed APIs"
234 },
222 "rust-analyzer.updates.askBeforeDownload": { 235 "rust-analyzer.updates.askBeforeDownload": {
223 "type": "boolean", 236 "type": "boolean",
224 "default": true, 237 "default": true,
@@ -235,7 +248,7 @@
235 "string" 248 "string"
236 ], 249 ],
237 "default": null, 250 "default": null,
238 "description": "Path to rust-analyzer executable (points to bundled binary by default)" 251 "description": "Path to rust-analyzer executable (points to bundled binary by default). If this is set, then \"rust-analyzer.updates.channel\" setting is not used"
239 }, 252 },
240 "rust-analyzer.excludeGlobs": { 253 "rust-analyzer.excludeGlobs": {
241 "type": "array", 254 "type": "array",
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index b2c830b30..d65454275 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -46,6 +46,7 @@ export async function createClient(config: Config, serverPath: string): Promise<
46 withSysroot: config.withSysroot, 46 withSysroot: config.withSysroot,
47 cargoFeatures: config.cargoFeatures, 47 cargoFeatures: config.cargoFeatures,
48 rustfmtArgs: config.rustfmtArgs, 48 rustfmtArgs: config.rustfmtArgs,
49 vscodeLldb: vscode.extensions.getExtension("vadimcn.vscode-lldb") != null,
49 }, 50 },
50 traceOutputChannel, 51 traceOutputChannel,
51 middleware: { 52 middleware: {
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index 06b513466..357155163 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -62,6 +62,26 @@ export function runSingle(ctx: Ctx): Cmd {
62 }; 62 };
63} 63}
64 64
65export function debugSingle(ctx: Ctx): Cmd {
66 return async (config: ra.Runnable) => {
67 const editor = ctx.activeRustEditor;
68 if (!editor) return;
69
70 const debugConfig = {
71 type: "lldb",
72 request: "launch",
73 name: config.label,
74 cargo: {
75 args: config.args,
76 },
77 args: config.extraArgs,
78 cwd: config.cwd
79 };
80
81 return vscode.debug.startDebugging(undefined, debugConfig);
82 };
83}
84
65class RunnableQuickPick implements vscode.QuickPickItem { 85class RunnableQuickPick implements vscode.QuickPickItem {
66 public label: string; 86 public label: string;
67 public description?: string | undefined; 87 public description?: string | undefined;
@@ -87,7 +107,7 @@ function createTask(spec: ra.Runnable): vscode.Task {
87 type: 'cargo', 107 type: 'cargo',
88 label: spec.label, 108 label: spec.label,
89 command: spec.bin, 109 command: spec.bin,
90 args: spec.args, 110 args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args,
91 env: spec.env, 111 env: spec.env,
92 }; 112 };
93 113
diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts
index 421301b42..c4d84b443 100644
--- a/editors/code/src/commands/server_version.ts
+++ b/editors/code/src/commands/server_version.ts
@@ -5,7 +5,7 @@ import { spawnSync } from 'child_process';
5 5
6export function serverVersion(ctx: Ctx): Cmd { 6export function serverVersion(ctx: Ctx): Cmd {
7 return async () => { 7 return async () => {
8 const binaryPath = await ensureServerBinary(ctx.config.serverSource); 8 const binaryPath = await ensureServerBinary(ctx.config);
9 9
10 if (binaryPath == null) { 10 if (binaryPath == null) {
11 throw new Error( 11 throw new Error(
@@ -18,4 +18,3 @@ export function serverVersion(ctx: Ctx): Cmd {
18 vscode.window.showInformationMessage('rust-analyzer version : ' + version); 18 vscode.window.showInformationMessage('rust-analyzer version : ' + version);
19 }; 19 };
20} 20}
21
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts
index eee48c693..6fee051fd 100644
--- a/editors/code/src/commands/ssr.ts
+++ b/editors/code/src/commands/ssr.ts
@@ -10,20 +10,22 @@ export function ssr(ctx: Ctx): Cmd {
10 if (!client) return; 10 if (!client) return;
11 11
12 const options: vscode.InputBoxOptions = { 12 const options: vscode.InputBoxOptions = {
13 placeHolder: "foo($a:expr, $b:expr) ==>> bar($a, foo($b))", 13 value: "() ==>> ()",
14 prompt: "Enter request", 14 prompt: "EnteR request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
15 validateInput: (x: string) => { 15 validateInput: async (x: string) => {
16 if (x.includes('==>>')) { 16 try {
17 return null; 17 await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
18 } catch (e) {
19 return e.toString();
18 } 20 }
19 return "Enter request: pattern ==>> template"; 21 return null;
20 } 22 }
21 }; 23 };
22 const request = await vscode.window.showInputBox(options); 24 const request = await vscode.window.showInputBox(options);
23 25
24 if (!request) return; 26 if (!request) return;
25 27
26 const change = await client.sendRequest(ra.ssr, { arg: request }); 28 const change = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
27 29
28 await applySourceChange(ctx, change); 30 await applySourceChange(ctx, change);
29 }; 31 };
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index 6db073bec..f63e1d20e 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -1,7 +1,7 @@
1import * as os from "os"; 1import * as os from "os";
2import * as vscode from 'vscode'; 2import * as vscode from 'vscode';
3import { ArtifactSource } from "./installation/interfaces"; 3import { ArtifactSource } from "./installation/interfaces";
4import { log } from "./util"; 4import { log, vscodeReloadWindow } from "./util";
5 5
6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 6const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
7 7
@@ -23,25 +23,40 @@ export interface CargoFeatures {
23 allFeatures: boolean; 23 allFeatures: boolean;
24 features: string[]; 24 features: string[];
25} 25}
26
27export const enum UpdatesChannel {
28 Stable = "stable",
29 Nightly = "nightly"
30}
31
32export const NIGHTLY_TAG = "nightly";
26export class Config { 33export class Config {
27 private static readonly rootSection = "rust-analyzer"; 34 readonly extensionId = "matklad.rust-analyzer";
28 private static readonly requiresReloadOpts = [ 35
36 private readonly rootSection = "rust-analyzer";
37 private readonly requiresReloadOpts = [
38 "serverPath",
29 "cargoFeatures", 39 "cargoFeatures",
30 "cargo-watch", 40 "cargo-watch",
31 "highlighting.semanticTokens", 41 "highlighting.semanticTokens",
32 "inlayHints", 42 "inlayHints",
33 ] 43 ]
34 .map(opt => `${Config.rootSection}.${opt}`); 44 .map(opt => `${this.rootSection}.${opt}`);
35 45
36 private static readonly extensionVersion: string = (() => { 46 readonly packageJsonVersion = vscode
37 const packageJsonVersion = vscode 47 .extensions
38 .extensions 48 .getExtension(this.extensionId)!
39 .getExtension("matklad.rust-analyzer")! 49 .packageJSON
40 .packageJSON 50 .version as string; // n.n.YYYYMMDD[-nightly]
41 .version as string; // n.n.YYYYMMDD 51
52 /**
53 * Either `nightly` or `YYYY-MM-DD` (i.e. `stable` release)
54 */
55 readonly extensionReleaseTag: string = (() => {
56 if (this.packageJsonVersion.endsWith(NIGHTLY_TAG)) return NIGHTLY_TAG;
42 57
43 const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/; 58 const realVersionRegexp = /^\d+\.\d+\.(\d{4})(\d{2})(\d{2})/;
44 const [, yyyy, mm, dd] = packageJsonVersion.match(realVersionRegexp)!; 59 const [, yyyy, mm, dd] = this.packageJsonVersion.match(realVersionRegexp)!;
45 60
46 return `${yyyy}-${mm}-${dd}`; 61 return `${yyyy}-${mm}-${dd}`;
47 })(); 62 })();
@@ -54,16 +69,19 @@ export class Config {
54 } 69 }
55 70
56 private refreshConfig() { 71 private refreshConfig() {
57 this.cfg = vscode.workspace.getConfiguration(Config.rootSection); 72 this.cfg = vscode.workspace.getConfiguration(this.rootSection);
58 const enableLogging = this.cfg.get("trace.extension") as boolean; 73 const enableLogging = this.cfg.get("trace.extension") as boolean;
59 log.setEnabled(enableLogging); 74 log.setEnabled(enableLogging);
60 log.debug("Using configuration:", this.cfg); 75 log.debug(
76 "Extension version:", this.packageJsonVersion,
77 "using configuration:", this.cfg
78 );
61 } 79 }
62 80
63 private async onConfigChange(event: vscode.ConfigurationChangeEvent) { 81 private async onConfigChange(event: vscode.ConfigurationChangeEvent) {
64 this.refreshConfig(); 82 this.refreshConfig();
65 83
66 const requiresReloadOpt = Config.requiresReloadOpts.find( 84 const requiresReloadOpt = this.requiresReloadOpts.find(
67 opt => event.affectsConfiguration(opt) 85 opt => event.affectsConfiguration(opt)
68 ); 86 );
69 87
@@ -75,7 +93,7 @@ export class Config {
75 ); 93 );
76 94
77 if (userResponse === "Reload now") { 95 if (userResponse === "Reload now") {
78 vscode.commands.executeCommand("workbench.action.reloadWindow"); 96 await vscodeReloadWindow();
79 } 97 }
80 } 98 }
81 99
@@ -121,8 +139,14 @@ export class Config {
121 } 139 }
122 } 140 }
123 141
142 get installedExtensionUpdateChannel(): UpdatesChannel {
143 return this.extensionReleaseTag === NIGHTLY_TAG
144 ? UpdatesChannel.Nightly
145 : UpdatesChannel.Stable;
146 }
147
124 get serverSource(): null | ArtifactSource { 148 get serverSource(): null | ArtifactSource {
125 const serverPath = RA_LSP_DEBUG ?? this.cfg.get<null | string>("serverPath"); 149 const serverPath = RA_LSP_DEBUG ?? this.serverPath;
126 150
127 if (serverPath) { 151 if (serverPath) {
128 return { 152 return {
@@ -135,13 +159,18 @@ export class Config {
135 159
136 if (!prebuiltBinaryName) return null; 160 if (!prebuiltBinaryName) return null;
137 161
162 return this.createGithubReleaseSource(
163 prebuiltBinaryName,
164 this.extensionReleaseTag
165 );
166 }
167
168 private createGithubReleaseSource(file: string, tag: string): ArtifactSource.GithubRelease {
138 return { 169 return {
139 type: ArtifactSource.Type.GithubRelease, 170 type: ArtifactSource.Type.GithubRelease,
171 file,
172 tag,
140 dir: this.ctx.globalStoragePath, 173 dir: this.ctx.globalStoragePath,
141 file: prebuiltBinaryName,
142 storage: this.ctx.globalState,
143 tag: Config.extensionVersion,
144 askBeforeDownload: this.cfg.get("updates.askBeforeDownload") as boolean,
145 repo: { 174 repo: {
146 name: "rust-analyzer", 175 name: "rust-analyzer",
147 owner: "rust-analyzer", 176 owner: "rust-analyzer",
@@ -149,9 +178,23 @@ export class Config {
149 }; 178 };
150 } 179 }
151 180
181 get nightlyVsixSource(): ArtifactSource.GithubRelease {
182 return this.createGithubReleaseSource("rust-analyzer.vsix", NIGHTLY_TAG);
183 }
184
185 readonly installedNightlyExtensionReleaseDate = new DateStorage(
186 "installed-nightly-extension-release-date",
187 this.ctx.globalState
188 );
189 readonly serverReleaseDate = new DateStorage("server-release-date", this.ctx.globalState);
190 readonly serverReleaseTag = new Storage<null | string>("server-release-tag", this.ctx.globalState, null);
191
152 // We don't do runtime config validation here for simplicity. More on stackoverflow: 192 // We don't do runtime config validation here for simplicity. More on stackoverflow:
153 // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension 193 // https://stackoverflow.com/questions/60135780/what-is-the-best-way-to-type-check-the-configuration-for-vscode-extension
154 194
195 private get serverPath() { return this.cfg.get("serverPath") as null | string; }
196 get updatesChannel() { return this.cfg.get("updates.channel") as UpdatesChannel; }
197 get askBeforeDownload() { return this.cfg.get("updates.askBeforeDownload") as boolean; }
155 get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; } 198 get highlightingSemanticTokens() { return this.cfg.get("highlighting.semanticTokens") as boolean; }
156 get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; } 199 get highlightingOn() { return this.cfg.get("highlightingOn") as boolean; }
157 get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; } 200 get rainbowHighlightingOn() { return this.cfg.get("rainbowHighlightingOn") as boolean; }
@@ -189,3 +232,37 @@ export class Config {
189 // for internal use 232 // for internal use
190 get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; } 233 get withSysroot() { return this.cfg.get("withSysroot", true) as boolean; }
191} 234}
235
236export class Storage<T> {
237 constructor(
238 private readonly key: string,
239 private readonly storage: vscode.Memento,
240 private readonly defaultVal: T
241 ) { }
242
243 get(): T {
244 const val = this.storage.get(this.key, this.defaultVal);
245 log.debug(this.key, "==", val);
246 return val;
247 }
248 async set(val: T) {
249 log.debug(this.key, "=", val);
250 await this.storage.update(this.key, val);
251 }
252}
253export class DateStorage {
254 inner: Storage<null | string>;
255
256 constructor(key: string, storage: vscode.Memento) {
257 this.inner = new Storage(key, storage, null);
258 }
259
260 get(): null | Date {
261 const dateStr = this.inner.get();
262 return dateStr ? new Date(dateStr) : null;
263 }
264
265 async set(date: null | Date) {
266 await this.inner.set(date ? date.toString() : null);
267 }
268}
diff --git a/editors/code/src/installation/download_artifact.ts b/editors/code/src/installation/download_artifact.ts
deleted file mode 100644
index 97e4d67c2..000000000
--- a/editors/code/src/installation/download_artifact.ts
+++ /dev/null
@@ -1,50 +0,0 @@
1import * as vscode from "vscode";
2import * as path from "path";
3import { promises as fs } from "fs";
4
5import { ArtifactReleaseInfo } from "./interfaces";
6import { downloadFile } from "./download_file";
7import { assert } from "../util";
8
9/**
10 * Downloads artifact from given `downloadUrl`.
11 * Creates `installationDir` if it is not yet created and put the artifact under
12 * `artifactFileName`.
13 * Displays info about the download progress in an info message printing the name
14 * of the artifact as `displayName`.
15 */
16export async function downloadArtifact(
17 { downloadUrl, releaseName }: ArtifactReleaseInfo,
18 artifactFileName: string,
19 installationDir: string,
20 displayName: string,
21) {
22 await fs.mkdir(installationDir).catch(err => assert(
23 err?.code === "EEXIST",
24 `Couldn't create directory "${installationDir}" to download ` +
25 `${artifactFileName} artifact: ${err?.message}`
26 ));
27
28 const installationPath = path.join(installationDir, artifactFileName);
29
30 await vscode.window.withProgress(
31 {
32 location: vscode.ProgressLocation.Notification,
33 cancellable: false, // FIXME: add support for canceling download?
34 title: `Downloading ${displayName} (${releaseName})`
35 },
36 async (progress, _cancellationToken) => {
37 let lastPrecentage = 0;
38 const filePermissions = 0o755; // (rwx, r_x, r_x)
39 await downloadFile(downloadUrl, installationPath, filePermissions, (readBytes, totalBytes) => {
40 const newPercentage = (readBytes / totalBytes) * 100;
41 progress.report({
42 message: newPercentage.toFixed(0) + "%",
43 increment: newPercentage - lastPrecentage
44 });
45
46 lastPrecentage = newPercentage;
47 });
48 }
49 );
50}
diff --git a/editors/code/src/installation/download_file.ts b/editors/code/src/installation/downloads.ts
index ee8949d61..7ce2e2960 100644
--- a/editors/code/src/installation/download_file.ts
+++ b/editors/code/src/installation/downloads.ts
@@ -1,8 +1,11 @@
1import fetch from "node-fetch"; 1import fetch from "node-fetch";
2import * as vscode from "vscode";
3import * as path from "path";
2import * as fs from "fs"; 4import * as fs from "fs";
3import * as stream from "stream"; 5import * as stream from "stream";
4import * as util from "util"; 6import * as util from "util";
5import { log, assert } from "../util"; 7import { log, assert } from "../util";
8import { ArtifactReleaseInfo } from "./interfaces";
6 9
7const pipeline = util.promisify(stream.pipeline); 10const pipeline = util.promisify(stream.pipeline);
8 11
@@ -49,3 +52,46 @@ export async function downloadFile(
49 // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776 52 // Issue at nodejs repo: https://github.com/nodejs/node/issues/31776
50 }); 53 });
51} 54}
55
56/**
57 * Downloads artifact from given `downloadUrl`.
58 * Creates `installationDir` if it is not yet created and puts the artifact under
59 * `artifactFileName`.
60 * Displays info about the download progress in an info message printing the name
61 * of the artifact as `displayName`.
62 */
63export async function downloadArtifactWithProgressUi(
64 { downloadUrl, releaseName }: ArtifactReleaseInfo,
65 artifactFileName: string,
66 installationDir: string,
67 displayName: string,
68) {
69 await fs.promises.mkdir(installationDir).catch(err => assert(
70 err?.code === "EEXIST",
71 `Couldn't create directory "${installationDir}" to download ` +
72 `${artifactFileName} artifact: ${err?.message}`
73 ));
74
75 const installationPath = path.join(installationDir, artifactFileName);
76
77 await vscode.window.withProgress(
78 {
79 location: vscode.ProgressLocation.Notification,
80 cancellable: false, // FIXME: add support for canceling download?
81 title: `Downloading rust-analyzer ${displayName} (${releaseName})`
82 },
83 async (progress, _cancellationToken) => {
84 let lastPrecentage = 0;
85 const filePermissions = 0o755; // (rwx, r_x, r_x)
86 await downloadFile(downloadUrl, installationPath, filePermissions, (readBytes, totalBytes) => {
87 const newPercentage = (readBytes / totalBytes) * 100;
88 progress.report({
89 message: newPercentage.toFixed(0) + "%",
90 increment: newPercentage - lastPrecentage
91 });
92
93 lastPrecentage = newPercentage;
94 });
95 }
96 );
97}
diff --git a/editors/code/src/installation/extension.ts b/editors/code/src/installation/extension.ts
new file mode 100644
index 000000000..eea6fded2
--- /dev/null
+++ b/editors/code/src/installation/extension.ts
@@ -0,0 +1,144 @@
1import * as vscode from "vscode";
2import * as path from "path";
3import { promises as fs } from 'fs';
4
5import { vscodeReinstallExtension, vscodeReloadWindow, log, vscodeInstallExtensionFromVsix, assert, notReentrant } from "../util";
6import { Config, UpdatesChannel } from "../config";
7import { ArtifactReleaseInfo, ArtifactSource } from "./interfaces";
8import { downloadArtifactWithProgressUi } from "./downloads";
9import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
10
11const HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS = 25;
12
13/**
14 * Installs `stable` or latest `nightly` version or does nothing if the current
15 * extension version is what's needed according to `desiredUpdateChannel`.
16 */
17export async function ensureProperExtensionVersion(config: Config): Promise<never | void> {
18 // User has built lsp server from sources, she should manage updates manually
19 if (config.serverSource?.type === ArtifactSource.Type.ExplicitPath) return;
20
21 const currentUpdChannel = config.installedExtensionUpdateChannel;
22 const desiredUpdChannel = config.updatesChannel;
23
24 if (currentUpdChannel === UpdatesChannel.Stable) {
25 // Release date is present only when we are on nightly
26 await config.installedNightlyExtensionReleaseDate.set(null);
27 }
28
29 if (desiredUpdChannel === UpdatesChannel.Stable) {
30 // VSCode should handle updates for stable channel
31 if (currentUpdChannel === UpdatesChannel.Stable) return;
32
33 if (!await askToDownloadProperExtensionVersion(config)) return;
34
35 await vscodeReinstallExtension(config.extensionId);
36 await vscodeReloadWindow(); // never returns
37 }
38
39 if (currentUpdChannel === UpdatesChannel.Stable) {
40 if (!await askToDownloadProperExtensionVersion(config)) return;
41
42 return await tryDownloadNightlyExtension(config);
43 }
44
45 const currentExtReleaseDate = config.installedNightlyExtensionReleaseDate.get();
46
47 if (currentExtReleaseDate === null) {
48 void vscode.window.showErrorMessage(
49 "Nightly release date must've been set during the installation. " +
50 "Did you download and install the nightly .vsix package manually?"
51 );
52 throw new Error("Nightly release date was not set in globalStorage");
53 }
54
55 const dateNow = new Date;
56 const hoursSinceLastUpdate = diffInHours(currentExtReleaseDate, dateNow);
57 log.debug(
58 "Current rust-analyzer nightly was downloaded", hoursSinceLastUpdate,
59 "hours ago, namely:", currentExtReleaseDate, "and now is", dateNow
60 );
61
62 if (hoursSinceLastUpdate < HEURISTIC_NIGHTLY_RELEASE_PERIOD_IN_HOURS) {
63 return;
64 }
65 if (!await askToDownloadProperExtensionVersion(config, "The installed nightly version is most likely outdated. ")) {
66 return;
67 }
68
69 await tryDownloadNightlyExtension(config, releaseInfo => {
70 assert(
71 currentExtReleaseDate.getTime() === config.installedNightlyExtensionReleaseDate.get()?.getTime(),
72 "Other active VSCode instance has reinstalled the extension"
73 );
74
75 if (releaseInfo.releaseDate.getTime() === currentExtReleaseDate.getTime()) {
76 vscode.window.showInformationMessage(
77 "Whoops, it appears that your nightly version is up-to-date. " +
78 "There might be some problems with the upcomming nightly release " +
79 "or you traveled too far into the future. Sorry for that 😅! "
80 );
81 return false;
82 }
83 return true;
84 });
85}
86
87async function askToDownloadProperExtensionVersion(config: Config, reason = "") {
88 if (!config.askBeforeDownload) return true;
89
90 const stableOrNightly = config.updatesChannel === UpdatesChannel.Stable ? "stable" : "latest nightly";
91
92 // In case of reentering this function and showing the same info message
93 // (e.g. after we had shown this message, the user changed the config)
94 // vscode will dismiss the already shown one (i.e. return undefined).
95 // This behaviour is what we want, but likely it is not documented
96
97 const userResponse = await vscode.window.showInformationMessage(
98 reason + `Do you want to download the ${stableOrNightly} rust-analyzer extension ` +
99 `version and reload the window now?`,
100 "Download now", "Cancel"
101 );
102 return userResponse === "Download now";
103}
104
105/**
106 * Shutdowns the process in case of success (i.e. reloads the window) or throws an error.
107 *
108 * ACHTUNG!: this function has a crazy amount of state transitions, handling errors during
109 * each of them would result in a ton of code (especially accounting for cross-process
110 * shared mutable `globalState` access). Enforcing no reentrancy for this is best-effort.
111 */
112const tryDownloadNightlyExtension = notReentrant(async (
113 config: Config,
114 shouldDownload: (releaseInfo: ArtifactReleaseInfo) => boolean = () => true
115): Promise<never | void> => {
116 const vsixSource = config.nightlyVsixSource;
117 try {
118 const releaseInfo = await fetchArtifactReleaseInfo(vsixSource.repo, vsixSource.file, vsixSource.tag);
119
120 if (!shouldDownload(releaseInfo)) return;
121
122 await downloadArtifactWithProgressUi(releaseInfo, vsixSource.file, vsixSource.dir, "nightly extension");
123
124 const vsixPath = path.join(vsixSource.dir, vsixSource.file);
125
126 await vscodeInstallExtensionFromVsix(vsixPath);
127 await config.installedNightlyExtensionReleaseDate.set(releaseInfo.releaseDate);
128 await fs.unlink(vsixPath);
129
130 await vscodeReloadWindow(); // never returns
131 } catch (err) {
132 log.downloadError(err, "nightly extension", vsixSource.repo.name);
133 }
134});
135
136function diffInHours(a: Date, b: Date): number {
137 // Discard the time and time-zone information (to abstract from daylight saving time bugs)
138 // https://stackoverflow.com/a/15289883/9259330
139
140 const utcA = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
141 const utcB = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
142
143 return (utcA - utcB) / (1000 * 60 * 60);
144}
diff --git a/editors/code/src/installation/fetch_artifact_release_info.ts b/editors/code/src/installation/fetch_artifact_release_info.ts
index b1b5a3485..1ad3b8338 100644
--- a/editors/code/src/installation/fetch_artifact_release_info.ts
+++ b/editors/code/src/installation/fetch_artifact_release_info.ts
@@ -59,12 +59,15 @@ export async function fetchArtifactReleaseInfo(
59 59
60 return { 60 return {
61 releaseName: release.name, 61 releaseName: release.name,
62 releaseDate: new Date(release.published_at),
62 downloadUrl: artifact.browser_download_url 63 downloadUrl: artifact.browser_download_url
63 }; 64 };
64 65
65 // We omit declaration of tremendous amount of fields that we are not using here 66 // We omit declaration of tremendous amount of fields that we are not using here
66 interface GithubRelease { 67 interface GithubRelease {
67 name: string; 68 name: string;
69 // eslint-disable-next-line camelcase
70 published_at: string;
68 assets: Array<{ 71 assets: Array<{
69 name: string; 72 name: string;
70 // eslint-disable-next-line camelcase 73 // eslint-disable-next-line camelcase
diff --git a/editors/code/src/installation/interfaces.ts b/editors/code/src/installation/interfaces.ts
index 50b635921..1a8ea0884 100644
--- a/editors/code/src/installation/interfaces.ts
+++ b/editors/code/src/installation/interfaces.ts
@@ -1,5 +1,3 @@
1import * as vscode from "vscode";
2
3export interface GithubRepo { 1export interface GithubRepo {
4 name: string; 2 name: string;
5 owner: string; 3 owner: string;
@@ -9,6 +7,7 @@ export interface GithubRepo {
9 * Metadata about particular artifact retrieved from GitHub releases. 7 * Metadata about particular artifact retrieved from GitHub releases.
10 */ 8 */
11export interface ArtifactReleaseInfo { 9export interface ArtifactReleaseInfo {
10 releaseDate: Date;
12 releaseName: string; 11 releaseName: string;
13 downloadUrl: string; 12 downloadUrl: string;
14} 13}
@@ -42,6 +41,9 @@ export namespace ArtifactSource {
42 */ 41 */
43 repo: GithubRepo; 42 repo: GithubRepo;
44 43
44
45 // FIXME: add installationPath: string;
46
45 /** 47 /**
46 * Directory on the filesystem where the bundled binary is stored. 48 * Directory on the filesystem where the bundled binary is stored.
47 */ 49 */
@@ -57,17 +59,5 @@ export namespace ArtifactSource {
57 * Tag of github release that denotes a version required by this extension. 59 * Tag of github release that denotes a version required by this extension.
58 */ 60 */
59 tag: string; 61 tag: string;
60
61 /**
62 * Object that provides `get()/update()` operations to store metadata
63 * about the actual binary, e.g. its actual version.
64 */
65 storage: vscode.Memento;
66
67 /**
68 * Ask for the user permission before downloading the artifact.
69 */
70 askBeforeDownload: boolean;
71 } 62 }
72
73} 63}
diff --git a/editors/code/src/installation/server.ts b/editors/code/src/installation/server.ts
index ef1c45ff6..05730a778 100644
--- a/editors/code/src/installation/server.ts
+++ b/editors/code/src/installation/server.ts
@@ -1,14 +1,16 @@
1import * as vscode from "vscode"; 1import * as vscode from "vscode";
2import * as path from "path"; 2import * as path from "path";
3import { promises as dns } from "dns";
4import { spawnSync } from "child_process"; 3import { spawnSync } from "child_process";
5 4
6import { ArtifactSource } from "./interfaces"; 5import { ArtifactSource } from "./interfaces";
7import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info"; 6import { fetchArtifactReleaseInfo } from "./fetch_artifact_release_info";
8import { downloadArtifact } from "./download_artifact"; 7import { downloadArtifactWithProgressUi } from "./downloads";
9import { log, assert } from "../util"; 8import { log, assert, notReentrant } from "../util";
9import { Config, NIGHTLY_TAG } from "../config";
10
11export async function ensureServerBinary(config: Config): Promise<null | string> {
12 const source = config.serverSource;
10 13
11export async function ensureServerBinary(source: null | ArtifactSource): Promise<null | string> {
12 if (!source) { 14 if (!source) {
13 vscode.window.showErrorMessage( 15 vscode.window.showErrorMessage(
14 "Unfortunately we don't ship binaries for your platform yet. " + 16 "Unfortunately we don't ship binaries for your platform yet. " +
@@ -35,18 +37,11 @@ export async function ensureServerBinary(source: null | ArtifactSource): Promise
35 return null; 37 return null;
36 } 38 }
37 case ArtifactSource.Type.GithubRelease: { 39 case ArtifactSource.Type.GithubRelease: {
38 const prebuiltBinaryPath = path.join(source.dir, source.file); 40 if (!shouldDownloadServer(source, config)) {
39 41 return path.join(source.dir, source.file);
40 const installedVersion: null | string = getServerVersion(source.storage);
41 const requiredVersion: string = source.tag;
42
43 log.debug("Installed version:", installedVersion, "required:", requiredVersion);
44
45 if (isBinaryAvailable(prebuiltBinaryPath) && installedVersion === requiredVersion) {
46 return prebuiltBinaryPath;
47 } 42 }
48 43
49 if (source.askBeforeDownload) { 44 if (config.askBeforeDownload) {
50 const userResponse = await vscode.window.showInformationMessage( 45 const userResponse = await vscode.window.showInformationMessage(
51 `Language server version ${source.tag} for rust-analyzer is not installed. ` + 46 `Language server version ${source.tag} for rust-analyzer is not installed. ` +
52 "Do you want to download it now?", 47 "Do you want to download it now?",
@@ -55,38 +50,56 @@ export async function ensureServerBinary(source: null | ArtifactSource): Promise
55 if (userResponse !== "Download now") return null; 50 if (userResponse !== "Download now") return null;
56 } 51 }
57 52
58 if (!await downloadServer(source)) return null; 53 return await downloadServer(source, config);
59
60 return prebuiltBinaryPath;
61 } 54 }
62 } 55 }
63} 56}
64 57
65async function downloadServer(source: ArtifactSource.GithubRelease): Promise<boolean> { 58function shouldDownloadServer(
59 source: ArtifactSource.GithubRelease,
60 config: Config
61): boolean {
62 if (!isBinaryAvailable(path.join(source.dir, source.file))) return true;
63
64 const installed = {
65 tag: config.serverReleaseTag.get(),
66 date: config.serverReleaseDate.get()
67 };
68 const required = {
69 tag: source.tag,
70 date: config.installedNightlyExtensionReleaseDate.get()
71 };
72
73 log.debug("Installed server:", installed, "required:", required);
74
75 if (required.tag !== NIGHTLY_TAG || installed.tag !== NIGHTLY_TAG) {
76 return required.tag !== installed.tag;
77 }
78
79 assert(required.date !== null, "Extension release date should have been saved during its installation");
80 assert(installed.date !== null, "Server release date should have been saved during its installation");
81
82 return installed.date.getTime() !== required.date.getTime();
83}
84
85/**
86 * Enforcing no reentrancy for this is best-effort.
87 */
88const downloadServer = notReentrant(async (
89 source: ArtifactSource.GithubRelease,
90 config: Config,
91): Promise<null | string> => {
66 try { 92 try {
67 const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag); 93 const releaseInfo = await fetchArtifactReleaseInfo(source.repo, source.file, source.tag);
68 94
69 await downloadArtifact(releaseInfo, source.file, source.dir, "language server"); 95 await downloadArtifactWithProgressUi(releaseInfo, source.file, source.dir, "language server");
70 await setServerVersion(source.storage, releaseInfo.releaseName); 96 await Promise.all([
97 config.serverReleaseTag.set(releaseInfo.releaseName),
98 config.serverReleaseDate.set(releaseInfo.releaseDate)
99 ]);
71 } catch (err) { 100 } catch (err) {
72 vscode.window.showErrorMessage( 101 log.downloadError(err, "language server", source.repo.name);
73 `Failed to download language server from ${source.repo.name} ` + 102 return null;
74 `GitHub repository: ${err.message}`
75 );
76
77 log.error(err);
78
79 dns.resolve('example.com').then(
80 addrs => log.debug("DNS resolution for example.com was successful", addrs),
81 err => {
82 log.error(
83 "DNS resolution for example.com failed, " +
84 "there might be an issue with Internet availability"
85 );
86 log.error(err);
87 }
88 );
89 return false;
90 } 103 }
91 104
92 const binaryPath = path.join(source.dir, source.file); 105 const binaryPath = path.join(source.dir, source.file);
@@ -101,8 +114,8 @@ async function downloadServer(source: ArtifactSource.GithubRelease): Promise<boo
101 "Rust analyzer language server was successfully installed 🦀" 114 "Rust analyzer language server was successfully installed 🦀"
102 ); 115 );
103 116
104 return true; 117 return binaryPath;
105} 118});
106 119
107function isBinaryAvailable(binaryPath: string): boolean { 120function isBinaryAvailable(binaryPath: string): boolean {
108 const res = spawnSync(binaryPath, ["--version"]); 121 const res = spawnSync(binaryPath, ["--version"]);
@@ -115,14 +128,3 @@ function isBinaryAvailable(binaryPath: string): boolean {
115 128
116 return res.status === 0; 129 return res.status === 0;
117} 130}
118
119function getServerVersion(storage: vscode.Memento): null | string {
120 const version = storage.get<null | string>("server-version", null);
121 log.debug("Get server-version:", version);
122 return version;
123}
124
125async function setServerVersion(storage: vscode.Memento, version: string): Promise<void> {
126 log.debug("Set server-version:", version);
127 await storage.update("server-version", version.toString());
128}
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index ecf53cf77..bd4661a36 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -8,6 +8,7 @@ import { activateHighlighting } from './highlighting';
8import { ensureServerBinary } from './installation/server'; 8import { ensureServerBinary } from './installation/server';
9import { Config } from './config'; 9import { Config } from './config';
10import { log } from './util'; 10import { log } from './util';
11import { ensureProperExtensionVersion } from './installation/extension';
11 12
12let ctx: Ctx | undefined; 13let ctx: Ctx | undefined;
13 14
@@ -34,7 +35,13 @@ export async function activate(context: vscode.ExtensionContext) {
34 35
35 const config = new Config(context); 36 const config = new Config(context);
36 37
37 const serverPath = await ensureServerBinary(config.serverSource); 38 vscode.workspace.onDidChangeConfiguration(() => ensureProperExtensionVersion(config).catch(log.error));
39
40 // Don't await the user response here, otherwise we will block the lsp server bootstrap
41 void ensureProperExtensionVersion(config).catch(log.error);
42
43 const serverPath = await ensureServerBinary(config);
44
38 if (serverPath == null) { 45 if (serverPath == null) {
39 throw new Error( 46 throw new Error(
40 "Rust Analyzer Language Server is not available. " + 47 "Rust Analyzer Language Server is not available. " +
@@ -83,6 +90,7 @@ export async function activate(context: vscode.ExtensionContext) {
83 90
84 // Internal commands which are invoked by the server. 91 // Internal commands which are invoked by the server.
85 ctx.registerCommand('runSingle', commands.runSingle); 92 ctx.registerCommand('runSingle', commands.runSingle);
93 ctx.registerCommand('debugSingle', commands.debugSingle);
86 ctx.registerCommand('showReferences', commands.showReferences); 94 ctx.registerCommand('showReferences', commands.showReferences);
87 ctx.registerCommand('applySourceChange', commands.applySourceChange); 95 ctx.registerCommand('applySourceChange', commands.applySourceChange);
88 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange); 96 ctx.registerCommand('selectAndApplySourceChange', commands.selectAndApplySourceChange);
diff --git a/editors/code/src/rust-analyzer-api.ts b/editors/code/src/rust-analyzer-api.ts
index bd6e3ada0..9846f7343 100644
--- a/editors/code/src/rust-analyzer-api.ts
+++ b/editors/code/src/rust-analyzer-api.ts
@@ -80,13 +80,12 @@ export interface Runnable {
80 label: string; 80 label: string;
81 bin: string; 81 bin: string;
82 args: Vec<string>; 82 args: Vec<string>;
83 extraArgs: Vec<string>;
83 env: FxHashMap<string, string>; 84 env: FxHashMap<string, string>;
84 cwd: Option<string>; 85 cwd: Option<string>;
85} 86}
86export const runnables = request<RunnablesParams, Vec<Runnable>>("runnables"); 87export const runnables = request<RunnablesParams, Vec<Runnable>>("runnables");
87 88
88
89
90export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint; 89export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint;
91 90
92export namespace InlayHint { 91export namespace InlayHint {
@@ -108,7 +107,8 @@ export const inlayHints = request<InlayHintsParams, Vec<InlayHint>>("inlayHints"
108 107
109 108
110export interface SsrParams { 109export interface SsrParams {
111 arg: string; 110 query: string;
111 parseOnly: boolean;
112} 112}
113export const ssr = request<SsrParams, SourceChange>("ssr"); 113export const ssr = request<SsrParams, SourceChange>("ssr");
114 114
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index 95a5f1227..2bfc145e6 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -1,5 +1,6 @@
1import * as lc from "vscode-languageclient"; 1import * as lc from "vscode-languageclient";
2import * as vscode from "vscode"; 2import * as vscode from "vscode";
3import { promises as dns } from "dns";
3import { strict as nativeAssert } from "assert"; 4import { strict as nativeAssert } from "assert";
4 5
5export function assert(condition: boolean, explanation: string): asserts condition { 6export function assert(condition: boolean, explanation: string): asserts condition {
@@ -11,21 +12,40 @@ export function assert(condition: boolean, explanation: string): asserts conditi
11 } 12 }
12} 13}
13 14
14export const log = { 15export const log = new class {
15 enabled: true, 16 private enabled = true;
17
18 setEnabled(yes: boolean): void {
19 log.enabled = yes;
20 }
21
16 debug(message?: any, ...optionalParams: any[]): void { 22 debug(message?: any, ...optionalParams: any[]): void {
17 if (!log.enabled) return; 23 if (!log.enabled) return;
18 // eslint-disable-next-line no-console 24 // eslint-disable-next-line no-console
19 console.log(message, ...optionalParams); 25 console.log(message, ...optionalParams);
20 }, 26 }
27
21 error(message?: any, ...optionalParams: any[]): void { 28 error(message?: any, ...optionalParams: any[]): void {
22 if (!log.enabled) return; 29 if (!log.enabled) return;
23 debugger; 30 debugger;
24 // eslint-disable-next-line no-console 31 // eslint-disable-next-line no-console
25 console.error(message, ...optionalParams); 32 console.error(message, ...optionalParams);
26 }, 33 }
27 setEnabled(yes: boolean): void { 34
28 log.enabled = yes; 35 downloadError(err: Error, artifactName: string, repoName: string) {
36 vscode.window.showErrorMessage(
37 `Failed to download the rust-analyzer ${artifactName} from ${repoName} ` +
38 `GitHub repository: ${err.message}`
39 );
40 log.error(err);
41 dns.resolve('example.com').then(
42 addrs => log.debug("DNS resolution for example.com was successful", addrs),
43 err => log.error(
44 "DNS resolution for example.com failed, " +
45 "there might be an issue with Internet availability",
46 err
47 )
48 );
29 } 49 }
30}; 50};
31 51
@@ -66,6 +86,17 @@ function sleep(ms: number) {
66 return new Promise(resolve => setTimeout(resolve, ms)); 86 return new Promise(resolve => setTimeout(resolve, ms));
67} 87}
68 88
89export function notReentrant<TThis, TParams extends any[], TRet>(
90 fn: (this: TThis, ...params: TParams) => Promise<TRet>
91): typeof fn {
92 let entered = false;
93 return function(...params) {
94 assert(!entered, `Reentrancy invariant for ${fn.name} is violated`);
95 entered = true;
96 return fn.apply(this, params).finally(() => entered = false);
97 };
98}
99
69export type RustDocument = vscode.TextDocument & { languageId: "rust" }; 100export type RustDocument = vscode.TextDocument & { languageId: "rust" };
70export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string }; 101export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string };
71 102
@@ -79,3 +110,29 @@ export function isRustDocument(document: vscode.TextDocument): document is RustD
79export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor { 110export function isRustEditor(editor: vscode.TextEditor): editor is RustEditor {
80 return isRustDocument(editor.document); 111 return isRustDocument(editor.document);
81} 112}
113
114/**
115 * @param extensionId The canonical extension identifier in the form of: `publisher.name`
116 */
117export async function vscodeReinstallExtension(extensionId: string) {
118 // Unfortunately there is no straightforward way as of now, these commands
119 // were found in vscode source code.
120
121 log.debug("Uninstalling extension", extensionId);
122 await vscode.commands.executeCommand("workbench.extensions.uninstallExtension", extensionId);
123 log.debug("Installing extension", extensionId);
124 await vscode.commands.executeCommand("workbench.extensions.installExtension", extensionId);
125}
126
127export async function vscodeReloadWindow(): Promise<never> {
128 await vscode.commands.executeCommand("workbench.action.reloadWindow");
129
130 assert(false, "unreachable");
131}
132
133export async function vscodeInstallExtensionFromVsix(vsixPath: string) {
134 await vscode.commands.executeCommand(
135 "workbench.extensions.installExtension",
136 vscode.Uri.file(vsixPath)
137 );
138}
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 014b61b37..e1472e85d 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -38,13 +38,11 @@ pub fn project_root() -> PathBuf {
38} 38}
39 39
40pub fn run_rustfmt(mode: Mode) -> Result<()> { 40pub fn run_rustfmt(mode: Mode) -> Result<()> {
41 let _dir = pushd(project_root());
41 ensure_rustfmt()?; 42 ensure_rustfmt()?;
42 43
43 if mode == Mode::Verify { 44 let check = if mode == Mode::Verify { "--check" } else { "" };
44 run!("rustup run {} -- cargo fmt -- --check", TOOLCHAIN)?; 45 run!("rustup run {} -- cargo fmt -- {}", TOOLCHAIN, check)?;
45 } else {
46 run!("rustup run {} -- cargo fmt", TOOLCHAIN)?;
47 }
48 Ok(()) 46 Ok(())
49} 47}
50 48