aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock55
-rw-r--r--crates/assists/src/ast_transform.rs74
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs2
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs2
-rw-r--r--crates/assists/src/utils/insert_use.rs204
-rw-r--r--crates/base_db/src/change.rs97
-rw-r--r--crates/base_db/src/fixture.rs175
-rw-r--r--crates/base_db/src/lib.rs2
-rw-r--r--crates/hir/src/code_model.rs2
-rw-r--r--crates/hir_def/src/nameres/collector.rs3
-rw-r--r--crates/hir_def/src/nameres/tests/macros.rs7
-rw-r--r--crates/ide/src/call_hierarchy.rs56
-rw-r--r--crates/ide/src/call_info.rs4
-rw-r--r--crates/ide/src/completion.rs4
-rw-r--r--crates/ide/src/completion/complete_keyword.rs8
-rw-r--r--crates/ide/src/completion/complete_mod.rs4
-rw-r--r--crates/ide/src/completion/complete_postfix.rs61
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs277
-rw-r--r--crates/ide/src/completion/complete_qualified_path.rs4
-rw-r--r--crates/ide/src/completion/complete_unqualified_path.rs34
-rw-r--r--crates/ide/src/completion/completion_context.rs7
-rw-r--r--crates/ide/src/completion/presentation.rs4
-rw-r--r--crates/ide/src/completion/test_utils.rs9
-rw-r--r--crates/ide/src/diagnostics.rs39
-rw-r--r--crates/ide/src/display/navigation_target.rs10
-rw-r--r--crates/ide/src/expand_macro.rs4
-rw-r--r--crates/ide/src/extend_selection.rs4
-rw-r--r--crates/ide/src/fixture.rs70
-rw-r--r--crates/ide/src/fn_references.rs10
-rw-r--r--crates/ide/src/goto_definition.rs24
-rw-r--r--crates/ide/src/goto_implementation.rs6
-rw-r--r--crates/ide/src/goto_type_definition.rs7
-rw-r--r--crates/ide/src/hover.rs108
-rw-r--r--crates/ide/src/inlay_hints.rs138
-rw-r--r--crates/ide/src/lib.rs18
-rw-r--r--crates/ide/src/link_rewrite.rs4
-rw-r--r--crates/ide/src/mock_analysis.rs176
-rw-r--r--crates/ide/src/parent_module.rs46
-rw-r--r--crates/ide/src/references.rs355
-rw-r--r--crates/ide/src/references/rename.rs42
-rw-r--r--crates/ide/src/runnables.rs44
-rw-r--r--crates/ide/src/status.rs52
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs16
-rw-r--r--crates/ide/src/syntax_tree.rs16
-rw-r--r--crates/ide/src/typing/on_enter.rs4
-rw-r--r--crates/ide_db/src/apply_change.rs (renamed from crates/ide_db/src/change.rs)93
-rw-r--r--crates/ide_db/src/lib.rs17
-rw-r--r--crates/ide_db/src/wasm_shims.rs19
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs7
-rw-r--r--crates/rust-analyzer/src/cli/load_cargo.rs4
-rw-r--r--crates/rust-analyzer/src/config.rs18
-rw-r--r--crates/rust-analyzer/src/dispatch.rs11
-rw-r--r--crates/rust-analyzer/src/global_state.rs4
-rw-r--r--crates/rust-analyzer/src/handlers.rs24
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs12
-rw-r--r--crates/rust-analyzer/src/main_loop.rs19
-rw-r--r--crates/rust-analyzer/src/reload.rs4
-rw-r--r--crates/rust-analyzer/src/to_proto.rs3
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/main.rs6
-rw-r--r--crates/ssr/src/resolving.rs2
-rw-r--r--crates/stdx/src/lib.rs1
-rw-r--r--crates/stdx/src/panic_context.rs49
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/ast/edit.rs2
-rw-r--r--crates/syntax/src/parsing/lexer.rs8
-rw-r--r--docs/dev/lsp-extensions.md9
-rw-r--r--docs/dev/style.md19
-rw-r--r--docs/user/manual.adoc4
-rw-r--r--editors/code/package.json16
-rw-r--r--editors/code/src/commands.ts19
-rw-r--r--editors/code/src/lsp_ext.ts7
-rw-r--r--editors/code/src/run.ts2
-rw-r--r--editors/code/src/tasks.ts10
-rw-r--r--editors/code/tests/unit/runnable_env.test.ts3
-rw-r--r--xtask/src/codegen/gen_feature_docs.rs12
75 files changed, 1632 insertions, 1062 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 74e5a8273..49022502d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,9 +82,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
82 82
83[[package]] 83[[package]]
84name = "backtrace" 84name = "backtrace"
85version = "0.3.50" 85version = "0.3.51"
86source = "registry+https://github.com/rust-lang/crates.io-index" 86source = "registry+https://github.com/rust-lang/crates.io-index"
87checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" 87checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1"
88dependencies = [ 88dependencies = [
89 "addr2line", 89 "addr2line",
90 "cfg-if", 90 "cfg-if",
@@ -214,9 +214,9 @@ dependencies = [
214 214
215[[package]] 215[[package]]
216name = "chrono" 216name = "chrono"
217version = "0.4.18" 217version = "0.4.19"
218source = "registry+https://github.com/rust-lang/crates.io-index" 218source = "registry+https://github.com/rust-lang/crates.io-index"
219checksum = "d021fddb7bd3e734370acfa4a83f34095571d8570c039f1420d77540f68d5772" 219checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
220dependencies = [ 220dependencies = [
221 "libc", 221 "libc",
222 "num-integer", 222 "num-integer",
@@ -365,9 +365,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
365 365
366[[package]] 366[[package]]
367name = "flate2" 367name = "flate2"
368version = "1.0.17" 368version = "1.0.18"
369source = "registry+https://github.com/rust-lang/crates.io-index" 369source = "registry+https://github.com/rust-lang/crates.io-index"
370checksum = "766d0e77a2c1502169d4a93ff3b8c15a71fd946cd0126309752104e5f3c46d94" 370checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee"
371dependencies = [ 371dependencies = [
372 "cfg-if", 372 "cfg-if",
373 "crc32fast", 373 "crc32fast",
@@ -453,9 +453,9 @@ dependencies = [
453 453
454[[package]] 454[[package]]
455name = "hashbrown" 455name = "hashbrown"
456version = "0.9.0" 456version = "0.9.1"
457source = "registry+https://github.com/rust-lang/crates.io-index" 457source = "registry+https://github.com/rust-lang/crates.io-index"
458checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7" 458checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
459 459
460[[package]] 460[[package]]
461name = "heck" 461name = "heck"
@@ -725,9 +725,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
725 725
726[[package]] 726[[package]]
727name = "libc" 727name = "libc"
728version = "0.2.77" 728version = "0.2.78"
729source = "registry+https://github.com/rust-lang/crates.io-index" 729source = "registry+https://github.com/rust-lang/crates.io-index"
730checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" 730checksum = "aa7087f49d294270db4e1928fc110c976cd4b9e5a16348e0a1df09afa99e6c98"
731 731
732[[package]] 732[[package]]
733name = "libloading" 733name = "libloading"
@@ -1070,6 +1070,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1070checksum = "28b9b4df73455c861d7cbf8be42f01d3b373ed7f02e378d55fa84eafc6f638b1" 1070checksum = "28b9b4df73455c861d7cbf8be42f01d3b373ed7f02e378d55fa84eafc6f638b1"
1071 1071
1072[[package]] 1072[[package]]
1073name = "pin-project-lite"
1074version = "0.1.10"
1075source = "registry+https://github.com/rust-lang/crates.io-index"
1076checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95"
1077
1078[[package]]
1073name = "plain" 1079name = "plain"
1074version = "0.2.3" 1080version = "0.2.3"
1075source = "registry+https://github.com/rust-lang/crates.io-index" 1081source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1077,9 +1083,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
1077 1083
1078[[package]] 1084[[package]]
1079name = "proc-macro2" 1085name = "proc-macro2"
1080version = "1.0.23" 1086version = "1.0.24"
1081source = "registry+https://github.com/rust-lang/crates.io-index" 1087source = "registry+https://github.com/rust-lang/crates.io-index"
1082checksum = "51ef7cd2518ead700af67bf9d1a658d90b6037d77110fd9c0445429d0ba1c6c9" 1088checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
1083dependencies = [ 1089dependencies = [
1084 "unicode-xid", 1090 "unicode-xid",
1085] 1091]
@@ -1180,9 +1186,9 @@ dependencies = [
1180 1186
1181[[package]] 1187[[package]]
1182name = "rayon" 1188name = "rayon"
1183version = "1.4.0" 1189version = "1.4.1"
1184source = "registry+https://github.com/rust-lang/crates.io-index" 1190source = "registry+https://github.com/rust-lang/crates.io-index"
1185checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" 1191checksum = "dcf6960dc9a5b4ee8d3e4c5787b4a112a8818e0290a42ff664ad60692fdf2032"
1186dependencies = [ 1192dependencies = [
1187 "autocfg", 1193 "autocfg",
1188 "crossbeam-deque", 1194 "crossbeam-deque",
@@ -1294,9 +1300,9 @@ dependencies = [
1294 1300
1295[[package]] 1301[[package]]
1296name = "rustc-ap-rustc_lexer" 1302name = "rustc-ap-rustc_lexer"
1297version = "673.0.0" 1303version = "681.0.0"
1298source = "registry+https://github.com/rust-lang/crates.io-index" 1304source = "registry+https://github.com/rust-lang/crates.io-index"
1299checksum = "f6b71fa1285bdefe5fb61e59b63d6cc246abf337f4acafdd620d721bc488e671" 1305checksum = "01e579a90506e9d9c9a098f380cad55b9ecf9e7be9fa96cb67b31f52045f41a8"
1300dependencies = [ 1306dependencies = [
1301 "unicode-xid", 1307 "unicode-xid",
1302] 1308]
@@ -1427,9 +1433,9 @@ dependencies = [
1427 1433
1428[[package]] 1434[[package]]
1429name = "serde_json" 1435name = "serde_json"
1430version = "1.0.57" 1436version = "1.0.58"
1431source = "registry+https://github.com/rust-lang/crates.io-index" 1437source = "registry+https://github.com/rust-lang/crates.io-index"
1432checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" 1438checksum = "a230ea9107ca2220eea9d46de97eddcb04cd00e92d13dda78e478dd33fa82bd4"
1433dependencies = [ 1439dependencies = [
1434 "itoa", 1440 "itoa",
1435 "ryu", 1441 "ryu",
@@ -1623,11 +1629,12 @@ dependencies = [
1623 1629
1624[[package]] 1630[[package]]
1625name = "tracing" 1631name = "tracing"
1626version = "0.1.19" 1632version = "0.1.21"
1627source = "registry+https://github.com/rust-lang/crates.io-index" 1633source = "registry+https://github.com/rust-lang/crates.io-index"
1628checksum = "6d79ca061b032d6ce30c660fded31189ca0b9922bf483cd70759f13a2d86786c" 1634checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27"
1629dependencies = [ 1635dependencies = [
1630 "cfg-if", 1636 "cfg-if",
1637 "pin-project-lite",
1631 "tracing-attributes", 1638 "tracing-attributes",
1632 "tracing-core", 1639 "tracing-core",
1633] 1640]
@@ -1645,9 +1652,9 @@ dependencies = [
1645 1652
1646[[package]] 1653[[package]]
1647name = "tracing-core" 1654name = "tracing-core"
1648version = "0.1.16" 1655version = "0.1.17"
1649source = "registry+https://github.com/rust-lang/crates.io-index" 1656source = "registry+https://github.com/rust-lang/crates.io-index"
1650checksum = "5bcf46c1f1f06aeea2d6b81f3c863d0930a596c86ad1920d4e5bad6dd1d7119a" 1657checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
1651dependencies = [ 1658dependencies = [
1652 "lazy_static", 1659 "lazy_static",
1653] 1660]
@@ -1696,9 +1703,9 @@ dependencies = [
1696 1703
1697[[package]] 1704[[package]]
1698name = "tracing-tree" 1705name = "tracing-tree"
1699version = "0.1.5" 1706version = "0.1.6"
1700source = "registry+https://github.com/rust-lang/crates.io-index" 1707source = "registry+https://github.com/rust-lang/crates.io-index"
1701checksum = "e1a3dc4774db3a6b2d66a4f8d8de670e874ec3ed55615860c994927419b32c5f" 1708checksum = "43aac8afb493b08e1e1904956f7407c1e671b9c83b26a17e1bd83d6a3520e350"
1702dependencies = [ 1709dependencies = [
1703 "ansi_term", 1710 "ansi_term",
1704 "atty", 1711 "atty",
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
index 835da3bb2..4307e0191 100644
--- a/crates/assists/src/ast_transform.rs
+++ b/crates/assists/src/ast_transform.rs
@@ -5,12 +5,13 @@ use hir::{HirDisplay, PathResolution, SemanticsScope};
5use syntax::{ 5use syntax::{
6 algo::SyntaxRewriter, 6 algo::SyntaxRewriter,
7 ast::{self, AstNode}, 7 ast::{self, AstNode},
8 SyntaxNode,
8}; 9};
9 10
10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { 11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
11 SyntaxRewriter::from_fn(|element| match element { 12 SyntaxRewriter::from_fn(|element| match element {
12 syntax::SyntaxElement::Node(n) => { 13 syntax::SyntaxElement::Node(n) => {
13 let replacement = transformer.get_substitution(&n)?; 14 let replacement = transformer.get_substitution(&n, transformer)?;
14 Some(replacement.into()) 15 Some(replacement.into())
15 } 16 }
16 _ => None, 17 _ => None,
@@ -47,32 +48,35 @@ pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
47/// We'd want to somehow express this concept simpler, but so far nobody got to 48/// We'd want to somehow express this concept simpler, but so far nobody got to
48/// simplifying this! 49/// simplifying this!
49pub trait AstTransform<'a> { 50pub trait AstTransform<'a> {
50 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; 51 fn get_substitution(
52 &self,
53 node: &SyntaxNode,
54 recur: &dyn AstTransform<'a>,
55 ) -> Option<SyntaxNode>;
51 56
52 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
53 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a> 57 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
54 where 58 where
55 Self: Sized + 'a, 59 Self: Sized + 'a,
56 { 60 {
57 self.chain_before(Box::new(other)) 61 Box::new(Or(Box::new(self), Box::new(other)))
58 } 62 }
59} 63}
60 64
61struct NullTransformer; 65struct Or<'a>(Box<dyn AstTransform<'a> + 'a>, Box<dyn AstTransform<'a> + 'a>);
62 66
63impl<'a> AstTransform<'a> for NullTransformer { 67impl<'a> AstTransform<'a> for Or<'a> {
64 fn get_substitution(&self, _node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> { 68 fn get_substitution(
65 None 69 &self,
66 } 70 node: &SyntaxNode,
67 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> { 71 recur: &dyn AstTransform<'a>,
68 other 72 ) -> Option<SyntaxNode> {
73 self.0.get_substitution(node, recur).or_else(|| self.1.get_substitution(node, recur))
69 } 74 }
70} 75}
71 76
72pub struct SubstituteTypeParams<'a> { 77pub struct SubstituteTypeParams<'a> {
73 source_scope: &'a SemanticsScope<'a>, 78 source_scope: &'a SemanticsScope<'a>,
74 substs: FxHashMap<hir::TypeParam, ast::Type>, 79 substs: FxHashMap<hir::TypeParam, ast::Type>,
75 previous: Box<dyn AstTransform<'a> + 'a>,
76} 80}
77 81
78impl<'a> SubstituteTypeParams<'a> { 82impl<'a> SubstituteTypeParams<'a> {
@@ -111,11 +115,7 @@ impl<'a> SubstituteTypeParams<'a> {
111 } 115 }
112 }) 116 })
113 .collect(); 117 .collect();
114 return SubstituteTypeParams { 118 return SubstituteTypeParams { source_scope, substs: substs_by_param };
115 source_scope,
116 substs: substs_by_param,
117 previous: Box::new(NullTransformer),
118 };
119 119
120 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the 120 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
121 // trait ref, and then go from the types in the substs back to the syntax). 121 // trait ref, and then go from the types in the substs back to the syntax).
@@ -140,7 +140,14 @@ impl<'a> SubstituteTypeParams<'a> {
140 Some(result) 140 Some(result)
141 } 141 }
142 } 142 }
143 fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> { 143}
144
145impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
146 fn get_substitution(
147 &self,
148 node: &SyntaxNode,
149 _recur: &dyn AstTransform<'a>,
150 ) -> Option<SyntaxNode> {
144 let type_ref = ast::Type::cast(node.clone())?; 151 let type_ref = ast::Type::cast(node.clone())?;
145 let path = match &type_ref { 152 let path = match &type_ref {
146 ast::Type::PathType(path_type) => path_type.path()?, 153 ast::Type::PathType(path_type) => path_type.path()?,
@@ -154,27 +161,23 @@ impl<'a> SubstituteTypeParams<'a> {
154 } 161 }
155} 162}
156 163
157impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
158 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
159 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
160 }
161 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
162 Box::new(SubstituteTypeParams { previous: other, ..self })
163 }
164}
165
166pub struct QualifyPaths<'a> { 164pub struct QualifyPaths<'a> {
167 target_scope: &'a SemanticsScope<'a>, 165 target_scope: &'a SemanticsScope<'a>,
168 source_scope: &'a SemanticsScope<'a>, 166 source_scope: &'a SemanticsScope<'a>,
169 previous: Box<dyn AstTransform<'a> + 'a>,
170} 167}
171 168
172impl<'a> QualifyPaths<'a> { 169impl<'a> QualifyPaths<'a> {
173 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self { 170 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
174 Self { target_scope, source_scope, previous: Box::new(NullTransformer) } 171 Self { target_scope, source_scope }
175 } 172 }
173}
176 174
177 fn get_substitution_inner(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> { 175impl<'a> AstTransform<'a> for QualifyPaths<'a> {
176 fn get_substitution(
177 &self,
178 node: &SyntaxNode,
179 recur: &dyn AstTransform<'a>,
180 ) -> Option<SyntaxNode> {
178 // FIXME handle value ns? 181 // FIXME handle value ns?
179 let from = self.target_scope.module()?; 182 let from = self.target_scope.module()?;
180 let p = ast::Path::cast(node.clone())?; 183 let p = ast::Path::cast(node.clone())?;
@@ -191,7 +194,7 @@ impl<'a> QualifyPaths<'a> {
191 let type_args = p 194 let type_args = p
192 .segment() 195 .segment()
193 .and_then(|s| s.generic_arg_list()) 196 .and_then(|s| s.generic_arg_list())
194 .map(|arg_list| apply(self, arg_list)); 197 .map(|arg_list| apply(recur, arg_list));
195 if let Some(type_args) = type_args { 198 if let Some(type_args) = type_args {
196 let last_segment = path.segment().unwrap(); 199 let last_segment = path.segment().unwrap();
197 path = path.with_segment(last_segment.with_generic_args(type_args)) 200 path = path.with_segment(last_segment.with_generic_args(type_args))
@@ -208,15 +211,6 @@ impl<'a> QualifyPaths<'a> {
208 } 211 }
209} 212}
210 213
211impl<'a> AstTransform<'a> for QualifyPaths<'a> {
212 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
213 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
214 }
215 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
216 Box::new(QualifyPaths { previous: other, ..self })
217 }
218}
219
220pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path { 214pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
221 let parse = ast::SourceFile::parse(&path.to_string()); 215 let parse = ast::SourceFile::parse(&path.to_string());
222 parse 216 parse
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index 1ac5fefd6..51b5a2eb0 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -146,7 +146,7 @@ fn add_missing_impl_members_inner(
146 146
147 let target = impl_def.syntax().text_range(); 147 let target = impl_def.syntax().text_range();
148 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| { 148 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
149 let impl_item_list = impl_def.assoc_item_list().unwrap_or(make::assoc_item_list()); 149 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
150 150
151 let n_existing_items = impl_item_list.assoc_items().count(); 151 let n_existing_items = impl_item_list.assoc_items().count();
152 let source_scope = ctx.sema.scope_for_def(trait_); 152 let source_scope = ctx.sema.scope_for_def(trait_);
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
index d1eadaa99..f5f03ef36 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -91,7 +91,7 @@ fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVari
91 .module(db) 91 .module(db)
92 .scope(db, None) 92 .scope(db, None)
93 .into_iter() 93 .into_iter()
94 .any(|(name, _)| name.to_string() == variant_name.to_string()) 94 .any(|(name, _)| name.to_string() == variant_name)
95} 95}
96 96
97#[allow(dead_code)] 97#[allow(dead_code)]
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 5719b06af..f6025c99a 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -4,13 +4,14 @@ use std::{
4 iter::{self, successors}, 4 iter::{self, successors},
5}; 5};
6 6
7use ast::{ 7use itertools::{EitherOrBoth, Itertools};
8 edit::{AstNodeEdit, IndentLevel},
9 PathSegmentKind, VisibilityOwner,
10};
11use syntax::{ 8use syntax::{
12 algo, 9 algo,
13 ast::{self, make, AstNode}, 10 ast::{
11 self,
12 edit::{AstNodeEdit, IndentLevel},
13 make, AstNode, PathSegmentKind, VisibilityOwner,
14 },
14 InsertPosition, SyntaxElement, SyntaxNode, 15 InsertPosition, SyntaxElement, SyntaxNode,
15}; 16};
16 17
@@ -174,7 +175,7 @@ pub(crate) fn try_merge_trees(
174 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; 175 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
175 let lhs = lhs.split_prefix(&lhs_prefix); 176 let lhs = lhs.split_prefix(&lhs_prefix);
176 let rhs = rhs.split_prefix(&rhs_prefix); 177 let rhs = rhs.split_prefix(&rhs_prefix);
177 recursive_merge(&lhs, &rhs, merge).map(|(merged, _)| merged) 178 recursive_merge(&lhs, &rhs, merge)
178} 179}
179 180
180/// Recursively "zips" together lhs and rhs. 181/// Recursively "zips" together lhs and rhs.
@@ -182,22 +183,24 @@ fn recursive_merge(
182 lhs: &ast::UseTree, 183 lhs: &ast::UseTree,
183 rhs: &ast::UseTree, 184 rhs: &ast::UseTree,
184 merge: MergeBehaviour, 185 merge: MergeBehaviour,
185) -> Option<(ast::UseTree, bool)> { 186) -> Option<ast::UseTree> {
186 let mut use_trees = lhs 187 let mut use_trees = lhs
187 .use_tree_list() 188 .use_tree_list()
188 .into_iter() 189 .into_iter()
189 .flat_map(|list| list.use_trees()) 190 .flat_map(|list| list.use_trees())
190 // check if any of the use trees are nested, if they are and the behaviour is `last` we are not allowed to merge this 191 // we use Option here to early return from this function(this is not the same as a `filter` op)
191 // so early exit the iterator by using Option's Intoiterator impl 192 .map(|tree| match merge.is_tree_allowed(&tree) {
192 .map(|tree| match merge == MergeBehaviour::Last && tree.use_tree_list().is_some() { 193 true => Some(tree),
193 true => None, 194 false => None,
194 false => Some(tree),
195 }) 195 })
196 .collect::<Option<Vec<_>>>()?; 196 .collect::<Option<Vec<_>>>()?;
197 use_trees.sort_unstable_by(|a, b| path_cmp_opt(a.path(), b.path())); 197 use_trees.sort_unstable_by(|a, b| path_cmp_for_sort(a.path(), b.path()));
198 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) { 198 for rhs_t in rhs.use_tree_list().into_iter().flat_map(|list| list.use_trees()) {
199 if !merge.is_tree_allowed(&rhs_t) {
200 return None;
201 }
199 let rhs_path = rhs_t.path(); 202 let rhs_path = rhs_t.path();
200 match use_trees.binary_search_by(|p| path_cmp_opt(p.path(), rhs_path.clone())) { 203 match use_trees.binary_search_by(|p| path_cmp_bin_search(p.path(), rhs_path.clone())) {
201 Ok(idx) => { 204 Ok(idx) => {
202 let lhs_t = &mut use_trees[idx]; 205 let lhs_t = &mut use_trees[idx];
203 let lhs_path = lhs_t.path()?; 206 let lhs_path = lhs_t.path()?;
@@ -239,17 +242,9 @@ fn recursive_merge(
239 } 242 }
240 let lhs = lhs_t.split_prefix(&lhs_prefix); 243 let lhs = lhs_t.split_prefix(&lhs_prefix);
241 let rhs = rhs_t.split_prefix(&rhs_prefix); 244 let rhs = rhs_t.split_prefix(&rhs_prefix);
242 let this_has_children = use_trees.len() > 0;
243 match recursive_merge(&lhs, &rhs, merge) { 245 match recursive_merge(&lhs, &rhs, merge) {
244 Some((_, has_multiple_children)) 246 Some(use_tree) => use_trees[idx] = use_tree,
245 if merge == MergeBehaviour::Last 247 None => return None,
246 && this_has_children
247 && has_multiple_children =>
248 {
249 return None
250 }
251 Some((use_tree, _)) => use_trees[idx] = use_tree,
252 None => use_trees.insert(idx, rhs_t),
253 } 248 }
254 } 249 }
255 Err(_) 250 Err(_)
@@ -264,8 +259,7 @@ fn recursive_merge(
264 } 259 }
265 } 260 }
266 } 261 }
267 let has_multiple_children = use_trees.len() > 1; 262 Some(lhs.with_use_tree_list(make::use_tree_list(use_trees)))
268 Some((lhs.with_use_tree_list(make::use_tree_list(use_trees)), has_multiple_children))
269} 263}
270 264
271/// Traverses both paths until they differ, returning the common prefix of both. 265/// Traverses both paths until they differ, returning the common prefix of both.
@@ -308,41 +302,83 @@ fn segment_iter(path: &ast::Path) -> impl Iterator<Item = ast::PathSegment> + Cl
308 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment())) 302 successors(first_segment(path), |p| p.parent_path().parent_path().and_then(|p| p.segment()))
309} 303}
310 304
305fn path_len(path: ast::Path) -> usize {
306 segment_iter(&path).count()
307}
308
311/// Orders paths in the following way: 309/// Orders paths in the following way:
312/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers 310/// the sole self token comes first, after that come uppercase identifiers, then lowercase identifiers
313// FIXME: rustfmt sort lowercase idents before uppercase, in general we want to have the same ordering rustfmt has 311// FIXME: rustfmt sorts lowercase idents before uppercase, in general we want to have the same ordering rustfmt has
314// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports. 312// which is `self` and `super` first, then identifier imports with lowercase ones first, then glob imports and at last list imports.
315// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}} 313// Example foo::{self, foo, baz, Baz, Qux, *, {Bar}}
316fn path_cmp(a: &ast::Path, b: &ast::Path) -> Ordering { 314fn path_cmp_for_sort(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering {
317 match (path_is_self(a), path_is_self(b)) { 315 match (a, b) {
318 (true, true) => Ordering::Equal, 316 (None, None) => Ordering::Equal,
319 (true, false) => Ordering::Less, 317 (None, Some(_)) => Ordering::Less,
320 (false, true) => Ordering::Greater, 318 (Some(_), None) => Ordering::Greater,
321 (false, false) => { 319 (Some(ref a), Some(ref b)) => match (path_is_self(a), path_is_self(b)) {
322 let a = segment_iter(a); 320 (true, true) => Ordering::Equal,
323 let b = segment_iter(b); 321 (true, false) => Ordering::Less,
324 // cmp_by would be useful for us here but that is currently unstable 322 (false, true) => Ordering::Greater,
325 // cmp doesnt work due the lifetimes on text's return type 323 (false, false) => path_cmp_short(a, b),
326 a.zip(b) 324 },
327 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
328 .find_map(|(a, b)| match a.text().cmp(b.text()) {
329 ord @ Ordering::Greater | ord @ Ordering::Less => Some(ord),
330 Ordering::Equal => None,
331 })
332 .unwrap_or(Ordering::Equal)
333 }
334 } 325 }
335} 326}
336 327
337fn path_cmp_opt(a: Option<ast::Path>, b: Option<ast::Path>) -> Ordering { 328/// Path comparison func for binary searching for merging.
338 match (a, b) { 329fn path_cmp_bin_search(lhs: Option<ast::Path>, rhs: Option<ast::Path>) -> Ordering {
330 match (lhs, rhs) {
339 (None, None) => Ordering::Equal, 331 (None, None) => Ordering::Equal,
340 (None, Some(_)) => Ordering::Less, 332 (None, Some(_)) => Ordering::Less,
341 (Some(_), None) => Ordering::Greater, 333 (Some(_), None) => Ordering::Greater,
342 (Some(a), Some(b)) => path_cmp(&a, &b), 334 (Some(ref a), Some(ref b)) => path_cmp_short(a, b),
343 } 335 }
344} 336}
345 337
338/// Short circuiting comparison, if both paths are equal until one of them ends they are considered
339/// equal
340fn path_cmp_short(a: &ast::Path, b: &ast::Path) -> Ordering {
341 let a = segment_iter(a);
342 let b = segment_iter(b);
343 // cmp_by would be useful for us here but that is currently unstable
344 // cmp doesnt work due the lifetimes on text's return type
345 a.zip(b)
346 .find_map(|(a, b)| match path_segment_cmp(&a, &b) {
347 Ordering::Equal => None,
348 ord => Some(ord),
349 })
350 .unwrap_or(Ordering::Equal)
351}
352
353/// Compares to paths, if one ends earlier than the other the has_tl parameters decide which is
354/// greater as a a path that has a tree list should be greater, while one that just ends without
355/// a tree list should be considered less.
356fn use_tree_path_cmp(a: &ast::Path, a_has_tl: bool, b: &ast::Path, b_has_tl: bool) -> Ordering {
357 let a_segments = segment_iter(a);
358 let b_segments = segment_iter(b);
359 // cmp_by would be useful for us here but that is currently unstable
360 // cmp doesnt work due the lifetimes on text's return type
361 a_segments
362 .zip_longest(b_segments)
363 .find_map(|zipped| match zipped {
364 EitherOrBoth::Both(ref a, ref b) => match path_segment_cmp(a, b) {
365 Ordering::Equal => None,
366 ord => Some(ord),
367 },
368 EitherOrBoth::Left(_) if !b_has_tl => Some(Ordering::Greater),
369 EitherOrBoth::Left(_) => Some(Ordering::Less),
370 EitherOrBoth::Right(_) if !a_has_tl => Some(Ordering::Less),
371 EitherOrBoth::Right(_) => Some(Ordering::Greater),
372 })
373 .unwrap_or(Ordering::Equal)
374}
375
376fn path_segment_cmp(a: &ast::PathSegment, b: &ast::PathSegment) -> Ordering {
377 let a = a.name_ref();
378 let b = b.name_ref();
379 a.as_ref().map(ast::NameRef::text).cmp(&b.as_ref().map(ast::NameRef::text))
380}
381
346/// What type of merges are allowed. 382/// What type of merges are allowed.
347#[derive(Copy, Clone, Debug, PartialEq, Eq)] 383#[derive(Copy, Clone, Debug, PartialEq, Eq)]
348pub enum MergeBehaviour { 384pub enum MergeBehaviour {
@@ -352,6 +388,19 @@ pub enum MergeBehaviour {
352 Last, 388 Last,
353} 389}
354 390
391impl MergeBehaviour {
392 #[inline]
393 fn is_tree_allowed(&self, tree: &ast::UseTree) -> bool {
394 match self {
395 MergeBehaviour::Full => true,
396 // only simple single segment paths are allowed
397 MergeBehaviour::Last => {
398 tree.use_tree_list().is_none() && tree.path().map(path_len) <= Some(1)
399 }
400 }
401 }
402}
403
355#[derive(Eq, PartialEq, PartialOrd, Ord)] 404#[derive(Eq, PartialEq, PartialOrd, Ord)]
356enum ImportGroup { 405enum ImportGroup {
357 // the order here defines the order of new group inserts 406 // the order here defines the order of new group inserts
@@ -379,7 +428,6 @@ impl ImportGroup {
379 PathSegmentKind::Name(name) => match name.text().as_str() { 428 PathSegmentKind::Name(name) => match name.text().as_str() {
380 "std" => ImportGroup::Std, 429 "std" => ImportGroup::Std,
381 "core" => ImportGroup::Std, 430 "core" => ImportGroup::Std,
382 // FIXME: can be ThisModule as well
383 _ => ImportGroup::ExternCrate, 431 _ => ImportGroup::ExternCrate,
384 }, 432 },
385 PathSegmentKind::Type { .. } => unreachable!(), 433 PathSegmentKind::Type { .. } => unreachable!(),
@@ -405,30 +453,30 @@ fn find_insert_position(
405 .as_syntax_node() 453 .as_syntax_node()
406 .children() 454 .children()
407 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node))) 455 .filter_map(|node| ast::Use::cast(node.clone()).zip(Some(node)))
408 .flat_map(|(use_, node)| use_.use_tree().and_then(|tree| tree.path()).zip(Some(node))); 456 .flat_map(|(use_, node)| {
457 let tree = use_.use_tree()?;
458 let path = tree.path()?;
459 let has_tl = tree.use_tree_list().is_some();
460 Some((path, has_tl, node))
461 });
409 // Iterator that discards anything thats not in the required grouping 462 // Iterator that discards anything thats not in the required grouping
410 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits 463 // This implementation allows the user to rearrange their import groups as this only takes the first group that fits
411 let group_iter = path_node_iter 464 let group_iter = path_node_iter
412 .clone() 465 .clone()
413 .skip_while(|(path, _)| ImportGroup::new(path) != group) 466 .skip_while(|(path, ..)| ImportGroup::new(path) != group)
414 .take_while(|(path, _)| ImportGroup::new(path) == group); 467 .take_while(|(path, ..)| ImportGroup::new(path) == group);
415 468
416 let segments = segment_iter(&insert_path);
417 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place 469 // track the last element we iterated over, if this is still None after the iteration then that means we never iterated in the first place
418 let mut last = None; 470 let mut last = None;
419 // find the element that would come directly after our new import 471 // find the element that would come directly after our new import
420 let post_insert = 472 let post_insert = group_iter.inspect(|(.., node)| last = Some(node.clone())).find(
421 group_iter.inspect(|(_, node)| last = Some(node.clone())).find(|(path, _)| { 473 |&(ref path, has_tl, _)| {
422 let check_segments = segment_iter(&path); 474 use_tree_path_cmp(&insert_path, false, path, has_tl) != Ordering::Greater
423 segments 475 },
424 .clone() 476 );
425 .zip(check_segments)
426 .flat_map(|(seg, seg2)| seg.name_ref().zip(seg2.name_ref()))
427 .all(|(l, r)| l.text() <= r.text())
428 });
429 match post_insert { 477 match post_insert {
430 // insert our import before that element 478 // insert our import before that element
431 Some((_, node)) => (InsertPosition::Before(node.into()), AddBlankLine::After), 479 Some((.., node)) => (InsertPosition::Before(node.into()), AddBlankLine::After),
432 // there is no element after our new import, so append it to the end of the group 480 // there is no element after our new import, so append it to the end of the group
433 None => match last { 481 None => match last {
434 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before), 482 Some(node) => (InsertPosition::After(node.into()), AddBlankLine::Before),
@@ -438,10 +486,10 @@ fn find_insert_position(
438 let mut last = None; 486 let mut last = None;
439 // find the group that comes after where we want to insert 487 // find the group that comes after where we want to insert
440 let post_group = path_node_iter 488 let post_group = path_node_iter
441 .inspect(|(_, node)| last = Some(node.clone())) 489 .inspect(|(.., node)| last = Some(node.clone()))
442 .find(|(p, _)| ImportGroup::new(p) > group); 490 .find(|(p, ..)| ImportGroup::new(p) > group);
443 match post_group { 491 match post_group {
444 Some((_, node)) => { 492 Some((.., node)) => {
445 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice) 493 (InsertPosition::Before(node.into()), AddBlankLine::AfterTwice)
446 } 494 }
447 // there is no such group, so append after the last one 495 // there is no such group, so append after the last one
@@ -676,6 +724,11 @@ use std::io;",
676 } 724 }
677 725
678 #[test] 726 #[test]
727 fn merge_last_into_self() {
728 check_last("foo::bar::baz", r"use foo::bar;", r"use foo::bar::{self, baz};");
729 }
730
731 #[test]
679 fn merge_groups_full() { 732 fn merge_groups_full() {
680 check_full( 733 check_full(
681 "std::io", 734 "std::io",
@@ -819,8 +872,23 @@ use std::io;",
819 } 872 }
820 873
821 #[test] 874 #[test]
822 fn merge_last_too_long() { 875 fn skip_merge_last_too_long() {
823 check_last("foo::bar", r"use foo::bar::baz::Qux;", r"use foo::bar::{self, baz::Qux};"); 876 check_last(
877 "foo::bar",
878 r"use foo::bar::baz::Qux;",
879 r"use foo::bar;
880use foo::bar::baz::Qux;",
881 );
882 }
883
884 #[test]
885 fn skip_merge_last_too_long2() {
886 check_last(
887 "foo::bar::baz::Qux",
888 r"use foo::bar;",
889 r"use foo::bar;
890use foo::bar::baz::Qux;",
891 );
824 } 892 }
825 893
826 #[test] 894 #[test]
diff --git a/crates/base_db/src/change.rs b/crates/base_db/src/change.rs
new file mode 100644
index 000000000..043e03bba
--- /dev/null
+++ b/crates/base_db/src/change.rs
@@ -0,0 +1,97 @@
1//! Defines a unit of change that can applied to the database to get the next
2//! state. Changes are transactional.
3
4use std::{fmt, sync::Arc};
5
6use rustc_hash::FxHashSet;
7use salsa::Durability;
8use vfs::FileId;
9
10use crate::{CrateGraph, SourceDatabaseExt, SourceRoot, SourceRootId};
11
12/// Encapsulate a bunch of raw `.set` calls on the database.
13#[derive(Default)]
14pub struct Change {
15 pub roots: Option<Vec<SourceRoot>>,
16 pub files_changed: Vec<(FileId, Option<Arc<String>>)>,
17 pub crate_graph: Option<CrateGraph>,
18}
19
20impl fmt::Debug for Change {
21 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
22 let mut d = fmt.debug_struct("AnalysisChange");
23 if let Some(roots) = &self.roots {
24 d.field("roots", roots);
25 }
26 if !self.files_changed.is_empty() {
27 d.field("files_changed", &self.files_changed.len());
28 }
29 if self.crate_graph.is_some() {
30 d.field("crate_graph", &self.crate_graph);
31 }
32 d.finish()
33 }
34}
35
36impl Change {
37 pub fn new() -> Change {
38 Change::default()
39 }
40
41 pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
42 self.roots = Some(roots);
43 }
44
45 pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) {
46 self.files_changed.push((file_id, new_text))
47 }
48
49 pub fn set_crate_graph(&mut self, graph: CrateGraph) {
50 self.crate_graph = Some(graph);
51 }
52
53 pub fn apply(self, db: &mut dyn SourceDatabaseExt) {
54 let _p = profile::span("RootDatabase::apply_change");
55 // db.request_cancellation();
56 // log::info!("apply_change {:?}", change);
57 if let Some(roots) = self.roots {
58 let mut local_roots = FxHashSet::default();
59 let mut library_roots = FxHashSet::default();
60 for (idx, root) in roots.into_iter().enumerate() {
61 let root_id = SourceRootId(idx as u32);
62 let durability = durability(&root);
63 if root.is_library {
64 library_roots.insert(root_id);
65 } else {
66 local_roots.insert(root_id);
67 }
68 for file_id in root.iter() {
69 db.set_file_source_root_with_durability(file_id, root_id, durability);
70 }
71 db.set_source_root_with_durability(root_id, Arc::new(root), durability);
72 }
73 // db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
74 // db.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
75 }
76
77 for (file_id, text) in self.files_changed {
78 let source_root_id = db.file_source_root(file_id);
79 let source_root = db.source_root(source_root_id);
80 let durability = durability(&source_root);
81 // XXX: can't actually remove the file, just reset the text
82 let text = text.unwrap_or_default();
83 db.set_file_text_with_durability(file_id, text, durability)
84 }
85 if let Some(crate_graph) = self.crate_graph {
86 db.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
87 }
88 }
89}
90
91fn durability(source_root: &SourceRoot) -> Durability {
92 if source_root.is_library {
93 Durability::HIGH
94 } else {
95 Durability::LOW
96 }
97}
diff --git a/crates/base_db/src/fixture.rs b/crates/base_db/src/fixture.rs
index 5ff8ead0e..b7286fc7d 100644
--- a/crates/base_db/src/fixture.rs
+++ b/crates/base_db/src/fixture.rs
@@ -65,24 +65,26 @@ use test_utils::{extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER}
65use vfs::{file_set::FileSet, VfsPath}; 65use vfs::{file_set::FileSet, VfsPath};
66 66
67use crate::{ 67use crate::{
68 input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, SourceDatabaseExt, 68 input::CrateName, Change, CrateGraph, CrateId, Edition, Env, FileId, FilePosition,
69 SourceRoot, SourceRootId, 69 SourceDatabaseExt, SourceRoot, SourceRootId,
70}; 70};
71 71
72pub const WORKSPACE: SourceRootId = SourceRootId(0); 72pub const WORKSPACE: SourceRootId = SourceRootId(0);
73 73
74pub trait WithFixture: Default + SourceDatabaseExt + 'static { 74pub trait WithFixture: Default + SourceDatabaseExt + 'static {
75 fn with_single_file(text: &str) -> (Self, FileId) { 75 fn with_single_file(text: &str) -> (Self, FileId) {
76 let fixture = ChangeFixture::parse(text);
76 let mut db = Self::default(); 77 let mut db = Self::default();
77 let (_, files) = with_files(&mut db, text); 78 fixture.change.apply(&mut db);
78 assert_eq!(files.len(), 1); 79 assert_eq!(fixture.files.len(), 1);
79 (db, files[0]) 80 (db, fixture.files[0])
80 } 81 }
81 82
82 fn with_files(ra_fixture: &str) -> Self { 83 fn with_files(ra_fixture: &str) -> Self {
84 let fixture = ChangeFixture::parse(ra_fixture);
83 let mut db = Self::default(); 85 let mut db = Self::default();
84 let (pos, _) = with_files(&mut db, ra_fixture); 86 fixture.change.apply(&mut db);
85 assert!(pos.is_none()); 87 assert!(fixture.file_position.is_none());
86 db 88 db
87 } 89 }
88 90
@@ -96,9 +98,10 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
96 } 98 }
97 99
98 fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) { 100 fn with_range_or_offset(ra_fixture: &str) -> (Self, FileId, RangeOrOffset) {
101 let fixture = ChangeFixture::parse(ra_fixture);
99 let mut db = Self::default(); 102 let mut db = Self::default();
100 let (pos, _) = with_files(&mut db, ra_fixture); 103 fixture.change.apply(&mut db);
101 let (file_id, range_or_offset) = pos.unwrap(); 104 let (file_id, range_or_offset) = fixture.file_position.unwrap();
102 (db, file_id, range_or_offset) 105 (db, file_id, range_or_offset)
103 } 106 }
104 107
@@ -113,89 +116,95 @@ pub trait WithFixture: Default + SourceDatabaseExt + 'static {
113 116
114impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {} 117impl<DB: SourceDatabaseExt + Default + 'static> WithFixture for DB {}
115 118
116fn with_files( 119pub struct ChangeFixture {
117 db: &mut dyn SourceDatabaseExt, 120 pub file_position: Option<(FileId, RangeOrOffset)>,
118 fixture: &str, 121 pub files: Vec<FileId>,
119) -> (Option<(FileId, RangeOrOffset)>, Vec<FileId>) { 122 pub change: Change,
120 let fixture = Fixture::parse(fixture); 123}
121
122 let mut files = Vec::new();
123 let mut crate_graph = CrateGraph::default();
124 let mut crates = FxHashMap::default();
125 let mut crate_deps = Vec::new();
126 let mut default_crate_root: Option<FileId> = None;
127
128 let mut file_set = FileSet::default();
129 let source_root_id = WORKSPACE;
130 let source_root_prefix = "/".to_string();
131 let mut file_id = FileId(0);
132
133 let mut file_position = None;
134
135 for entry in fixture {
136 let text = if entry.text.contains(CURSOR_MARKER) {
137 let (range_or_offset, text) = extract_range_or_offset(&entry.text);
138 assert!(file_position.is_none());
139 file_position = Some((file_id, range_or_offset));
140 text.to_string()
141 } else {
142 entry.text.clone()
143 };
144 124
145 let meta = FileMeta::from(entry); 125impl ChangeFixture {
146 assert!(meta.path.starts_with(&source_root_prefix)); 126 pub fn parse(ra_fixture: &str) -> ChangeFixture {
127 let fixture = Fixture::parse(ra_fixture);
128 let mut change = Change::new();
129
130 let mut files = Vec::new();
131 let mut crate_graph = CrateGraph::default();
132 let mut crates = FxHashMap::default();
133 let mut crate_deps = Vec::new();
134 let mut default_crate_root: Option<FileId> = None;
135 let mut default_cfg = CfgOptions::default();
136
137 let mut file_set = FileSet::default();
138 let source_root_prefix = "/".to_string();
139 let mut file_id = FileId(0);
140
141 let mut file_position = None;
142
143 for entry in fixture {
144 let text = if entry.text.contains(CURSOR_MARKER) {
145 let (range_or_offset, text) = extract_range_or_offset(&entry.text);
146 assert!(file_position.is_none());
147 file_position = Some((file_id, range_or_offset));
148 text.to_string()
149 } else {
150 entry.text.clone()
151 };
152
153 let meta = FileMeta::from(entry);
154 assert!(meta.path.starts_with(&source_root_prefix));
155
156 if let Some(krate) = meta.krate {
157 let crate_id = crate_graph.add_crate_root(
158 file_id,
159 meta.edition,
160 Some(krate.clone()),
161 meta.cfg,
162 meta.env,
163 Default::default(),
164 );
165 let crate_name = CrateName::new(&krate).unwrap();
166 let prev = crates.insert(crate_name.clone(), crate_id);
167 assert!(prev.is_none());
168 for dep in meta.deps {
169 let dep = CrateName::new(&dep).unwrap();
170 crate_deps.push((crate_name.clone(), dep))
171 }
172 } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
173 assert!(default_crate_root.is_none());
174 default_crate_root = Some(file_id);
175 default_cfg = meta.cfg;
176 }
177
178 change.change_file(file_id, Some(Arc::new(text)));
179 let path = VfsPath::new_virtual_path(meta.path);
180 file_set.insert(file_id, path.into());
181 files.push(file_id);
182 file_id.0 += 1;
183 }
147 184
148 if let Some(krate) = meta.krate { 185 if crates.is_empty() {
149 let crate_id = crate_graph.add_crate_root( 186 let crate_root = default_crate_root.unwrap();
150 file_id, 187 crate_graph.add_crate_root(
151 meta.edition, 188 crate_root,
152 Some(krate.clone()), 189 Edition::Edition2018,
153 meta.cfg, 190 Some("test".to_string()),
154 meta.env, 191 default_cfg,
192 Env::default(),
155 Default::default(), 193 Default::default(),
156 ); 194 );
157 let crate_name = CrateName::new(&krate).unwrap(); 195 } else {
158 let prev = crates.insert(crate_name.clone(), crate_id); 196 for (from, to) in crate_deps {
159 assert!(prev.is_none()); 197 let from_id = crates[&from];
160 for dep in meta.deps { 198 let to_id = crates[&to];
161 let dep = CrateName::new(&dep).unwrap(); 199 crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap();
162 crate_deps.push((crate_name.clone(), dep))
163 } 200 }
164 } else if meta.path == "/main.rs" || meta.path == "/lib.rs" {
165 assert!(default_crate_root.is_none());
166 default_crate_root = Some(file_id);
167 } 201 }
168 202
169 db.set_file_text(file_id, Arc::new(text)); 203 change.set_roots(vec![SourceRoot::new_local(file_set)]);
170 db.set_file_source_root(file_id, source_root_id); 204 change.set_crate_graph(crate_graph);
171 let path = VfsPath::new_virtual_path(meta.path);
172 file_set.insert(file_id, path.into());
173 files.push(file_id);
174 file_id.0 += 1;
175 }
176 205
177 if crates.is_empty() { 206 ChangeFixture { file_position, files, change }
178 let crate_root = default_crate_root.unwrap();
179 crate_graph.add_crate_root(
180 crate_root,
181 Edition::Edition2018,
182 None,
183 CfgOptions::default(),
184 Env::default(),
185 Default::default(),
186 );
187 } else {
188 for (from, to) in crate_deps {
189 let from_id = crates[&from];
190 let to_id = crates[&to];
191 crate_graph.add_dep(from_id, CrateName::new(&to).unwrap(), to_id).unwrap();
192 }
193 } 207 }
194
195 db.set_source_root(source_root_id, Arc::new(SourceRoot::new_local(file_set)));
196 db.set_crate_graph(Arc::new(crate_graph));
197
198 (file_position, files)
199} 208}
200 209
201struct FileMeta { 210struct FileMeta {
diff --git a/crates/base_db/src/lib.rs b/crates/base_db/src/lib.rs
index ee3415850..e38aa7257 100644
--- a/crates/base_db/src/lib.rs
+++ b/crates/base_db/src/lib.rs
@@ -1,6 +1,7 @@
1//! base_db defines basic database traits. The concrete DB is defined by ide. 1//! base_db defines basic database traits. The concrete DB is defined by ide.
2mod cancellation; 2mod cancellation;
3mod input; 3mod input;
4mod change;
4pub mod fixture; 5pub mod fixture;
5 6
6use std::{panic, sync::Arc}; 7use std::{panic, sync::Arc};
@@ -10,6 +11,7 @@ use syntax::{ast, Parse, SourceFile, TextRange, TextSize};
10 11
11pub use crate::{ 12pub use crate::{
12 cancellation::Canceled, 13 cancellation::Canceled,
14 change::Change,
13 input::{ 15 input::{
14 CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId, 16 CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, FileId, ProcMacroId,
15 SourceRoot, SourceRootId, 17 SourceRoot, SourceRootId,
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 567fd91af..5721a66c4 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -145,7 +145,7 @@ impl Crate {
145 } 145 }
146 }).flat_map(|t| t).next(); 146 }).flat_map(|t| t).next();
147 147
148 doc_url.map(|s| s.trim_matches('"').trim_end_matches("/").to_owned() + "/") 148 doc_url.map(|s| s.trim_matches('"').trim_end_matches('/').to_owned() + "/")
149 } 149 }
150} 150}
151 151
diff --git a/crates/hir_def/src/nameres/collector.rs b/crates/hir_def/src/nameres/collector.rs
index 100e25ffc..c8cd04264 100644
--- a/crates/hir_def/src/nameres/collector.rs
+++ b/crates/hir_def/src/nameres/collector.rs
@@ -1229,9 +1229,10 @@ impl ModCollector<'_, '_> {
1229 } else { 1229 } else {
1230 let derive = attrs.by_key("proc_macro_derive"); 1230 let derive = attrs.by_key("proc_macro_derive");
1231 if let Some(arg) = derive.tt_values().next() { 1231 if let Some(arg) = derive.tt_values().next() {
1232 if let [TokenTree::Leaf(Leaf::Ident(trait_name))] = &*arg.token_trees { 1232 if let [TokenTree::Leaf(Leaf::Ident(trait_name)), ..] = &*arg.token_trees {
1233 trait_name.as_name() 1233 trait_name.as_name()
1234 } else { 1234 } else {
1235 log::trace!("malformed `#[proc_macro_derive]`: {}", arg);
1235 return; 1236 return;
1236 } 1237 }
1237 } else { 1238 } else {
diff --git a/crates/hir_def/src/nameres/tests/macros.rs b/crates/hir_def/src/nameres/tests/macros.rs
index 0851c3b7d..305fca0f9 100644
--- a/crates/hir_def/src/nameres/tests/macros.rs
+++ b/crates/hir_def/src/nameres/tests/macros.rs
@@ -688,13 +688,20 @@ fn resolves_proc_macros() {
688 pub fn derive_macro(_item: TokenStream) -> TokenStream { 688 pub fn derive_macro(_item: TokenStream) -> TokenStream {
689 TokenStream 689 TokenStream
690 } 690 }
691
692 #[proc_macro_derive(AnotherTrait, attributes(helper_attr))]
693 pub fn derive_macro_2(_item: TokenStream) -> TokenStream {
694 TokenStream
695 }
691 ", 696 ",
692 expect![[r#" 697 expect![[r#"
693 crate 698 crate
699 AnotherTrait: m
694 DummyTrait: m 700 DummyTrait: m
695 TokenStream: t v 701 TokenStream: t v
696 attribute_macro: v m 702 attribute_macro: v m
697 derive_macro: v 703 derive_macro: v
704 derive_macro_2: v
698 function_like_macro: v m 705 function_like_macro: v m
699 "#]], 706 "#]],
700 ); 707 );
diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs
index 58e26b94c..d2cf2cc7d 100644
--- a/crates/ide/src/call_hierarchy.rs
+++ b/crates/ide/src/call_hierarchy.rs
@@ -139,7 +139,7 @@ impl CallLocations {
139mod tests { 139mod tests {
140 use base_db::FilePosition; 140 use base_db::FilePosition;
141 141
142 use crate::mock_analysis::analysis_and_position; 142 use crate::fixture;
143 143
144 fn check_hierarchy( 144 fn check_hierarchy(
145 ra_fixture: &str, 145 ra_fixture: &str,
@@ -147,7 +147,7 @@ mod tests {
147 expected_incoming: &[&str], 147 expected_incoming: &[&str],
148 expected_outgoing: &[&str], 148 expected_outgoing: &[&str],
149 ) { 149 ) {
150 let (analysis, pos) = analysis_and_position(ra_fixture); 150 let (analysis, pos) = fixture::position(ra_fixture);
151 151
152 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info; 152 let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
153 assert_eq!(navs.len(), 1); 153 assert_eq!(navs.len(), 1);
@@ -181,8 +181,8 @@ fn caller() {
181 call<|>ee(); 181 call<|>ee();
182} 182}
183"#, 183"#,
184 "callee FN FileId(1) 0..14 3..9", 184 "callee FN FileId(0) 0..14 3..9",
185 &["caller FN FileId(1) 15..44 18..24 : [33..39]"], 185 &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
186 &[], 186 &[],
187 ); 187 );
188 } 188 }
@@ -197,8 +197,8 @@ fn caller() {
197 callee(); 197 callee();
198} 198}
199"#, 199"#,
200 "callee FN FileId(1) 0..14 3..9", 200 "callee FN FileId(0) 0..14 3..9",
201 &["caller FN FileId(1) 15..44 18..24 : [33..39]"], 201 &["caller FN FileId(0) 15..44 18..24 : [33..39]"],
202 &[], 202 &[],
203 ); 203 );
204 } 204 }
@@ -214,8 +214,8 @@ fn caller() {
214 callee(); 214 callee();
215} 215}
216"#, 216"#,
217 "callee FN FileId(1) 0..14 3..9", 217 "callee FN FileId(0) 0..14 3..9",
218 &["caller FN FileId(1) 15..58 18..24 : [33..39, 47..53]"], 218 &["caller FN FileId(0) 15..58 18..24 : [33..39, 47..53]"],
219 &[], 219 &[],
220 ); 220 );
221 } 221 }
@@ -234,10 +234,10 @@ fn caller2() {
234 callee(); 234 callee();
235} 235}
236"#, 236"#,
237 "callee FN FileId(1) 0..14 3..9", 237 "callee FN FileId(0) 0..14 3..9",
238 &[ 238 &[
239 "caller1 FN FileId(1) 15..45 18..25 : [34..40]", 239 "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
240 "caller2 FN FileId(1) 47..77 50..57 : [66..72]", 240 "caller2 FN FileId(0) 47..77 50..57 : [66..72]",
241 ], 241 ],
242 &[], 242 &[],
243 ); 243 );
@@ -263,10 +263,10 @@ mod tests {
263 } 263 }
264} 264}
265"#, 265"#,
266 "callee FN FileId(1) 0..14 3..9", 266 "callee FN FileId(0) 0..14 3..9",
267 &[ 267 &[
268 "caller1 FN FileId(1) 15..45 18..25 : [34..40]", 268 "caller1 FN FileId(0) 15..45 18..25 : [34..40]",
269 "test_caller FN FileId(1) 95..149 110..121 : [134..140]", 269 "test_caller FN FileId(0) 95..149 110..121 : [134..140]",
270 ], 270 ],
271 &[], 271 &[],
272 ); 272 );
@@ -287,8 +287,8 @@ fn caller() {
287//- /foo/mod.rs 287//- /foo/mod.rs
288pub fn callee() {} 288pub fn callee() {}
289"#, 289"#,
290 "callee FN FileId(2) 0..18 7..13", 290 "callee FN FileId(1) 0..18 7..13",
291 &["caller FN FileId(1) 27..56 30..36 : [45..51]"], 291 &["caller FN FileId(0) 27..56 30..36 : [45..51]"],
292 &[], 292 &[],
293 ); 293 );
294 } 294 }
@@ -304,9 +304,9 @@ fn call<|>er() {
304 callee(); 304 callee();
305} 305}
306"#, 306"#,
307 "caller FN FileId(1) 15..58 18..24", 307 "caller FN FileId(0) 15..58 18..24",
308 &[], 308 &[],
309 &["callee FN FileId(1) 0..14 3..9 : [33..39, 47..53]"], 309 &["callee FN FileId(0) 0..14 3..9 : [33..39, 47..53]"],
310 ); 310 );
311 } 311 }
312 312
@@ -325,9 +325,9 @@ fn call<|>er() {
325//- /foo/mod.rs 325//- /foo/mod.rs
326pub fn callee() {} 326pub fn callee() {}
327"#, 327"#,
328 "caller FN FileId(1) 27..56 30..36", 328 "caller FN FileId(0) 27..56 30..36",
329 &[], 329 &[],
330 &["callee FN FileId(2) 0..18 7..13 : [45..51]"], 330 &["callee FN FileId(1) 0..18 7..13 : [45..51]"],
331 ); 331 );
332 } 332 }
333 333
@@ -348,9 +348,9 @@ fn caller3() {
348 348
349} 349}
350"#, 350"#,
351 "caller2 FN FileId(1) 33..64 36..43", 351 "caller2 FN FileId(0) 33..64 36..43",
352 &["caller1 FN FileId(1) 0..31 3..10 : [19..26]"], 352 &["caller1 FN FileId(0) 0..31 3..10 : [19..26]"],
353 &["caller3 FN FileId(1) 66..83 69..76 : [52..59]"], 353 &["caller3 FN FileId(0) 66..83 69..76 : [52..59]"],
354 ); 354 );
355 } 355 }
356 356
@@ -368,9 +368,9 @@ fn main() {
368 a<|>() 368 a<|>()
369} 369}
370"#, 370"#,
371 "a FN FileId(1) 0..18 3..4", 371 "a FN FileId(0) 0..18 3..4",
372 &["main FN FileId(1) 31..52 34..38 : [47..48]"], 372 &["main FN FileId(0) 31..52 34..38 : [47..48]"],
373 &["b FN FileId(1) 20..29 23..24 : [13..14]"], 373 &["b FN FileId(0) 20..29 23..24 : [13..14]"],
374 ); 374 );
375 375
376 check_hierarchy( 376 check_hierarchy(
@@ -385,8 +385,8 @@ fn main() {
385 a() 385 a()
386} 386}
387"#, 387"#,
388 "b FN FileId(1) 20..29 23..24", 388 "b FN FileId(0) 20..29 23..24",
389 &["a FN FileId(1) 0..18 3..4 : [13..14]"], 389 &["a FN FileId(0) 0..18 3..4 : [13..14]"],
390 &[], 390 &[],
391 ); 391 );
392 } 392 }
diff --git a/crates/ide/src/call_info.rs b/crates/ide/src/call_info.rs
index 7e99c6b72..d7b2b926e 100644
--- a/crates/ide/src/call_info.rs
+++ b/crates/ide/src/call_info.rs
@@ -232,10 +232,10 @@ mod tests {
232 use expect_test::{expect, Expect}; 232 use expect_test::{expect, Expect};
233 use test_utils::mark; 233 use test_utils::mark;
234 234
235 use crate::mock_analysis::analysis_and_position; 235 use crate::fixture;
236 236
237 fn check(ra_fixture: &str, expect: Expect) { 237 fn check(ra_fixture: &str, expect: Expect) {
238 let (analysis, position) = analysis_and_position(ra_fixture); 238 let (analysis, position) = fixture::position(ra_fixture);
239 let call_info = analysis.call_info(position).unwrap(); 239 let call_info = analysis.call_info(position).unwrap();
240 let actual = match call_info { 240 let actual = match call_info {
241 Some(call_info) => { 241 Some(call_info) => {
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs
index daea2aa95..697f691b0 100644
--- a/crates/ide/src/completion.rs
+++ b/crates/ide/src/completion.rs
@@ -133,7 +133,7 @@ pub(crate) fn completions(
133#[cfg(test)] 133#[cfg(test)]
134mod tests { 134mod tests {
135 use crate::completion::completion_config::CompletionConfig; 135 use crate::completion::completion_config::CompletionConfig;
136 use crate::mock_analysis::analysis_and_position; 136 use crate::fixture;
137 137
138 struct DetailAndDocumentation<'a> { 138 struct DetailAndDocumentation<'a> {
139 detail: &'a str, 139 detail: &'a str,
@@ -141,7 +141,7 @@ mod tests {
141 } 141 }
142 142
143 fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) { 143 fn check_detail_and_documentation(ra_fixture: &str, expected: DetailAndDocumentation) {
144 let (analysis, position) = analysis_and_position(ra_fixture); 144 let (analysis, position) = fixture::position(ra_fixture);
145 let config = CompletionConfig::default(); 145 let config = CompletionConfig::default();
146 let completions = analysis.completions(&config, position).unwrap().unwrap(); 146 let completions = analysis.completions(&config, position).unwrap().unwrap();
147 for item in completions { 147 for item in completions {
diff --git a/crates/ide/src/completion/complete_keyword.rs b/crates/ide/src/completion/complete_keyword.rs
index 5645b41fa..e59747095 100644
--- a/crates/ide/src/completion/complete_keyword.rs
+++ b/crates/ide/src/completion/complete_keyword.rs
@@ -495,13 +495,13 @@ Some multi-line comment<|>
495 fn test_completion_await_impls_future() { 495 fn test_completion_await_impls_future() {
496 check( 496 check(
497 r#" 497 r#"
498//- /main.rs 498//- /main.rs crate:main deps:std
499use std::future::*; 499use std::future::*;
500struct A {} 500struct A {}
501impl Future for A {} 501impl Future for A {}
502fn foo(a: A) { a.<|> } 502fn foo(a: A) { a.<|> }
503 503
504//- /std/lib.rs 504//- /std/lib.rs crate:std
505pub mod future { 505pub mod future {
506 #[lang = "future_trait"] 506 #[lang = "future_trait"]
507 pub trait Future {} 507 pub trait Future {}
@@ -514,14 +514,14 @@ pub mod future {
514 514
515 check( 515 check(
516 r#" 516 r#"
517//- /main.rs 517//- /main.rs crate:main deps:std
518use std::future::*; 518use std::future::*;
519fn foo() { 519fn foo() {
520 let a = async {}; 520 let a = async {};
521 a.<|> 521 a.<|>
522} 522}
523 523
524//- /std/lib.rs 524//- /std/lib.rs crate:std
525pub mod future { 525pub mod future {
526 #[lang = "future_trait"] 526 #[lang = "future_trait"]
527 pub trait Future { 527 pub trait Future {
diff --git a/crates/ide/src/completion/complete_mod.rs b/crates/ide/src/completion/complete_mod.rs
index 3cfc2e131..c7a99bdc3 100644
--- a/crates/ide/src/completion/complete_mod.rs
+++ b/crates/ide/src/completion/complete_mod.rs
@@ -300,7 +300,7 @@ mod tests {
300 // "#, 300 // "#,
301 // expect![[r#" 301 // expect![[r#"
302 // md bar; 302 // md bar;
303 // "#]], 303 // "#]],foo
304 // ); 304 // );
305 // } 305 // }
306 306
@@ -308,7 +308,7 @@ mod tests {
308 fn already_declared_bin_module_completion_omitted() { 308 fn already_declared_bin_module_completion_omitted() {
309 check( 309 check(
310 r#" 310 r#"
311 //- /src/bin.rs 311 //- /src/bin.rs crate:main
312 fn main() {} 312 fn main() {}
313 //- /src/bin/foo.rs 313 //- /src/bin/foo.rs
314 mod <|> 314 mod <|>
diff --git a/crates/ide/src/completion/complete_postfix.rs b/crates/ide/src/completion/complete_postfix.rs
index 26a5af5b9..db5319618 100644
--- a/crates/ide/src/completion/complete_postfix.rs
+++ b/crates/ide/src/completion/complete_postfix.rs
@@ -1,11 +1,15 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2
3mod format_like;
4
2use assists::utils::TryEnum; 5use assists::utils::TryEnum;
3use syntax::{ 6use syntax::{
4 ast::{self, AstNode}, 7 ast::{self, AstNode, AstToken},
5 TextRange, TextSize, 8 TextRange, TextSize,
6}; 9};
7use text_edit::TextEdit; 10use text_edit::TextEdit;
8 11
12use self::format_like::add_format_like_completions;
9use crate::{ 13use crate::{
10 completion::{ 14 completion::{
11 completion_config::SnippetCap, 15 completion_config::SnippetCap,
@@ -207,6 +211,12 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
207 &format!("${{1}}({})", receiver_text), 211 &format!("${{1}}({})", receiver_text),
208 ) 212 )
209 .add_to(acc); 213 .add_to(acc);
214
215 if let ast::Expr::Literal(literal) = dot_receiver.clone() {
216 if let Some(literal_text) = ast::String::cast(literal.token()) {
217 add_format_like_completions(acc, ctx, &dot_receiver, cap, &literal_text);
218 }
219 }
210} 220}
211 221
212fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String { 222fn get_receiver_text(receiver: &ast::Expr, receiver_is_ambiguous_float_literal: bool) -> String {
@@ -392,4 +402,53 @@ fn main() {
392 check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#); 402 check_edit("dbg", r#"fn main() { &&42.<|> }"#, r#"fn main() { dbg!(&&42) }"#);
393 check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#); 403 check_edit("refm", r#"fn main() { &&42.<|> }"#, r#"fn main() { &&&mut 42 }"#);
394 } 404 }
405
406 #[test]
407 fn postfix_completion_for_format_like_strings() {
408 check_edit(
409 "fmt",
410 r#"fn main() { "{some_var:?}".<|> }"#,
411 r#"fn main() { format!("{:?}", some_var) }"#,
412 );
413 check_edit(
414 "panic",
415 r#"fn main() { "Panic with {a}".<|> }"#,
416 r#"fn main() { panic!("Panic with {}", a) }"#,
417 );
418 check_edit(
419 "println",
420 r#"fn main() { "{ 2+2 } { SomeStruct { val: 1, other: 32 } :?}".<|> }"#,
421 r#"fn main() { println!("{} {:?}", 2+2, SomeStruct { val: 1, other: 32 }) }"#,
422 );
423 check_edit(
424 "loge",
425 r#"fn main() { "{2+2}".<|> }"#,
426 r#"fn main() { log::error!("{}", 2+2) }"#,
427 );
428 check_edit(
429 "logt",
430 r#"fn main() { "{2+2}".<|> }"#,
431 r#"fn main() { log::trace!("{}", 2+2) }"#,
432 );
433 check_edit(
434 "logd",
435 r#"fn main() { "{2+2}".<|> }"#,
436 r#"fn main() { log::debug!("{}", 2+2) }"#,
437 );
438 check_edit(
439 "logi",
440 r#"fn main() { "{2+2}".<|> }"#,
441 r#"fn main() { log::info!("{}", 2+2) }"#,
442 );
443 check_edit(
444 "logw",
445 r#"fn main() { "{2+2}".<|> }"#,
446 r#"fn main() { log::warn!("{}", 2+2) }"#,
447 );
448 check_edit(
449 "loge",
450 r#"fn main() { "{2+2}".<|> }"#,
451 r#"fn main() { log::error!("{}", 2+2) }"#,
452 );
453 }
395} 454}
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
new file mode 100644
index 000000000..81c33bf3a
--- /dev/null
+++ b/crates/ide/src/completion/complete_postfix/format_like.rs
@@ -0,0 +1,277 @@
1// Feature: Format String Completion.
2//
3// `"Result {result} is {2 + 2}"` is expanded to the `"Result {} is {}", result, 2 + 2`.
4//
5// The following postfix snippets are available:
6//
7// - `format` -> `format!(...)`
8// - `panic` -> `panic!(...)`
9// - `println` -> `println!(...)`
10// - `log`:
11// + `logd` -> `log::debug!(...)`
12// + `logt` -> `log::trace!(...)`
13// + `logi` -> `log::info!(...)`
14// + `logw` -> `log::warn!(...)`
15// + `loge` -> `log::error!(...)`
16
17use crate::completion::{
18 complete_postfix::postfix_snippet, completion_config::SnippetCap,
19 completion_context::CompletionContext, completion_item::Completions,
20};
21use syntax::ast::{self, AstToken};
22
23/// Mapping ("postfix completion item" => "macro to use")
24static KINDS: &[(&str, &str)] = &[
25 ("fmt", "format!"),
26 ("panic", "panic!"),
27 ("println", "println!"),
28 ("logd", "log::debug!"),
29 ("logt", "log::trace!"),
30 ("logi", "log::info!"),
31 ("logw", "log::warn!"),
32 ("loge", "log::error!"),
33];
34
35pub(super) fn add_format_like_completions(
36 acc: &mut Completions,
37 ctx: &CompletionContext,
38 dot_receiver: &ast::Expr,
39 cap: SnippetCap,
40 receiver_text: &ast::String,
41) {
42 let input = match string_literal_contents(receiver_text) {
43 // It's not a string literal, do not parse input.
44 Some(input) => input,
45 None => return,
46 };
47
48 let mut parser = FormatStrParser::new(input);
49
50 if parser.parse().is_ok() {
51 for (label, macro_name) in KINDS {
52 let snippet = parser.into_suggestion(macro_name);
53
54 postfix_snippet(ctx, cap, &dot_receiver, label, macro_name, &snippet).add_to(acc);
55 }
56 }
57}
58
59/// Checks whether provided item is a string literal.
60fn string_literal_contents(item: &ast::String) -> Option<String> {
61 let item = item.text();
62 if item.len() >= 2 && item.starts_with("\"") && item.ends_with("\"") {
63 return Some(item[1..item.len() - 1].to_owned());
64 }
65
66 None
67}
68
69/// Parser for a format-like string. It is more allowing in terms of string contents,
70/// as we expect variable placeholders to be filled with expressions.
71#[derive(Debug)]
72pub struct FormatStrParser {
73 input: String,
74 output: String,
75 extracted_expressions: Vec<String>,
76 state: State,
77 parsed: bool,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq)]
81enum State {
82 NotExpr,
83 MaybeExpr,
84 Expr,
85 MaybeIncorrect,
86 FormatOpts,
87}
88
89impl FormatStrParser {
90 pub fn new(input: String) -> Self {
91 Self {
92 input: input.into(),
93 output: String::new(),
94 extracted_expressions: Vec::new(),
95 state: State::NotExpr,
96 parsed: false,
97 }
98 }
99
100 pub fn parse(&mut self) -> Result<(), ()> {
101 let mut current_expr = String::new();
102
103 let mut placeholder_id = 1;
104
105 // Count of open braces inside of an expression.
106 // We assume that user knows what they're doing, thus we treat it like a correct pattern, e.g.
107 // "{MyStruct { val_a: 0, val_b: 1 }}".
108 let mut inexpr_open_count = 0;
109
110 for chr in self.input.chars() {
111 match (self.state, chr) {
112 (State::NotExpr, '{') => {
113 self.output.push(chr);
114 self.state = State::MaybeExpr;
115 }
116 (State::NotExpr, '}') => {
117 self.output.push(chr);
118 self.state = State::MaybeIncorrect;
119 }
120 (State::NotExpr, _) => {
121 self.output.push(chr);
122 }
123 (State::MaybeIncorrect, '}') => {
124 // It's okay, we met "}}".
125 self.output.push(chr);
126 self.state = State::NotExpr;
127 }
128 (State::MaybeIncorrect, _) => {
129 // Error in the string.
130 return Err(());
131 }
132 (State::MaybeExpr, '{') => {
133 self.output.push(chr);
134 self.state = State::NotExpr;
135 }
136 (State::MaybeExpr, '}') => {
137 // This is an empty sequence '{}'. Replace it with placeholder.
138 self.output.push(chr);
139 self.extracted_expressions.push(format!("${}", placeholder_id));
140 placeholder_id += 1;
141 self.state = State::NotExpr;
142 }
143 (State::MaybeExpr, _) => {
144 current_expr.push(chr);
145 self.state = State::Expr;
146 }
147 (State::Expr, '}') => {
148 if inexpr_open_count == 0 {
149 self.output.push(chr);
150 self.extracted_expressions.push(current_expr.trim().into());
151 current_expr = String::new();
152 self.state = State::NotExpr;
153 } else {
154 // We're closing one brace met before inside of the expression.
155 current_expr.push(chr);
156 inexpr_open_count -= 1;
157 }
158 }
159 (State::Expr, ':') => {
160 if inexpr_open_count == 0 {
161 // We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
162 self.output.push(chr);
163 self.extracted_expressions.push(current_expr.trim().into());
164 current_expr = String::new();
165 self.state = State::FormatOpts;
166 } else {
167 // We're inside of braced expression, assume that it's a struct field name/value delimeter.
168 current_expr.push(chr);
169 }
170 }
171 (State::Expr, '{') => {
172 current_expr.push(chr);
173 inexpr_open_count += 1;
174 }
175 (State::Expr, _) => {
176 current_expr.push(chr);
177 }
178 (State::FormatOpts, '}') => {
179 self.output.push(chr);
180 self.state = State::NotExpr;
181 }
182 (State::FormatOpts, _) => {
183 self.output.push(chr);
184 }
185 }
186 }
187
188 if self.state != State::NotExpr {
189 return Err(());
190 }
191
192 self.parsed = true;
193 Ok(())
194 }
195
196 pub fn into_suggestion(&self, macro_name: &str) -> String {
197 assert!(self.parsed, "Attempt to get a suggestion from not parsed expression");
198
199 let expressions_as_string = self.extracted_expressions.join(", ");
200 format!(r#"{}("{}", {})"#, macro_name, self.output, expressions_as_string)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use expect_test::{expect, Expect};
208
209 fn check(input: &str, expect: &Expect) {
210 let mut parser = FormatStrParser::new((*input).to_owned());
211 let outcome_repr = if parser.parse().is_ok() {
212 // Parsing should be OK, expected repr is "string; expr_1, expr_2".
213 if parser.extracted_expressions.is_empty() {
214 parser.output
215 } else {
216 format!("{}; {}", parser.output, parser.extracted_expressions.join(", "))
217 }
218 } else {
219 // Parsing should fail, expected repr is "-".
220 "-".to_owned()
221 };
222
223 expect.assert_eq(&outcome_repr);
224 }
225
226 #[test]
227 fn format_str_parser() {
228 let test_vector = &[
229 ("no expressions", expect![["no expressions"]]),
230 ("{expr} is {2 + 2}", expect![["{} is {}; expr, 2 + 2"]]),
231 ("{expr:?}", expect![["{:?}; expr"]]),
232 ("{malformed", expect![["-"]]),
233 ("malformed}", expect![["-"]]),
234 ("{{correct", expect![["{{correct"]]),
235 ("correct}}", expect![["correct}}"]]),
236 ("{correct}}}", expect![["{}}}; correct"]]),
237 ("{correct}}}}}", expect![["{}}}}}; correct"]]),
238 ("{incorrect}}", expect![["-"]]),
239 ("placeholders {} {}", expect![["placeholders {} {}; $1, $2"]]),
240 ("mixed {} {2 + 2} {}", expect![["mixed {} {} {}; $1, 2 + 2, $2"]]),
241 (
242 "{SomeStruct { val_a: 0, val_b: 1 }}",
243 expect![["{}; SomeStruct { val_a: 0, val_b: 1 }"]],
244 ),
245 ("{expr:?} is {2.32f64:.5}", expect![["{:?} is {:.5}; expr, 2.32f64"]]),
246 (
247 "{SomeStruct { val_a: 0, val_b: 1 }:?}",
248 expect![["{:?}; SomeStruct { val_a: 0, val_b: 1 }"]],
249 ),
250 ("{ 2 + 2 }", expect![["{}; 2 + 2"]]),
251 ];
252
253 for (input, output) in test_vector {
254 check(input, output)
255 }
256 }
257
258 #[test]
259 fn test_into_suggestion() {
260 let test_vector = &[
261 ("println!", "{}", r#"println!("{}", $1)"#),
262 (
263 "log::info!",
264 "{} {expr} {} {2 + 2}",
265 r#"log::info!("{} {} {} {}", $1, expr, $2, 2 + 2)"#,
266 ),
267 ("format!", "{expr:?}", r#"format!("{:?}", expr)"#),
268 ];
269
270 for (kind, input, output) in test_vector {
271 let mut parser = FormatStrParser::new((*input).to_owned());
272 parser.parse().expect("Parsing must succeed");
273
274 assert_eq!(&parser.into_suggestion(*kind), output);
275 }
276 }
277}
diff --git a/crates/ide/src/completion/complete_qualified_path.rs b/crates/ide/src/completion/complete_qualified_path.rs
index 00e89f0fd..2fafedd47 100644
--- a/crates/ide/src/completion/complete_qualified_path.rs
+++ b/crates/ide/src/completion/complete_qualified_path.rs
@@ -422,10 +422,10 @@ fn foo() { let _ = U::<|> }
422 fn completes_use_paths_across_crates() { 422 fn completes_use_paths_across_crates() {
423 check( 423 check(
424 r#" 424 r#"
425//- /main.rs 425//- /main.rs crate:main deps:foo
426use foo::<|>; 426use foo::<|>;
427 427
428//- /foo/lib.rs 428//- /foo/lib.rs crate:foo
429pub mod bar { pub struct S; } 429pub mod bar { pub struct S; }
430"#, 430"#,
431 expect![[r#" 431 expect![[r#"
diff --git a/crates/ide/src/completion/complete_unqualified_path.rs b/crates/ide/src/completion/complete_unqualified_path.rs
index 8eda4b64d..8b6757195 100644
--- a/crates/ide/src/completion/complete_unqualified_path.rs
+++ b/crates/ide/src/completion/complete_unqualified_path.rs
@@ -267,14 +267,34 @@ fn quux() { <|> }
267 ); 267 );
268 } 268 }
269 269
270 /// Regression test for issue #6091.
271 #[test]
272 fn correctly_completes_module_items_prefixed_with_underscore() {
273 check_edit(
274 "_alpha",
275 r#"
276fn main() {
277 _<|>
278}
279fn _alpha() {}
280"#,
281 r#"
282fn main() {
283 _alpha()$0
284}
285fn _alpha() {}
286"#,
287 )
288 }
289
270 #[test] 290 #[test]
271 fn completes_extern_prelude() { 291 fn completes_extern_prelude() {
272 check( 292 check(
273 r#" 293 r#"
274//- /lib.rs 294//- /lib.rs crate:main deps:other_crate
275use <|>; 295use <|>;
276 296
277//- /other_crate/lib.rs 297//- /other_crate/lib.rs crate:other_crate
278// nothing here 298// nothing here
279"#, 299"#,
280 expect![[r#" 300 expect![[r#"
@@ -350,10 +370,10 @@ fn foo() {
350 fn completes_prelude() { 370 fn completes_prelude() {
351 check( 371 check(
352 r#" 372 r#"
353//- /main.rs 373//- /main.rs crate:main deps:std
354fn foo() { let x: <|> } 374fn foo() { let x: <|> }
355 375
356//- /std/lib.rs 376//- /std/lib.rs crate:std
357#[prelude_import] 377#[prelude_import]
358use prelude::*; 378use prelude::*;
359 379
@@ -371,16 +391,16 @@ mod prelude { struct Option; }
371 fn completes_std_prelude_if_core_is_defined() { 391 fn completes_std_prelude_if_core_is_defined() {
372 check( 392 check(
373 r#" 393 r#"
374//- /main.rs 394//- /main.rs crate:main deps:core,std
375fn foo() { let x: <|> } 395fn foo() { let x: <|> }
376 396
377//- /core/lib.rs 397//- /core/lib.rs crate:core
378#[prelude_import] 398#[prelude_import]
379use prelude::*; 399use prelude::*;
380 400
381mod prelude { struct Option; } 401mod prelude { struct Option; }
382 402
383//- /std/lib.rs 403//- /std/lib.rs crate:std deps:core
384#[prelude_import] 404#[prelude_import]
385use prelude::*; 405use prelude::*;
386 406
diff --git a/crates/ide/src/completion/completion_context.rs b/crates/ide/src/completion/completion_context.rs
index 671b13328..8dea8a4bf 100644
--- a/crates/ide/src/completion/completion_context.rs
+++ b/crates/ide/src/completion/completion_context.rs
@@ -221,10 +221,11 @@ impl<'a> CompletionContext<'a> {
221 Some(ctx) 221 Some(ctx)
222 } 222 }
223 223
224 // The range of the identifier that is being completed. 224 /// The range of the identifier that is being completed.
225 pub(crate) fn source_range(&self) -> TextRange { 225 pub(crate) fn source_range(&self) -> TextRange {
226 // check kind of macro-expanded token, but use range of original token 226 // check kind of macro-expanded token, but use range of original token
227 if self.token.kind() == IDENT || self.token.kind().is_keyword() { 227 let kind = self.token.kind();
228 if kind == IDENT || kind == UNDERSCORE || kind.is_keyword() {
228 mark::hit!(completes_if_prefix_is_keyword); 229 mark::hit!(completes_if_prefix_is_keyword);
229 self.original_token.text_range() 230 self.original_token.text_range()
230 } else { 231 } else {
@@ -469,7 +470,7 @@ impl<'a> CompletionContext<'a> {
469 } 470 }
470 } else { 471 } else {
471 false 472 false
472 } 473 };
473 } 474 }
474 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { 475 if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) {
475 // As above 476 // As above
diff --git a/crates/ide/src/completion/presentation.rs b/crates/ide/src/completion/presentation.rs
index 987cbfa7a..a5172b87e 100644
--- a/crates/ide/src/completion/presentation.rs
+++ b/crates/ide/src/completion/presentation.rs
@@ -1172,9 +1172,9 @@ fn foo(xs: Vec<i128>)
1172 check_edit( 1172 check_edit(
1173 "frobnicate!", 1173 "frobnicate!",
1174 r#" 1174 r#"
1175//- /main.rs 1175//- /main.rs crate:main deps:foo
1176use foo::<|>; 1176use foo::<|>;
1177//- /foo/lib.rs 1177//- /foo/lib.rs crate:foo
1178#[macro_export] 1178#[macro_export]
1179macro_rules frobnicate { () => () } 1179macro_rules frobnicate { () => () }
1180"#, 1180"#,
diff --git a/crates/ide/src/completion/test_utils.rs b/crates/ide/src/completion/test_utils.rs
index 1452d7e9e..feb8cd2a6 100644
--- a/crates/ide/src/completion/test_utils.rs
+++ b/crates/ide/src/completion/test_utils.rs
@@ -8,8 +8,7 @@ use test_utils::assert_eq_text;
8 8
9use crate::{ 9use crate::{
10 completion::{completion_item::CompletionKind, CompletionConfig}, 10 completion::{completion_item::CompletionKind, CompletionConfig},
11 mock_analysis::analysis_and_position, 11 fixture, CompletionItem,
12 CompletionItem,
13}; 12};
14 13
15pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { 14pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> {
@@ -80,7 +79,7 @@ pub(crate) fn check_edit_with_config(
80 ra_fixture_after: &str, 79 ra_fixture_after: &str,
81) { 80) {
82 let ra_fixture_after = trim_indent(ra_fixture_after); 81 let ra_fixture_after = trim_indent(ra_fixture_after);
83 let (analysis, position) = analysis_and_position(ra_fixture_before); 82 let (analysis, position) = fixture::position(ra_fixture_before);
84 let completions: Vec<CompletionItem> = 83 let completions: Vec<CompletionItem> =
85 analysis.completions(&config, position).unwrap().unwrap().into(); 84 analysis.completions(&config, position).unwrap().unwrap().into();
86 let (completion,) = completions 85 let (completion,) = completions
@@ -94,7 +93,7 @@ pub(crate) fn check_edit_with_config(
94} 93}
95 94
96pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { 95pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) {
97 let (analysis, pos) = analysis_and_position(code); 96 let (analysis, pos) = fixture::position(code);
98 analysis 97 analysis
99 .with_db(|db| { 98 .with_db(|db| {
100 let sema = Semantics::new(db); 99 let sema = Semantics::new(db);
@@ -109,6 +108,6 @@ pub(crate) fn get_all_completion_items(
109 config: CompletionConfig, 108 config: CompletionConfig,
110 code: &str, 109 code: &str,
111) -> Vec<CompletionItem> { 110) -> Vec<CompletionItem> {
112 let (analysis, position) = analysis_and_position(code); 111 let (analysis, position) = fixture::position(code);
113 analysis.completions(&config, position).unwrap().unwrap().into() 112 analysis.completions(&config, position).unwrap().unwrap().into()
114} 113}
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index dc815a483..f5d627b6e 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -218,10 +218,7 @@ mod tests {
218 use stdx::trim_indent; 218 use stdx::trim_indent;
219 use test_utils::assert_eq_text; 219 use test_utils::assert_eq_text;
220 220
221 use crate::{ 221 use crate::{fixture, DiagnosticsConfig};
222 mock_analysis::{analysis_and_position, single_file, MockAnalysis},
223 DiagnosticsConfig,
224 };
225 222
226 /// Takes a multi-file input fixture with annotated cursor positions, 223 /// Takes a multi-file input fixture with annotated cursor positions,
227 /// and checks that: 224 /// and checks that:
@@ -231,7 +228,7 @@ mod tests {
231 fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { 228 fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) {
232 let after = trim_indent(ra_fixture_after); 229 let after = trim_indent(ra_fixture_after);
233 230
234 let (analysis, file_position) = analysis_and_position(ra_fixture_before); 231 let (analysis, file_position) = fixture::position(ra_fixture_before);
235 let diagnostic = analysis 232 let diagnostic = analysis
236 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) 233 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
237 .unwrap() 234 .unwrap()
@@ -260,7 +257,7 @@ mod tests {
260 /// which has a fix that can apply to other files. 257 /// which has a fix that can apply to other files.
261 fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) { 258 fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) {
262 let ra_fixture_after = &trim_indent(ra_fixture_after); 259 let ra_fixture_after = &trim_indent(ra_fixture_after);
263 let (analysis, file_pos) = analysis_and_position(ra_fixture_before); 260 let (analysis, file_pos) = fixture::position(ra_fixture_before);
264 let current_file_id = file_pos.file_id; 261 let current_file_id = file_pos.file_id;
265 let diagnostic = analysis 262 let diagnostic = analysis
266 .diagnostics(&DiagnosticsConfig::default(), current_file_id) 263 .diagnostics(&DiagnosticsConfig::default(), current_file_id)
@@ -282,9 +279,7 @@ mod tests {
282 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics 279 /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics
283 /// apply to the file containing the cursor. 280 /// apply to the file containing the cursor.
284 fn check_no_diagnostics(ra_fixture: &str) { 281 fn check_no_diagnostics(ra_fixture: &str) {
285 let mock = MockAnalysis::with_files(ra_fixture); 282 let (analysis, files) = fixture::files(ra_fixture);
286 let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
287 let analysis = mock.analysis();
288 let diagnostics = files 283 let diagnostics = files
289 .into_iter() 284 .into_iter()
290 .flat_map(|file_id| { 285 .flat_map(|file_id| {
@@ -295,7 +290,7 @@ mod tests {
295 } 290 }
296 291
297 fn check_expect(ra_fixture: &str, expect: Expect) { 292 fn check_expect(ra_fixture: &str, expect: Expect) {
298 let (analysis, file_id) = single_file(ra_fixture); 293 let (analysis, file_id) = fixture::file(ra_fixture);
299 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 294 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
300 expect.assert_debug_eq(&diagnostics) 295 expect.assert_debug_eq(&diagnostics)
301 } 296 }
@@ -304,7 +299,7 @@ mod tests {
304 fn test_wrap_return_type() { 299 fn test_wrap_return_type() {
305 check_fix( 300 check_fix(
306 r#" 301 r#"
307//- /main.rs 302//- /main.rs crate:main deps:core
308use core::result::Result::{self, Ok, Err}; 303use core::result::Result::{self, Ok, Err};
309 304
310fn div(x: i32, y: i32) -> Result<i32, ()> { 305fn div(x: i32, y: i32) -> Result<i32, ()> {
@@ -313,7 +308,7 @@ fn div(x: i32, y: i32) -> Result<i32, ()> {
313 } 308 }
314 x / y<|> 309 x / y<|>
315} 310}
316//- /core/lib.rs 311//- /core/lib.rs crate:core
317pub mod result { 312pub mod result {
318 pub enum Result<T, E> { Ok(T), Err(E) } 313 pub enum Result<T, E> { Ok(T), Err(E) }
319} 314}
@@ -335,7 +330,7 @@ fn div(x: i32, y: i32) -> Result<i32, ()> {
335 fn test_wrap_return_type_handles_generic_functions() { 330 fn test_wrap_return_type_handles_generic_functions() {
336 check_fix( 331 check_fix(
337 r#" 332 r#"
338//- /main.rs 333//- /main.rs crate:main deps:core
339use core::result::Result::{self, Ok, Err}; 334use core::result::Result::{self, Ok, Err};
340 335
341fn div<T>(x: T) -> Result<T, i32> { 336fn div<T>(x: T) -> Result<T, i32> {
@@ -344,7 +339,7 @@ fn div<T>(x: T) -> Result<T, i32> {
344 } 339 }
345 <|>x 340 <|>x
346} 341}
347//- /core/lib.rs 342//- /core/lib.rs crate:core
348pub mod result { 343pub mod result {
349 pub enum Result<T, E> { Ok(T), Err(E) } 344 pub enum Result<T, E> { Ok(T), Err(E) }
350} 345}
@@ -366,7 +361,7 @@ fn div<T>(x: T) -> Result<T, i32> {
366 fn test_wrap_return_type_handles_type_aliases() { 361 fn test_wrap_return_type_handles_type_aliases() {
367 check_fix( 362 check_fix(
368 r#" 363 r#"
369//- /main.rs 364//- /main.rs crate:main deps:core
370use core::result::Result::{self, Ok, Err}; 365use core::result::Result::{self, Ok, Err};
371 366
372type MyResult<T> = Result<T, ()>; 367type MyResult<T> = Result<T, ()>;
@@ -377,7 +372,7 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
377 } 372 }
378 x <|>/ y 373 x <|>/ y
379} 374}
380//- /core/lib.rs 375//- /core/lib.rs crate:core
381pub mod result { 376pub mod result {
382 pub enum Result<T, E> { Ok(T), Err(E) } 377 pub enum Result<T, E> { Ok(T), Err(E) }
383} 378}
@@ -401,12 +396,12 @@ fn div(x: i32, y: i32) -> MyResult<i32> {
401 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { 396 fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() {
402 check_no_diagnostics( 397 check_no_diagnostics(
403 r#" 398 r#"
404//- /main.rs 399//- /main.rs crate:main deps:core
405use core::result::Result::{self, Ok, Err}; 400use core::result::Result::{self, Ok, Err};
406 401
407fn foo() -> Result<(), i32> { 0 } 402fn foo() -> Result<(), i32> { 0 }
408 403
409//- /core/lib.rs 404//- /core/lib.rs crate:core
410pub mod result { 405pub mod result {
411 pub enum Result<T, E> { Ok(T), Err(E) } 406 pub enum Result<T, E> { Ok(T), Err(E) }
412} 407}
@@ -418,14 +413,14 @@ pub mod result {
418 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { 413 fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() {
419 check_no_diagnostics( 414 check_no_diagnostics(
420 r#" 415 r#"
421//- /main.rs 416//- /main.rs crate:main deps:core
422use core::result::Result::{self, Ok, Err}; 417use core::result::Result::{self, Ok, Err};
423 418
424enum SomeOtherEnum { Ok(i32), Err(String) } 419enum SomeOtherEnum { Ok(i32), Err(String) }
425 420
426fn foo() -> SomeOtherEnum { 0 } 421fn foo() -> SomeOtherEnum { 0 }
427 422
428//- /core/lib.rs 423//- /core/lib.rs crate:core
429pub mod result { 424pub mod result {
430 pub enum Result<T, E> { Ok(T), Err(E) } 425 pub enum Result<T, E> { Ok(T), Err(E) }
431} 426}
@@ -567,7 +562,7 @@ fn test_fn() {
567 file_system_edits: [ 562 file_system_edits: [
568 CreateFile { 563 CreateFile {
569 anchor: FileId( 564 anchor: FileId(
570 1, 565 0,
571 ), 566 ),
572 dst: "foo.rs", 567 dst: "foo.rs",
573 }, 568 },
@@ -787,7 +782,7 @@ struct Foo {
787 let mut config = DiagnosticsConfig::default(); 782 let mut config = DiagnosticsConfig::default();
788 config.disabled.insert("unresolved-module".into()); 783 config.disabled.insert("unresolved-module".into());
789 784
790 let (analysis, file_id) = single_file(r#"mod foo;"#); 785 let (analysis, file_id) = fixture::file(r#"mod foo;"#);
791 786
792 let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); 787 let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
793 assert!(diagnostics.is_empty()); 788 assert!(diagnostics.is_empty());
diff --git a/crates/ide/src/display/navigation_target.rs b/crates/ide/src/display/navigation_target.rs
index 1ee80c2dd..cf9d617dc 100644
--- a/crates/ide/src/display/navigation_target.rs
+++ b/crates/ide/src/display/navigation_target.rs
@@ -423,11 +423,11 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) ->
423mod tests { 423mod tests {
424 use expect_test::expect; 424 use expect_test::expect;
425 425
426 use crate::{mock_analysis::single_file, Query}; 426 use crate::{fixture, Query};
427 427
428 #[test] 428 #[test]
429 fn test_nav_for_symbol() { 429 fn test_nav_for_symbol() {
430 let (analysis, _) = single_file( 430 let (analysis, _) = fixture::file(
431 r#" 431 r#"
432enum FooInner { } 432enum FooInner { }
433fn foo() { enum FooInner { } } 433fn foo() { enum FooInner { } }
@@ -439,7 +439,7 @@ fn foo() { enum FooInner { } }
439 [ 439 [
440 NavigationTarget { 440 NavigationTarget {
441 file_id: FileId( 441 file_id: FileId(
442 1, 442 0,
443 ), 443 ),
444 full_range: 0..17, 444 full_range: 0..17,
445 focus_range: Some( 445 focus_range: Some(
@@ -455,7 +455,7 @@ fn foo() { enum FooInner { } }
455 }, 455 },
456 NavigationTarget { 456 NavigationTarget {
457 file_id: FileId( 457 file_id: FileId(
458 1, 458 0,
459 ), 459 ),
460 full_range: 29..46, 460 full_range: 29..46,
461 focus_range: Some( 461 focus_range: Some(
@@ -478,7 +478,7 @@ fn foo() { enum FooInner { } }
478 478
479 #[test] 479 #[test]
480 fn test_world_symbols_are_case_sensitive() { 480 fn test_world_symbols_are_case_sensitive() {
481 let (analysis, _) = single_file( 481 let (analysis, _) = fixture::file(
482 r#" 482 r#"
483fn foo() {} 483fn foo() {}
484struct Foo; 484struct Foo;
diff --git a/crates/ide/src/expand_macro.rs b/crates/ide/src/expand_macro.rs
index 8a285bcf7..8d75e0f05 100644
--- a/crates/ide/src/expand_macro.rs
+++ b/crates/ide/src/expand_macro.rs
@@ -122,10 +122,10 @@ fn insert_whitespaces(syn: SyntaxNode) -> String {
122mod tests { 122mod tests {
123 use expect_test::{expect, Expect}; 123 use expect_test::{expect, Expect};
124 124
125 use crate::mock_analysis::analysis_and_position; 125 use crate::fixture;
126 126
127 fn check(ra_fixture: &str, expect: Expect) { 127 fn check(ra_fixture: &str, expect: Expect) {
128 let (analysis, pos) = analysis_and_position(ra_fixture); 128 let (analysis, pos) = fixture::position(ra_fixture);
129 let expansion = analysis.expand_macro(pos).unwrap().unwrap(); 129 let expansion = analysis.expand_macro(pos).unwrap().unwrap();
130 let actual = format!("{}\n{}", expansion.name, expansion.expansion); 130 let actual = format!("{}\n{}", expansion.name, expansion.expansion);
131 expect.assert_eq(&actual); 131 expect.assert_eq(&actual);
diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs
index 34563a026..3ee0af8ad 100644
--- a/crates/ide/src/extend_selection.rs
+++ b/crates/ide/src/extend_selection.rs
@@ -315,12 +315,12 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
315 315
316#[cfg(test)] 316#[cfg(test)]
317mod tests { 317mod tests {
318 use crate::mock_analysis::analysis_and_position; 318 use crate::fixture;
319 319
320 use super::*; 320 use super::*;
321 321
322 fn do_check(before: &str, afters: &[&str]) { 322 fn do_check(before: &str, afters: &[&str]) {
323 let (analysis, position) = analysis_and_position(&before); 323 let (analysis, position) = fixture::position(&before);
324 let before = analysis.file_text(position.file_id).unwrap(); 324 let before = analysis.file_text(position.file_id).unwrap();
325 let range = TextRange::empty(position.offset); 325 let range = TextRange::empty(position.offset);
326 let mut frange = FileRange { file_id: position.file_id, range }; 326 let mut frange = FileRange { file_id: position.file_id, range };
diff --git a/crates/ide/src/fixture.rs b/crates/ide/src/fixture.rs
new file mode 100644
index 000000000..ed06689f0
--- /dev/null
+++ b/crates/ide/src/fixture.rs
@@ -0,0 +1,70 @@
1//! Utilities for creating `Analysis` instances for tests.
2use base_db::fixture::ChangeFixture;
3use test_utils::{extract_annotations, RangeOrOffset};
4
5use crate::{Analysis, AnalysisHost, FileId, FilePosition, FileRange};
6
7/// Creates analysis for a single file.
8pub(crate) fn file(ra_fixture: &str) -> (Analysis, FileId) {
9 let mut host = AnalysisHost::default();
10 let change_fixture = ChangeFixture::parse(ra_fixture);
11 host.db.apply_change(change_fixture.change);
12 (host.analysis(), change_fixture.files[0])
13}
14
15/// Creates analysis for many files.
16pub(crate) fn files(ra_fixture: &str) -> (Analysis, Vec<FileId>) {
17 let mut host = AnalysisHost::default();
18 let change_fixture = ChangeFixture::parse(ra_fixture);
19 host.db.apply_change(change_fixture.change);
20 (host.analysis(), change_fixture.files)
21}
22
23/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
24pub(crate) fn position(ra_fixture: &str) -> (Analysis, FilePosition) {
25 let mut host = AnalysisHost::default();
26 let change_fixture = ChangeFixture::parse(ra_fixture);
27 host.db.apply_change(change_fixture.change);
28 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker (<|>)");
29 let offset = match range_or_offset {
30 RangeOrOffset::Range(_) => panic!(),
31 RangeOrOffset::Offset(it) => it,
32 };
33 (host.analysis(), FilePosition { file_id, offset })
34}
35
36/// Creates analysis for a single file, returns range marked with a pair of <|>.
37pub(crate) fn range(ra_fixture: &str) -> (Analysis, FileRange) {
38 let mut host = AnalysisHost::default();
39 let change_fixture = ChangeFixture::parse(ra_fixture);
40 host.db.apply_change(change_fixture.change);
41 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker (<|>)");
42 let range = match range_or_offset {
43 RangeOrOffset::Range(it) => it,
44 RangeOrOffset::Offset(_) => panic!(),
45 };
46 (host.analysis(), FileRange { file_id, range })
47}
48
49/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
50pub(crate) fn annotations(ra_fixture: &str) -> (Analysis, FilePosition, Vec<(FileRange, String)>) {
51 let mut host = AnalysisHost::default();
52 let change_fixture = ChangeFixture::parse(ra_fixture);
53 host.db.apply_change(change_fixture.change);
54 let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker (<|>)");
55 let offset = match range_or_offset {
56 RangeOrOffset::Range(_) => panic!(),
57 RangeOrOffset::Offset(it) => it,
58 };
59
60 let annotations = change_fixture
61 .files
62 .iter()
63 .flat_map(|&file_id| {
64 let file_text = host.analysis().file_text(file_id).unwrap();
65 let annotations = extract_annotations(&file_text);
66 annotations.into_iter().map(move |(range, data)| (FileRange { file_id, range }, data))
67 })
68 .collect();
69 (host.analysis(), FilePosition { file_id, offset }, annotations)
70}
diff --git a/crates/ide/src/fn_references.rs b/crates/ide/src/fn_references.rs
index 1989a562b..459f201ed 100644
--- a/crates/ide/src/fn_references.rs
+++ b/crates/ide/src/fn_references.rs
@@ -25,15 +25,14 @@ fn method_range(item: SyntaxNode, file_id: FileId) -> Option<FileRange> {
25 25
26#[cfg(test)] 26#[cfg(test)]
27mod tests { 27mod tests {
28 use crate::mock_analysis::analysis_and_position; 28 use crate::fixture;
29 use crate::{FileRange, TextSize}; 29 use crate::{FileRange, TextSize};
30 use std::ops::RangeInclusive; 30 use std::ops::RangeInclusive;
31 31
32 #[test] 32 #[test]
33 fn test_find_all_methods() { 33 fn test_find_all_methods() {
34 let (analysis, pos) = analysis_and_position( 34 let (analysis, pos) = fixture::position(
35 r#" 35 r#"
36 //- /lib.rs
37 fn private_fn() {<|>} 36 fn private_fn() {<|>}
38 37
39 pub fn pub_fn() {} 38 pub fn pub_fn() {}
@@ -48,9 +47,8 @@ mod tests {
48 47
49 #[test] 48 #[test]
50 fn test_find_trait_methods() { 49 fn test_find_trait_methods() {
51 let (analysis, pos) = analysis_and_position( 50 let (analysis, pos) = fixture::position(
52 r#" 51 r#"
53 //- /lib.rs
54 trait Foo { 52 trait Foo {
55 fn bar() {<|>} 53 fn bar() {<|>}
56 fn baz() {} 54 fn baz() {}
@@ -64,7 +62,7 @@ mod tests {
64 62
65 #[test] 63 #[test]
66 fn test_skip_tests() { 64 fn test_skip_tests() {
67 let (analysis, pos) = analysis_and_position( 65 let (analysis, pos) = fixture::position(
68 r#" 66 r#"
69 //- /lib.rs 67 //- /lib.rs
70 #[test] 68 #[test]
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 15e9b7fad..582bf4837 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -103,12 +103,11 @@ mod tests {
103 use base_db::FileRange; 103 use base_db::FileRange;
104 use syntax::{TextRange, TextSize}; 104 use syntax::{TextRange, TextSize};
105 105
106 use crate::mock_analysis::MockAnalysis; 106 use crate::fixture;
107 107
108 fn check(ra_fixture: &str) { 108 fn check(ra_fixture: &str) {
109 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture); 109 let (analysis, position, mut annotations) = fixture::annotations(ra_fixture);
110 let (mut expected, data) = mock.annotation(); 110 let (mut expected, data) = annotations.pop().unwrap();
111 let analysis = mock.analysis();
112 match data.as_str() { 111 match data.as_str() {
113 "" => (), 112 "" => (),
114 "file" => { 113 "file" => {
@@ -133,9 +132,9 @@ mod tests {
133 fn goto_def_for_extern_crate() { 132 fn goto_def_for_extern_crate() {
134 check( 133 check(
135 r#" 134 r#"
136 //- /main.rs 135 //- /main.rs crate:main deps:std
137 extern crate std<|>; 136 extern crate std<|>;
138 //- /std/lib.rs 137 //- /std/lib.rs crate:std
139 // empty 138 // empty
140 //^ file 139 //^ file
141 "#, 140 "#,
@@ -146,9 +145,9 @@ mod tests {
146 fn goto_def_for_renamed_extern_crate() { 145 fn goto_def_for_renamed_extern_crate() {
147 check( 146 check(
148 r#" 147 r#"
149 //- /main.rs 148 //- /main.rs crate:main deps:std
150 extern crate std as abc<|>; 149 extern crate std as abc<|>;
151 //- /std/lib.rs 150 //- /std/lib.rs crate:std
152 // empty 151 // empty
153 //^ file 152 //^ file
154 "#, 153 "#,
@@ -342,10 +341,10 @@ fn bar() {
342 fn goto_def_for_use_alias() { 341 fn goto_def_for_use_alias() {
343 check( 342 check(
344 r#" 343 r#"
345//- /lib.rs 344//- /lib.rs crate:main deps:foo
346use foo as bar<|>; 345use foo as bar<|>;
347 346
348//- /foo/lib.rs 347//- /foo/lib.rs crate:foo
349// empty 348// empty
350//^ file 349//^ file
351"#, 350"#,
@@ -356,10 +355,10 @@ use foo as bar<|>;
356 fn goto_def_for_use_alias_foo_macro() { 355 fn goto_def_for_use_alias_foo_macro() {
357 check( 356 check(
358 r#" 357 r#"
359//- /lib.rs 358//- /lib.rs crate:main deps:foo
360use foo::foo as bar<|>; 359use foo::foo as bar<|>;
361 360
362//- /foo/lib.rs 361//- /foo/lib.rs crate:foo
363#[macro_export] 362#[macro_export]
364macro_rules! foo { () => { () } } 363macro_rules! foo { () => { () } }
365 //^^^ 364 //^^^
@@ -371,7 +370,6 @@ macro_rules! foo { () => { () } }
371 fn goto_def_for_methods() { 370 fn goto_def_for_methods() {
372 check( 371 check(
373 r#" 372 r#"
374//- /lib.rs
375struct Foo; 373struct Foo;
376impl Foo { 374impl Foo {
377 fn frobnicate(&self) { } 375 fn frobnicate(&self) { }
diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs
index f503f4ec5..6c586bbd1 100644
--- a/crates/ide/src/goto_implementation.rs
+++ b/crates/ide/src/goto_implementation.rs
@@ -76,12 +76,10 @@ fn impls_for_trait(
76mod tests { 76mod tests {
77 use base_db::FileRange; 77 use base_db::FileRange;
78 78
79 use crate::mock_analysis::MockAnalysis; 79 use crate::fixture;
80 80
81 fn check(ra_fixture: &str) { 81 fn check(ra_fixture: &str) {
82 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture); 82 let (analysis, position, annotations) = fixture::annotations(ra_fixture);
83 let annotations = mock.annotations();
84 let analysis = mock.analysis();
85 83
86 let navs = analysis.goto_implementation(position).unwrap().unwrap().info; 84 let navs = analysis.goto_implementation(position).unwrap().unwrap().info;
87 85
diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs
index 4a151b150..6d0df04dd 100644
--- a/crates/ide/src/goto_type_definition.rs
+++ b/crates/ide/src/goto_type_definition.rs
@@ -56,13 +56,12 @@ fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
56mod tests { 56mod tests {
57 use base_db::FileRange; 57 use base_db::FileRange;
58 58
59 use crate::mock_analysis::MockAnalysis; 59 use crate::fixture;
60 60
61 fn check(ra_fixture: &str) { 61 fn check(ra_fixture: &str) {
62 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture); 62 let (analysis, position, mut annotations) = fixture::annotations(ra_fixture);
63 let (expected, data) = mock.annotation(); 63 let (expected, data) = annotations.pop().unwrap();
64 assert!(data.is_empty()); 64 assert!(data.is_empty());
65 let analysis = mock.analysis();
66 65
67 let mut navs = analysis.goto_type_definition(position).unwrap().unwrap().info; 66 let mut navs = analysis.goto_type_definition(position).unwrap().unwrap().info;
68 assert_eq!(navs.len(), 1); 67 assert_eq!(navs.len(), 1);
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index bb9f12cd3..9cf02f0a3 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -377,17 +377,17 @@ mod tests {
377 use base_db::FileLoader; 377 use base_db::FileLoader;
378 use expect_test::{expect, Expect}; 378 use expect_test::{expect, Expect};
379 379
380 use crate::mock_analysis::analysis_and_position; 380 use crate::fixture;
381 381
382 use super::*; 382 use super::*;
383 383
384 fn check_hover_no_result(ra_fixture: &str) { 384 fn check_hover_no_result(ra_fixture: &str) {
385 let (analysis, position) = analysis_and_position(ra_fixture); 385 let (analysis, position) = fixture::position(ra_fixture);
386 assert!(analysis.hover(position, true).unwrap().is_none()); 386 assert!(analysis.hover(position, true).unwrap().is_none());
387 } 387 }
388 388
389 fn check(ra_fixture: &str, expect: Expect) { 389 fn check(ra_fixture: &str, expect: Expect) {
390 let (analysis, position) = analysis_and_position(ra_fixture); 390 let (analysis, position) = fixture::position(ra_fixture);
391 let hover = analysis.hover(position, true).unwrap().unwrap(); 391 let hover = analysis.hover(position, true).unwrap().unwrap();
392 392
393 let content = analysis.db.file_text(position.file_id); 393 let content = analysis.db.file_text(position.file_id);
@@ -398,7 +398,7 @@ mod tests {
398 } 398 }
399 399
400 fn check_hover_no_links(ra_fixture: &str, expect: Expect) { 400 fn check_hover_no_links(ra_fixture: &str, expect: Expect) {
401 let (analysis, position) = analysis_and_position(ra_fixture); 401 let (analysis, position) = fixture::position(ra_fixture);
402 let hover = analysis.hover(position, false).unwrap().unwrap(); 402 let hover = analysis.hover(position, false).unwrap().unwrap();
403 403
404 let content = analysis.db.file_text(position.file_id); 404 let content = analysis.db.file_text(position.file_id);
@@ -409,7 +409,7 @@ mod tests {
409 } 409 }
410 410
411 fn check_actions(ra_fixture: &str, expect: Expect) { 411 fn check_actions(ra_fixture: &str, expect: Expect) {
412 let (analysis, position) = analysis_and_position(ra_fixture); 412 let (analysis, position) = fixture::position(ra_fixture);
413 let hover = analysis.hover(position, true).unwrap().unwrap(); 413 let hover = analysis.hover(position, true).unwrap().unwrap();
414 expect.assert_debug_eq(&hover.info.actions) 414 expect.assert_debug_eq(&hover.info.actions)
415 } 415 }
@@ -963,7 +963,7 @@ impl Thing {
963 "#]], 963 "#]],
964 ) 964 )
965 } /* FIXME: revive these tests 965 } /* FIXME: revive these tests
966 let (analysis, position) = analysis_and_position( 966 let (analysis, position) = fixture::position(
967 " 967 "
968 struct Thing { x: u32 } 968 struct Thing { x: u32 }
969 impl Thing { 969 impl Thing {
@@ -977,7 +977,7 @@ impl Thing {
977 let hover = analysis.hover(position).unwrap().unwrap(); 977 let hover = analysis.hover(position).unwrap().unwrap();
978 assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing")); 978 assert_eq!(trim_markup(&hover.info.markup.as_str()), ("Thing"));
979 979
980 let (analysis, position) = analysis_and_position( 980 let (analysis, position) = fixture::position(
981 " 981 "
982 enum Thing { A } 982 enum Thing { A }
983 impl Thing { 983 impl Thing {
@@ -990,7 +990,7 @@ impl Thing {
990 let hover = analysis.hover(position).unwrap().unwrap(); 990 let hover = analysis.hover(position).unwrap().unwrap();
991 assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing")); 991 assert_eq!(trim_markup(&hover.info.markup.as_str()), ("enum Thing"));
992 992
993 let (analysis, position) = analysis_and_position( 993 let (analysis, position) = fixture::position(
994 " 994 "
995 enum Thing { A } 995 enum Thing { A }
996 impl Thing { 996 impl Thing {
@@ -1275,7 +1275,7 @@ fn bar() { fo<|>o(); }
1275 Implementaion( 1275 Implementaion(
1276 FilePosition { 1276 FilePosition {
1277 file_id: FileId( 1277 file_id: FileId(
1278 1, 1278 0,
1279 ), 1279 ),
1280 offset: 13, 1280 offset: 13,
1281 }, 1281 },
@@ -1289,9 +1289,9 @@ fn bar() { fo<|>o(); }
1289 fn test_hover_extern_crate() { 1289 fn test_hover_extern_crate() {
1290 check( 1290 check(
1291 r#" 1291 r#"
1292//- /main.rs 1292//- /main.rs crate:main deps:std
1293extern crate st<|>d; 1293extern crate st<|>d;
1294//- /std/lib.rs 1294//- /std/lib.rs crate:std
1295//! Standard library for this test 1295//! Standard library for this test
1296//! 1296//!
1297//! Printed? 1297//! Printed?
@@ -1307,9 +1307,9 @@ extern crate st<|>d;
1307 ); 1307 );
1308 check( 1308 check(
1309 r#" 1309 r#"
1310//- /main.rs 1310//- /main.rs crate:main deps:std
1311extern crate std as ab<|>c; 1311extern crate std as ab<|>c;
1312//- /std/lib.rs 1312//- /std/lib.rs crate:std
1313//! Standard library for this test 1313//! Standard library for this test
1314//! 1314//!
1315//! Printed? 1315//! Printed?
@@ -1989,7 +1989,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
1989 Implementaion( 1989 Implementaion(
1990 FilePosition { 1990 FilePosition {
1991 file_id: FileId( 1991 file_id: FileId(
1992 1, 1992 0,
1993 ), 1993 ),
1994 offset: 6, 1994 offset: 6,
1995 }, 1995 },
@@ -2008,7 +2008,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
2008 Implementaion( 2008 Implementaion(
2009 FilePosition { 2009 FilePosition {
2010 file_id: FileId( 2010 file_id: FileId(
2011 1, 2011 0,
2012 ), 2012 ),
2013 offset: 7, 2013 offset: 7,
2014 }, 2014 },
@@ -2027,7 +2027,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
2027 Implementaion( 2027 Implementaion(
2028 FilePosition { 2028 FilePosition {
2029 file_id: FileId( 2029 file_id: FileId(
2030 1, 2030 0,
2031 ), 2031 ),
2032 offset: 6, 2032 offset: 6,
2033 }, 2033 },
@@ -2046,7 +2046,7 @@ fn foo() { let bar = Bar; bar.fo<|>o(); }
2046 Implementaion( 2046 Implementaion(
2047 FilePosition { 2047 FilePosition {
2048 file_id: FileId( 2048 file_id: FileId(
2049 1, 2049 0,
2050 ), 2050 ),
2051 offset: 5, 2051 offset: 5,
2052 }, 2052 },
@@ -2069,7 +2069,7 @@ fn foo_<|>test() {}
2069 Runnable { 2069 Runnable {
2070 nav: NavigationTarget { 2070 nav: NavigationTarget {
2071 file_id: FileId( 2071 file_id: FileId(
2072 1, 2072 0,
2073 ), 2073 ),
2074 full_range: 0..24, 2074 full_range: 0..24,
2075 focus_range: Some( 2075 focus_range: Some(
@@ -2112,7 +2112,7 @@ mod tests<|> {
2112 Runnable { 2112 Runnable {
2113 nav: NavigationTarget { 2113 nav: NavigationTarget {
2114 file_id: FileId( 2114 file_id: FileId(
2115 1, 2115 0,
2116 ), 2116 ),
2117 full_range: 0..46, 2117 full_range: 0..46,
2118 focus_range: Some( 2118 focus_range: Some(
@@ -2151,7 +2151,7 @@ fn main() { let s<|>t = S{ f1:0 }; }
2151 mod_path: "test::S", 2151 mod_path: "test::S",
2152 nav: NavigationTarget { 2152 nav: NavigationTarget {
2153 file_id: FileId( 2153 file_id: FileId(
2154 1, 2154 0,
2155 ), 2155 ),
2156 full_range: 0..19, 2156 full_range: 0..19,
2157 focus_range: Some( 2157 focus_range: Some(
@@ -2190,7 +2190,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; }
2190 mod_path: "test::S", 2190 mod_path: "test::S",
2191 nav: NavigationTarget { 2191 nav: NavigationTarget {
2192 file_id: FileId( 2192 file_id: FileId(
2193 1, 2193 0,
2194 ), 2194 ),
2195 full_range: 17..37, 2195 full_range: 17..37,
2196 focus_range: Some( 2196 focus_range: Some(
@@ -2209,7 +2209,7 @@ fn main() { let s<|>t = S{ f1:Arg(0) }; }
2209 mod_path: "test::Arg", 2209 mod_path: "test::Arg",
2210 nav: NavigationTarget { 2210 nav: NavigationTarget {
2211 file_id: FileId( 2211 file_id: FileId(
2212 1, 2212 0,
2213 ), 2213 ),
2214 full_range: 0..16, 2214 full_range: 0..16,
2215 focus_range: Some( 2215 focus_range: Some(
@@ -2248,7 +2248,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; }
2248 mod_path: "test::S", 2248 mod_path: "test::S",
2249 nav: NavigationTarget { 2249 nav: NavigationTarget {
2250 file_id: FileId( 2250 file_id: FileId(
2251 1, 2251 0,
2252 ), 2252 ),
2253 full_range: 17..37, 2253 full_range: 17..37,
2254 focus_range: Some( 2254 focus_range: Some(
@@ -2267,7 +2267,7 @@ fn main() { let s<|>t = S{ f1: S{ f1: Arg(0) } }; }
2267 mod_path: "test::Arg", 2267 mod_path: "test::Arg",
2268 nav: NavigationTarget { 2268 nav: NavigationTarget {
2269 file_id: FileId( 2269 file_id: FileId(
2270 1, 2270 0,
2271 ), 2271 ),
2272 full_range: 0..16, 2272 full_range: 0..16,
2273 focus_range: Some( 2273 focus_range: Some(
@@ -2309,7 +2309,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
2309 mod_path: "test::A", 2309 mod_path: "test::A",
2310 nav: NavigationTarget { 2310 nav: NavigationTarget {
2311 file_id: FileId( 2311 file_id: FileId(
2312 1, 2312 0,
2313 ), 2313 ),
2314 full_range: 0..14, 2314 full_range: 0..14,
2315 focus_range: Some( 2315 focus_range: Some(
@@ -2328,7 +2328,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
2328 mod_path: "test::B", 2328 mod_path: "test::B",
2329 nav: NavigationTarget { 2329 nav: NavigationTarget {
2330 file_id: FileId( 2330 file_id: FileId(
2331 1, 2331 0,
2332 ), 2332 ),
2333 full_range: 15..29, 2333 full_range: 15..29,
2334 focus_range: Some( 2334 focus_range: Some(
@@ -2347,7 +2347,7 @@ fn main() { let s<|>t = (A(1), B(2), M::C(3) ); }
2347 mod_path: "test::M::C", 2347 mod_path: "test::M::C",
2348 nav: NavigationTarget { 2348 nav: NavigationTarget {
2349 file_id: FileId( 2349 file_id: FileId(
2350 1, 2350 0,
2351 ), 2351 ),
2352 full_range: 42..60, 2352 full_range: 42..60,
2353 focus_range: Some( 2353 focus_range: Some(
@@ -2386,7 +2386,7 @@ fn main() { let s<|>t = foo(); }
2386 mod_path: "test::Foo", 2386 mod_path: "test::Foo",
2387 nav: NavigationTarget { 2387 nav: NavigationTarget {
2388 file_id: FileId( 2388 file_id: FileId(
2389 1, 2389 0,
2390 ), 2390 ),
2391 full_range: 0..12, 2391 full_range: 0..12,
2392 focus_range: Some( 2392 focus_range: Some(
@@ -2426,7 +2426,7 @@ fn main() { let s<|>t = foo(); }
2426 mod_path: "test::Foo", 2426 mod_path: "test::Foo",
2427 nav: NavigationTarget { 2427 nav: NavigationTarget {
2428 file_id: FileId( 2428 file_id: FileId(
2429 1, 2429 0,
2430 ), 2430 ),
2431 full_range: 0..15, 2431 full_range: 0..15,
2432 focus_range: Some( 2432 focus_range: Some(
@@ -2445,7 +2445,7 @@ fn main() { let s<|>t = foo(); }
2445 mod_path: "test::S", 2445 mod_path: "test::S",
2446 nav: NavigationTarget { 2446 nav: NavigationTarget {
2447 file_id: FileId( 2447 file_id: FileId(
2448 1, 2448 0,
2449 ), 2449 ),
2450 full_range: 16..25, 2450 full_range: 16..25,
2451 focus_range: Some( 2451 focus_range: Some(
@@ -2485,7 +2485,7 @@ fn main() { let s<|>t = foo(); }
2485 mod_path: "test::Foo", 2485 mod_path: "test::Foo",
2486 nav: NavigationTarget { 2486 nav: NavigationTarget {
2487 file_id: FileId( 2487 file_id: FileId(
2488 1, 2488 0,
2489 ), 2489 ),
2490 full_range: 0..12, 2490 full_range: 0..12,
2491 focus_range: Some( 2491 focus_range: Some(
@@ -2504,7 +2504,7 @@ fn main() { let s<|>t = foo(); }
2504 mod_path: "test::Bar", 2504 mod_path: "test::Bar",
2505 nav: NavigationTarget { 2505 nav: NavigationTarget {
2506 file_id: FileId( 2506 file_id: FileId(
2507 1, 2507 0,
2508 ), 2508 ),
2509 full_range: 13..25, 2509 full_range: 13..25,
2510 focus_range: Some( 2510 focus_range: Some(
@@ -2547,7 +2547,7 @@ fn main() { let s<|>t = foo(); }
2547 mod_path: "test::Foo", 2547 mod_path: "test::Foo",
2548 nav: NavigationTarget { 2548 nav: NavigationTarget {
2549 file_id: FileId( 2549 file_id: FileId(
2550 1, 2550 0,
2551 ), 2551 ),
2552 full_range: 0..15, 2552 full_range: 0..15,
2553 focus_range: Some( 2553 focus_range: Some(
@@ -2566,7 +2566,7 @@ fn main() { let s<|>t = foo(); }
2566 mod_path: "test::Bar", 2566 mod_path: "test::Bar",
2567 nav: NavigationTarget { 2567 nav: NavigationTarget {
2568 file_id: FileId( 2568 file_id: FileId(
2569 1, 2569 0,
2570 ), 2570 ),
2571 full_range: 16..31, 2571 full_range: 16..31,
2572 focus_range: Some( 2572 focus_range: Some(
@@ -2585,7 +2585,7 @@ fn main() { let s<|>t = foo(); }
2585 mod_path: "test::S1", 2585 mod_path: "test::S1",
2586 nav: NavigationTarget { 2586 nav: NavigationTarget {
2587 file_id: FileId( 2587 file_id: FileId(
2588 1, 2588 0,
2589 ), 2589 ),
2590 full_range: 32..44, 2590 full_range: 32..44,
2591 focus_range: Some( 2591 focus_range: Some(
@@ -2604,7 +2604,7 @@ fn main() { let s<|>t = foo(); }
2604 mod_path: "test::S2", 2604 mod_path: "test::S2",
2605 nav: NavigationTarget { 2605 nav: NavigationTarget {
2606 file_id: FileId( 2606 file_id: FileId(
2607 1, 2607 0,
2608 ), 2608 ),
2609 full_range: 45..57, 2609 full_range: 45..57,
2610 focus_range: Some( 2610 focus_range: Some(
@@ -2641,7 +2641,7 @@ fn foo(ar<|>g: &impl Foo) {}
2641 mod_path: "test::Foo", 2641 mod_path: "test::Foo",
2642 nav: NavigationTarget { 2642 nav: NavigationTarget {
2643 file_id: FileId( 2643 file_id: FileId(
2644 1, 2644 0,
2645 ), 2645 ),
2646 full_range: 0..12, 2646 full_range: 0..12,
2647 focus_range: Some( 2647 focus_range: Some(
@@ -2681,7 +2681,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
2681 mod_path: "test::Foo", 2681 mod_path: "test::Foo",
2682 nav: NavigationTarget { 2682 nav: NavigationTarget {
2683 file_id: FileId( 2683 file_id: FileId(
2684 1, 2684 0,
2685 ), 2685 ),
2686 full_range: 0..12, 2686 full_range: 0..12,
2687 focus_range: Some( 2687 focus_range: Some(
@@ -2700,7 +2700,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
2700 mod_path: "test::Bar", 2700 mod_path: "test::Bar",
2701 nav: NavigationTarget { 2701 nav: NavigationTarget {
2702 file_id: FileId( 2702 file_id: FileId(
2703 1, 2703 0,
2704 ), 2704 ),
2705 full_range: 13..28, 2705 full_range: 13..28,
2706 focus_range: Some( 2706 focus_range: Some(
@@ -2719,7 +2719,7 @@ fn foo(ar<|>g: &impl Foo + Bar<S>) {}
2719 mod_path: "test::S", 2719 mod_path: "test::S",
2720 nav: NavigationTarget { 2720 nav: NavigationTarget {
2721 file_id: FileId( 2721 file_id: FileId(
2722 1, 2722 0,
2723 ), 2723 ),
2724 full_range: 29..39, 2724 full_range: 29..39,
2725 focus_range: Some( 2725 focus_range: Some(
@@ -2764,7 +2764,7 @@ mod future {
2764 mod_path: "test::future::Future", 2764 mod_path: "test::future::Future",
2765 nav: NavigationTarget { 2765 nav: NavigationTarget {
2766 file_id: FileId( 2766 file_id: FileId(
2767 1, 2767 0,
2768 ), 2768 ),
2769 full_range: 101..163, 2769 full_range: 101..163,
2770 focus_range: Some( 2770 focus_range: Some(
@@ -2783,7 +2783,7 @@ mod future {
2783 mod_path: "test::S", 2783 mod_path: "test::S",
2784 nav: NavigationTarget { 2784 nav: NavigationTarget {
2785 file_id: FileId( 2785 file_id: FileId(
2786 1, 2786 0,
2787 ), 2787 ),
2788 full_range: 0..9, 2788 full_range: 0..9,
2789 focus_range: Some( 2789 focus_range: Some(
@@ -2821,7 +2821,7 @@ fn foo(ar<|>g: &impl Foo<S>) {}
2821 mod_path: "test::Foo", 2821 mod_path: "test::Foo",
2822 nav: NavigationTarget { 2822 nav: NavigationTarget {
2823 file_id: FileId( 2823 file_id: FileId(
2824 1, 2824 0,
2825 ), 2825 ),
2826 full_range: 0..15, 2826 full_range: 0..15,
2827 focus_range: Some( 2827 focus_range: Some(
@@ -2840,7 +2840,7 @@ fn foo(ar<|>g: &impl Foo<S>) {}
2840 mod_path: "test::S", 2840 mod_path: "test::S",
2841 nav: NavigationTarget { 2841 nav: NavigationTarget {
2842 file_id: FileId( 2842 file_id: FileId(
2843 1, 2843 0,
2844 ), 2844 ),
2845 full_range: 16..27, 2845 full_range: 16..27,
2846 focus_range: Some( 2846 focus_range: Some(
@@ -2883,7 +2883,7 @@ fn main() { let s<|>t = foo(); }
2883 mod_path: "test::B", 2883 mod_path: "test::B",
2884 nav: NavigationTarget { 2884 nav: NavigationTarget {
2885 file_id: FileId( 2885 file_id: FileId(
2886 1, 2886 0,
2887 ), 2887 ),
2888 full_range: 42..55, 2888 full_range: 42..55,
2889 focus_range: Some( 2889 focus_range: Some(
@@ -2902,7 +2902,7 @@ fn main() { let s<|>t = foo(); }
2902 mod_path: "test::Foo", 2902 mod_path: "test::Foo",
2903 nav: NavigationTarget { 2903 nav: NavigationTarget {
2904 file_id: FileId( 2904 file_id: FileId(
2905 1, 2905 0,
2906 ), 2906 ),
2907 full_range: 0..12, 2907 full_range: 0..12,
2908 focus_range: Some( 2908 focus_range: Some(
@@ -2939,7 +2939,7 @@ fn foo(ar<|>g: &dyn Foo) {}
2939 mod_path: "test::Foo", 2939 mod_path: "test::Foo",
2940 nav: NavigationTarget { 2940 nav: NavigationTarget {
2941 file_id: FileId( 2941 file_id: FileId(
2942 1, 2942 0,
2943 ), 2943 ),
2944 full_range: 0..12, 2944 full_range: 0..12,
2945 focus_range: Some( 2945 focus_range: Some(
@@ -2977,7 +2977,7 @@ fn foo(ar<|>g: &dyn Foo<S>) {}
2977 mod_path: "test::Foo", 2977 mod_path: "test::Foo",
2978 nav: NavigationTarget { 2978 nav: NavigationTarget {
2979 file_id: FileId( 2979 file_id: FileId(
2980 1, 2980 0,
2981 ), 2981 ),
2982 full_range: 0..15, 2982 full_range: 0..15,
2983 focus_range: Some( 2983 focus_range: Some(
@@ -2996,7 +2996,7 @@ fn foo(ar<|>g: &dyn Foo<S>) {}
2996 mod_path: "test::S", 2996 mod_path: "test::S",
2997 nav: NavigationTarget { 2997 nav: NavigationTarget {
2998 file_id: FileId( 2998 file_id: FileId(
2999 1, 2999 0,
3000 ), 3000 ),
3001 full_range: 16..27, 3001 full_range: 16..27,
3002 focus_range: Some( 3002 focus_range: Some(
@@ -3037,7 +3037,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
3037 mod_path: "test::ImplTrait", 3037 mod_path: "test::ImplTrait",
3038 nav: NavigationTarget { 3038 nav: NavigationTarget {
3039 file_id: FileId( 3039 file_id: FileId(
3040 1, 3040 0,
3041 ), 3041 ),
3042 full_range: 0..21, 3042 full_range: 0..21,
3043 focus_range: Some( 3043 focus_range: Some(
@@ -3056,7 +3056,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
3056 mod_path: "test::B", 3056 mod_path: "test::B",
3057 nav: NavigationTarget { 3057 nav: NavigationTarget {
3058 file_id: FileId( 3058 file_id: FileId(
3059 1, 3059 0,
3060 ), 3060 ),
3061 full_range: 43..57, 3061 full_range: 43..57,
3062 focus_range: Some( 3062 focus_range: Some(
@@ -3075,7 +3075,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
3075 mod_path: "test::DynTrait", 3075 mod_path: "test::DynTrait",
3076 nav: NavigationTarget { 3076 nav: NavigationTarget {
3077 file_id: FileId( 3077 file_id: FileId(
3078 1, 3078 0,
3079 ), 3079 ),
3080 full_range: 22..42, 3080 full_range: 22..42,
3081 focus_range: Some( 3081 focus_range: Some(
@@ -3094,7 +3094,7 @@ fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {}
3094 mod_path: "test::S", 3094 mod_path: "test::S",
3095 nav: NavigationTarget { 3095 nav: NavigationTarget {
3096 file_id: FileId( 3096 file_id: FileId(
3097 1, 3097 0,
3098 ), 3098 ),
3099 full_range: 58..69, 3099 full_range: 58..69,
3100 focus_range: Some( 3100 focus_range: Some(
@@ -3142,7 +3142,7 @@ fn main() { let s<|>t = test().get(); }
3142 mod_path: "test::Foo", 3142 mod_path: "test::Foo",
3143 nav: NavigationTarget { 3143 nav: NavigationTarget {
3144 file_id: FileId( 3144 file_id: FileId(
3145 1, 3145 0,
3146 ), 3146 ),
3147 full_range: 0..62, 3147 full_range: 0..62,
3148 focus_range: Some( 3148 focus_range: Some(
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 583f39d85..1d7e8de56 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -189,7 +189,7 @@ fn get_bind_pat_hints(
189 189
190 let ty = sema.type_of_pat(&pat.clone().into())?; 190 let ty = sema.type_of_pat(&pat.clone().into())?;
191 191
192 if should_not_display_type_hint(sema.db, &pat, &ty) { 192 if should_not_display_type_hint(sema, &pat, &ty) {
193 return None; 193 return None;
194 } 194 }
195 195
@@ -215,10 +215,12 @@ fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &Typ
215} 215}
216 216
217fn should_not_display_type_hint( 217fn should_not_display_type_hint(
218 db: &RootDatabase, 218 sema: &Semantics<RootDatabase>,
219 bind_pat: &ast::IdentPat, 219 bind_pat: &ast::IdentPat,
220 pat_ty: &Type, 220 pat_ty: &Type,
221) -> bool { 221) -> bool {
222 let db = sema.db;
223
222 if pat_ty.is_unknown() { 224 if pat_ty.is_unknown() {
223 return true; 225 return true;
224 } 226 }
@@ -249,6 +251,15 @@ fn should_not_display_type_hint(
249 return it.condition().and_then(|condition| condition.pat()).is_some() 251 return it.condition().and_then(|condition| condition.pat()).is_some()
250 && pat_is_enum_variant(db, bind_pat, pat_ty); 252 && pat_is_enum_variant(db, bind_pat, pat_ty);
251 }, 253 },
254 ast::ForExpr(it) => {
255 // We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
256 // Type of expr should be iterable.
257 return it.in_token().is_none() ||
258 it.iterable()
259 .and_then(|iterable_expr|sema.type_of_expr(&iterable_expr))
260 .map(|iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
261 .unwrap_or(true)
262 },
252 _ => (), 263 _ => (),
253 } 264 }
254 } 265 }
@@ -339,14 +350,14 @@ mod tests {
339 use expect_test::{expect, Expect}; 350 use expect_test::{expect, Expect};
340 use test_utils::extract_annotations; 351 use test_utils::extract_annotations;
341 352
342 use crate::{inlay_hints::InlayHintsConfig, mock_analysis::single_file}; 353 use crate::{fixture, inlay_hints::InlayHintsConfig};
343 354
344 fn check(ra_fixture: &str) { 355 fn check(ra_fixture: &str) {
345 check_with_config(InlayHintsConfig::default(), ra_fixture); 356 check_with_config(InlayHintsConfig::default(), ra_fixture);
346 } 357 }
347 358
348 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) { 359 fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
349 let (analysis, file_id) = single_file(ra_fixture); 360 let (analysis, file_id) = fixture::file(ra_fixture);
350 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap()); 361 let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
351 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); 362 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
352 let actual = 363 let actual =
@@ -355,7 +366,7 @@ mod tests {
355 } 366 }
356 367
357 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) { 368 fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
358 let (analysis, file_id) = single_file(ra_fixture); 369 let (analysis, file_id) = fixture::file(ra_fixture);
359 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap(); 370 let inlay_hints = analysis.inlay_hints(file_id, &config).unwrap();
360 expect.assert_debug_eq(&inlay_hints) 371 expect.assert_debug_eq(&inlay_hints)
361 } 372 }
@@ -496,19 +507,6 @@ fn main() {
496 } 507 }
497 508
498 #[test] 509 #[test]
499 fn for_expression() {
500 check(
501 r#"
502fn main() {
503 let mut start = 0;
504 //^^^^^^^^^ i32
505 for increment in 0..2 { start += increment; }
506 //^^^^^^^^^ i32
507}"#,
508 );
509 }
510
511 #[test]
512 fn if_expr() { 510 fn if_expr() {
513 check( 511 check(
514 r#" 512 r#"
@@ -924,4 +922,108 @@ fn main() {
924 "#]], 922 "#]],
925 ); 923 );
926 } 924 }
925
926 #[test]
927 fn incomplete_for_no_hint() {
928 check(
929 r#"
930fn main() {
931 let data = &[1i32, 2, 3];
932 //^^^^ &[i32; _]
933 for i
934}"#,
935 );
936 check(
937 r#"
938//- /main.rs crate:main deps:core
939pub struct Vec<T> {}
940
941impl<T> Vec<T> {
942 pub fn new() -> Self { Vec {} }
943 pub fn push(&mut self, t: T) {}
944}
945
946impl<T> IntoIterator for Vec<T> {
947 type Item=T;
948}
949
950fn main() {
951 let mut data = Vec::new();
952 //^^^^^^^^ Vec<&str>
953 data.push("foo");
954 for i in
955
956 println!("Unit expr");
957}
958
959//- /core.rs crate:core
960#[prelude_import] use iter::*;
961mod iter {
962 trait IntoIterator {
963 type Item;
964 }
965}
966//- /alloc.rs crate:alloc deps:core
967mod collections {
968 struct Vec<T> {}
969 impl<T> Vec<T> {
970 fn new() -> Self { Vec {} }
971 fn push(&mut self, t: T) { }
972 }
973 impl<T> IntoIterator for Vec<T> {
974 type Item=T;
975 }
976}
977"#,
978 );
979 }
980
981 #[test]
982 fn complete_for_hint() {
983 check(
984 r#"
985//- /main.rs crate:main deps:core
986pub struct Vec<T> {}
987
988impl<T> Vec<T> {
989 pub fn new() -> Self { Vec {} }
990 pub fn push(&mut self, t: T) {}
991}
992
993impl<T> IntoIterator for Vec<T> {
994 type Item=T;
995}
996
997fn main() {
998 let mut data = Vec::new();
999 //^^^^^^^^ Vec<&str>
1000 data.push("foo");
1001 for i in data {
1002 //^ &str
1003 let z = i;
1004 //^ &str
1005 }
1006}
1007
1008//- /core.rs crate:core
1009#[prelude_import] use iter::*;
1010mod iter {
1011 trait IntoIterator {
1012 type Item;
1013 }
1014}
1015//- /alloc.rs crate:alloc deps:core
1016mod collections {
1017 struct Vec<T> {}
1018 impl<T> Vec<T> {
1019 fn new() -> Self { Vec {} }
1020 fn push(&mut self, t: T) { }
1021 }
1022 impl<T> IntoIterator for Vec<T> {
1023 type Item=T;
1024 }
1025}
1026"#,
1027 );
1028 }
927} 1029}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 31f2bcba3..1aa673cf8 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -15,7 +15,8 @@ macro_rules! eprintln {
15 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) }; 15 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
16} 16}
17 17
18pub mod mock_analysis; 18#[cfg(test)]
19mod fixture;
19 20
20mod markup; 21mod markup;
21mod prime_caches; 22mod prime_caches;
@@ -86,12 +87,11 @@ pub use assists::{
86 utils::MergeBehaviour, Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist, 87 utils::MergeBehaviour, Assist, AssistConfig, AssistId, AssistKind, ResolvedAssist,
87}; 88};
88pub use base_db::{ 89pub use base_db::{
89 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot, 90 Canceled, Change, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRoot,
90 SourceRootId, 91 SourceRootId,
91}; 92};
92pub use hir::{Documentation, Semantics}; 93pub use hir::{Documentation, Semantics};
93pub use ide_db::{ 94pub use ide_db::{
94 change::AnalysisChange,
95 label::Label, 95 label::Label,
96 line_index::{LineCol, LineIndex}, 96 line_index::{LineCol, LineIndex},
97 search::SearchScope, 97 search::SearchScope,
@@ -140,14 +140,10 @@ impl AnalysisHost {
140 140
141 /// Applies changes to the current state of the world. If there are 141 /// Applies changes to the current state of the world. If there are
142 /// outstanding snapshots, they will be canceled. 142 /// outstanding snapshots, they will be canceled.
143 pub fn apply_change(&mut self, change: AnalysisChange) { 143 pub fn apply_change(&mut self, change: Change) {
144 self.db.apply_change(change) 144 self.db.apply_change(change)
145 } 145 }
146 146
147 pub fn maybe_collect_garbage(&mut self) {
148 self.db.maybe_collect_garbage();
149 }
150
151 pub fn collect_garbage(&mut self) { 147 pub fn collect_garbage(&mut self) {
152 self.db.collect_garbage(); 148 self.db.collect_garbage();
153 } 149 }
@@ -198,7 +194,7 @@ impl Analysis {
198 file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string())); 194 file_set.insert(file_id, VfsPath::new_virtual_path("/main.rs".to_string()));
199 let source_root = SourceRoot::new_local(file_set); 195 let source_root = SourceRoot::new_local(file_set);
200 196
201 let mut change = AnalysisChange::new(); 197 let mut change = Change::new();
202 change.set_roots(vec![source_root]); 198 change.set_roots(vec![source_root]);
203 let mut crate_graph = CrateGraph::default(); 199 let mut crate_graph = CrateGraph::default();
204 // FIXME: cfg options 200 // FIXME: cfg options
@@ -220,8 +216,8 @@ impl Analysis {
220 } 216 }
221 217
222 /// Debug info about the current state of the analysis. 218 /// Debug info about the current state of the analysis.
223 pub fn status(&self) -> Cancelable<String> { 219 pub fn status(&self, file_id: Option<FileId>) -> Cancelable<String> {
224 self.with_db(|db| status::status(&*db)) 220 self.with_db(|db| status::status(&*db, file_id))
225 } 221 }
226 222
227 pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> { 223 pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> {
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs
index 107787bb9..a16f90e17 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/link_rewrite.rs
@@ -120,7 +120,7 @@ fn rewrite_intra_doc_link(
120 120
121/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). 121/// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`).
122fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<String> { 122fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<String> {
123 if !(target.contains("#") || target.contains(".html")) { 123 if !(target.contains('#') || target.contains(".html")) {
124 return None; 124 return None;
125 } 125 }
126 126
@@ -190,7 +190,7 @@ fn strip_prefixes_suffixes(mut s: &str) -> &str {
190 prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); 190 prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix));
191 suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); 191 suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix));
192 }); 192 });
193 s.trim_start_matches("@").trim() 193 s.trim_start_matches('@').trim()
194} 194}
195 195
196static TYPES: ([&str; 7], [&str; 0]) = 196static TYPES: ([&str; 7], [&str; 0]) =
diff --git a/crates/ide/src/mock_analysis.rs b/crates/ide/src/mock_analysis.rs
deleted file mode 100644
index 235796dbc..000000000
--- a/crates/ide/src/mock_analysis.rs
+++ /dev/null
@@ -1,176 +0,0 @@
1//! FIXME: write short doc here
2use std::sync::Arc;
3
4use base_db::{CrateName, FileSet, SourceRoot, VfsPath};
5use cfg::CfgOptions;
6use test_utils::{
7 extract_annotations, extract_range_or_offset, Fixture, RangeOrOffset, CURSOR_MARKER,
8};
9
10use crate::{
11 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
12};
13
14/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
15/// from a set of in-memory files.
16#[derive(Debug, Default)]
17pub struct MockAnalysis {
18 files: Vec<Fixture>,
19}
20
21impl MockAnalysis {
22 /// Creates `MockAnalysis` using a fixture data in the following format:
23 ///
24 /// ```not_rust
25 /// //- /main.rs
26 /// mod foo;
27 /// fn main() {}
28 ///
29 /// //- /foo.rs
30 /// struct Baz;
31 /// ```
32 pub fn with_files(ra_fixture: &str) -> MockAnalysis {
33 let (res, pos) = MockAnalysis::with_fixture(ra_fixture);
34 assert!(pos.is_none());
35 res
36 }
37
38 /// Same as `with_files`, but requires that a single file contains a `<|>` marker,
39 /// whose position is also returned.
40 pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
41 let (res, position) = MockAnalysis::with_fixture(fixture);
42 let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
43 let offset = match range_or_offset {
44 RangeOrOffset::Range(_) => panic!(),
45 RangeOrOffset::Offset(it) => it,
46 };
47 (res, FilePosition { file_id, offset })
48 }
49
50 fn with_fixture(fixture: &str) -> (MockAnalysis, Option<(FileId, RangeOrOffset)>) {
51 let mut position = None;
52 let mut res = MockAnalysis::default();
53 for mut entry in Fixture::parse(fixture) {
54 if entry.text.contains(CURSOR_MARKER) {
55 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
56 let (range_or_offset, text) = extract_range_or_offset(&entry.text);
57 entry.text = text;
58 let file_id = res.add_file_fixture(entry);
59 position = Some((file_id, range_or_offset));
60 } else {
61 res.add_file_fixture(entry);
62 }
63 }
64 (res, position)
65 }
66
67 fn add_file_fixture(&mut self, fixture: Fixture) -> FileId {
68 let file_id = FileId((self.files.len() + 1) as u32);
69 self.files.push(fixture);
70 file_id
71 }
72
73 pub fn id_of(&self, path: &str) -> FileId {
74 let (file_id, _) =
75 self.files().find(|(_, data)| path == data.path).expect("no file in this mock");
76 file_id
77 }
78 pub fn annotations(&self) -> Vec<(FileRange, String)> {
79 self.files()
80 .flat_map(|(file_id, fixture)| {
81 let annotations = extract_annotations(&fixture.text);
82 annotations
83 .into_iter()
84 .map(move |(range, data)| (FileRange { file_id, range }, data))
85 })
86 .collect()
87 }
88 pub fn files(&self) -> impl Iterator<Item = (FileId, &Fixture)> + '_ {
89 self.files.iter().enumerate().map(|(idx, fixture)| (FileId(idx as u32 + 1), fixture))
90 }
91 pub fn annotation(&self) -> (FileRange, String) {
92 let mut all = self.annotations();
93 assert_eq!(all.len(), 1);
94 all.pop().unwrap()
95 }
96 pub fn analysis_host(self) -> AnalysisHost {
97 let mut host = AnalysisHost::default();
98 let mut change = AnalysisChange::new();
99 let mut file_set = FileSet::default();
100 let mut crate_graph = CrateGraph::default();
101 let mut root_crate = None;
102 for (i, data) in self.files.into_iter().enumerate() {
103 let path = data.path;
104 assert!(path.starts_with('/'));
105
106 let mut cfg = CfgOptions::default();
107 data.cfg_atoms.iter().for_each(|it| cfg.insert_atom(it.into()));
108 data.cfg_key_values.iter().for_each(|(k, v)| cfg.insert_key_value(k.into(), v.into()));
109 let edition: Edition =
110 data.edition.and_then(|it| it.parse().ok()).unwrap_or(Edition::Edition2018);
111
112 let file_id = FileId(i as u32 + 1);
113 let env = data.env.into_iter().collect();
114 if path == "/lib.rs" || path == "/main.rs" {
115 root_crate = Some(crate_graph.add_crate_root(
116 file_id,
117 edition,
118 Some("test".to_string()),
119 cfg,
120 env,
121 Default::default(),
122 ));
123 } else if path.ends_with("/lib.rs") {
124 let base = &path[..path.len() - "/lib.rs".len()];
125 let crate_name = &base[base.rfind('/').unwrap() + '/'.len_utf8()..];
126 let other_crate = crate_graph.add_crate_root(
127 file_id,
128 edition,
129 Some(crate_name.to_string()),
130 cfg,
131 env,
132 Default::default(),
133 );
134 if let Some(root_crate) = root_crate {
135 crate_graph
136 .add_dep(root_crate, CrateName::new(crate_name).unwrap(), other_crate)
137 .unwrap();
138 }
139 }
140 let path = VfsPath::new_virtual_path(path.to_string());
141 file_set.insert(file_id, path);
142 change.change_file(file_id, Some(Arc::new(data.text).to_owned()));
143 }
144 change.set_crate_graph(crate_graph);
145 change.set_roots(vec![SourceRoot::new_local(file_set)]);
146 host.apply_change(change);
147 host
148 }
149 pub fn analysis(self) -> Analysis {
150 self.analysis_host().analysis()
151 }
152}
153
154/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
155pub fn analysis_and_position(ra_fixture: &str) -> (Analysis, FilePosition) {
156 let (mock, position) = MockAnalysis::with_files_and_position(ra_fixture);
157 (mock.analysis(), position)
158}
159
160/// Creates analysis for a single file.
161pub fn single_file(ra_fixture: &str) -> (Analysis, FileId) {
162 let mock = MockAnalysis::with_files(ra_fixture);
163 let file_id = mock.id_of("/main.rs");
164 (mock.analysis(), file_id)
165}
166
167/// Creates analysis for a single file, returns range marked with a pair of <|>.
168pub fn analysis_and_range(ra_fixture: &str) -> (Analysis, FileRange) {
169 let (res, position) = MockAnalysis::with_fixture(ra_fixture);
170 let (file_id, range_or_offset) = position.expect("expected a marker (<|>)");
171 let range = match range_or_offset {
172 RangeOrOffset::Range(it) => it,
173 RangeOrOffset::Offset(_) => panic!(),
174 };
175 (res.analysis(), FileRange { file_id, range })
176}
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs
index 59ed2967c..ef94acfec 100644
--- a/crates/ide/src/parent_module.rs
+++ b/crates/ide/src/parent_module.rs
@@ -63,19 +63,13 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
63 63
64#[cfg(test)] 64#[cfg(test)]
65mod tests { 65mod tests {
66 use base_db::Env;
67 use cfg::CfgOptions;
68 use test_utils::mark; 66 use test_utils::mark;
69 67
70 use crate::{ 68 use crate::fixture::{self};
71 mock_analysis::{analysis_and_position, MockAnalysis},
72 AnalysisChange, CrateGraph,
73 Edition::Edition2018,
74 };
75 69
76 #[test] 70 #[test]
77 fn test_resolve_parent_module() { 71 fn test_resolve_parent_module() {
78 let (analysis, pos) = analysis_and_position( 72 let (analysis, pos) = fixture::position(
79 " 73 "
80 //- /lib.rs 74 //- /lib.rs
81 mod foo; 75 mod foo;
@@ -84,13 +78,13 @@ mod tests {
84 ", 78 ",
85 ); 79 );
86 let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); 80 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
87 nav.assert_match("foo MODULE FileId(1) 0..8"); 81 nav.assert_match("foo MODULE FileId(0) 0..8");
88 } 82 }
89 83
90 #[test] 84 #[test]
91 fn test_resolve_parent_module_on_module_decl() { 85 fn test_resolve_parent_module_on_module_decl() {
92 mark::check!(test_resolve_parent_module_on_module_decl); 86 mark::check!(test_resolve_parent_module_on_module_decl);
93 let (analysis, pos) = analysis_and_position( 87 let (analysis, pos) = fixture::position(
94 " 88 "
95 //- /lib.rs 89 //- /lib.rs
96 mod foo; 90 mod foo;
@@ -103,12 +97,12 @@ mod tests {
103 ", 97 ",
104 ); 98 );
105 let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); 99 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
106 nav.assert_match("foo MODULE FileId(1) 0..8"); 100 nav.assert_match("foo MODULE FileId(0) 0..8");
107 } 101 }
108 102
109 #[test] 103 #[test]
110 fn test_resolve_parent_module_for_inline() { 104 fn test_resolve_parent_module_for_inline() {
111 let (analysis, pos) = analysis_and_position( 105 let (analysis, pos) = fixture::position(
112 " 106 "
113 //- /lib.rs 107 //- /lib.rs
114 mod foo { 108 mod foo {
@@ -119,37 +113,19 @@ mod tests {
119 ", 113 ",
120 ); 114 );
121 let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); 115 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
122 nav.assert_match("baz MODULE FileId(1) 32..44"); 116 nav.assert_match("baz MODULE FileId(0) 32..44");
123 } 117 }
124 118
125 #[test] 119 #[test]
126 fn test_resolve_crate_root() { 120 fn test_resolve_crate_root() {
127 let mock = MockAnalysis::with_files( 121 let (analysis, file_id) = fixture::file(
128 r#" 122 r#"
129//- /bar.rs 123//- /main.rs
130mod foo; 124mod foo;
131//- /foo.rs 125//- /foo.rs
132// empty 126<|>
133"#, 127"#,
134 ); 128 );
135 let root_file = mock.id_of("/bar.rs"); 129 assert_eq!(analysis.crate_for(file_id).unwrap().len(), 1);
136 let mod_file = mock.id_of("/foo.rs");
137 let mut host = mock.analysis_host();
138 assert!(host.analysis().crate_for(mod_file).unwrap().is_empty());
139
140 let mut crate_graph = CrateGraph::default();
141 let crate_id = crate_graph.add_crate_root(
142 root_file,
143 Edition2018,
144 None,
145 CfgOptions::default(),
146 Env::default(),
147 Default::default(),
148 );
149 let mut change = AnalysisChange::new();
150 change.set_crate_graph(crate_graph);
151 host.apply_change(change);
152
153 assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]);
154 } 130 }
155} 131}
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 722c8f406..e0830eb4f 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -190,14 +190,15 @@ fn get_struct_def_name_for_struct_literal_search(
190 190
191#[cfg(test)] 191#[cfg(test)]
192mod tests { 192mod tests {
193 use crate::{ 193 use base_db::FileId;
194 mock_analysis::{analysis_and_position, MockAnalysis}, 194 use expect_test::{expect, Expect};
195 Declaration, Reference, ReferenceSearchResult, SearchScope, 195 use stdx::format_to;
196 }; 196
197 use crate::{fixture, SearchScope};
197 198
198 #[test] 199 #[test]
199 fn test_struct_literal_after_space() { 200 fn test_struct_literal_after_space() {
200 let refs = get_all_refs( 201 check(
201 r#" 202 r#"
202struct Foo <|>{ 203struct Foo <|>{
203 a: i32, 204 a: i32,
@@ -210,17 +211,17 @@ fn main() {
210 f = Foo {a: Foo::f()}; 211 f = Foo {a: Foo::f()};
211} 212}
212"#, 213"#,
213 ); 214 expect![[r#"
214 check_result( 215 Foo STRUCT FileId(0) 0..26 7..10 Other
215 refs, 216
216 "Foo STRUCT FileId(1) 0..26 7..10 Other", 217 FileId(0) 101..104 StructLiteral
217 &["FileId(1) 101..104 StructLiteral"], 218 "#]],
218 ); 219 );
219 } 220 }
220 221
221 #[test] 222 #[test]
222 fn test_struct_literal_before_space() { 223 fn test_struct_literal_before_space() {
223 let refs = get_all_refs( 224 check(
224 r#" 225 r#"
225struct Foo<|> {} 226struct Foo<|> {}
226 fn main() { 227 fn main() {
@@ -228,17 +229,18 @@ struct Foo<|> {}
228 f = Foo {}; 229 f = Foo {};
229} 230}
230"#, 231"#,
231 ); 232 expect![[r#"
232 check_result( 233 Foo STRUCT FileId(0) 0..13 7..10 Other
233 refs, 234
234 "Foo STRUCT FileId(1) 0..13 7..10 Other", 235 FileId(0) 41..44 Other
235 &["FileId(1) 41..44 Other", "FileId(1) 54..57 StructLiteral"], 236 FileId(0) 54..57 StructLiteral
237 "#]],
236 ); 238 );
237 } 239 }
238 240
239 #[test] 241 #[test]
240 fn test_struct_literal_with_generic_type() { 242 fn test_struct_literal_with_generic_type() {
241 let refs = get_all_refs( 243 check(
242 r#" 244 r#"
243struct Foo<T> <|>{} 245struct Foo<T> <|>{}
244 fn main() { 246 fn main() {
@@ -246,17 +248,17 @@ struct Foo<T> <|>{}
246 f = Foo {}; 248 f = Foo {};
247} 249}
248"#, 250"#,
249 ); 251 expect![[r#"
250 check_result( 252 Foo STRUCT FileId(0) 0..16 7..10 Other
251 refs, 253
252 "Foo STRUCT FileId(1) 0..16 7..10 Other", 254 FileId(0) 64..67 StructLiteral
253 &["FileId(1) 64..67 StructLiteral"], 255 "#]],
254 ); 256 );
255 } 257 }
256 258
257 #[test] 259 #[test]
258 fn test_struct_literal_for_tuple() { 260 fn test_struct_literal_for_tuple() {
259 let refs = get_all_refs( 261 check(
260 r#" 262 r#"
261struct Foo<|>(i32); 263struct Foo<|>(i32);
262 264
@@ -265,17 +267,17 @@ fn main() {
265 f = Foo(1); 267 f = Foo(1);
266} 268}
267"#, 269"#,
268 ); 270 expect![[r#"
269 check_result( 271 Foo STRUCT FileId(0) 0..16 7..10 Other
270 refs, 272
271 "Foo STRUCT FileId(1) 0..16 7..10 Other", 273 FileId(0) 54..57 StructLiteral
272 &["FileId(1) 54..57 StructLiteral"], 274 "#]],
273 ); 275 );
274 } 276 }
275 277
276 #[test] 278 #[test]
277 fn test_find_all_refs_for_local() { 279 fn test_find_all_refs_for_local() {
278 let refs = get_all_refs( 280 check(
279 r#" 281 r#"
280fn main() { 282fn main() {
281 let mut i = 1; 283 let mut i = 1;
@@ -288,22 +290,20 @@ fn main() {
288 290
289 i = 5; 291 i = 5;
290}"#, 292}"#,
291 ); 293 expect![[r#"
292 check_result( 294 i IDENT_PAT FileId(0) 24..25 Other Write
293 refs, 295
294 "i IDENT_PAT FileId(1) 24..25 Other Write", 296 FileId(0) 50..51 Other Write
295 &[ 297 FileId(0) 54..55 Other Read
296 "FileId(1) 50..51 Other Write", 298 FileId(0) 76..77 Other Write
297 "FileId(1) 54..55 Other Read", 299 FileId(0) 94..95 Other Write
298 "FileId(1) 76..77 Other Write", 300 "#]],
299 "FileId(1) 94..95 Other Write",
300 ],
301 ); 301 );
302 } 302 }
303 303
304 #[test] 304 #[test]
305 fn search_filters_by_range() { 305 fn search_filters_by_range() {
306 let refs = get_all_refs( 306 check(
307 r#" 307 r#"
308fn foo() { 308fn foo() {
309 let spam<|> = 92; 309 let spam<|> = 92;
@@ -314,41 +314,46 @@ fn bar() {
314 spam + spam 314 spam + spam
315} 315}
316"#, 316"#,
317 ); 317 expect![[r#"
318 check_result( 318 spam IDENT_PAT FileId(0) 19..23 Other
319 refs, 319
320 "spam IDENT_PAT FileId(1) 19..23 Other", 320 FileId(0) 34..38 Other Read
321 &["FileId(1) 34..38 Other Read", "FileId(1) 41..45 Other Read"], 321 FileId(0) 41..45 Other Read
322 "#]],
322 ); 323 );
323 } 324 }
324 325
325 #[test] 326 #[test]
326 fn test_find_all_refs_for_param_inside() { 327 fn test_find_all_refs_for_param_inside() {
327 let refs = get_all_refs( 328 check(
328 r#" 329 r#"
329fn foo(i : u32) -> u32 { 330fn foo(i : u32) -> u32 { i<|> }
330 i<|>
331}
332"#, 331"#,
332 expect![[r#"
333 i IDENT_PAT FileId(0) 7..8 Other
334
335 FileId(0) 25..26 Other Read
336 "#]],
333 ); 337 );
334 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
335 } 338 }
336 339
337 #[test] 340 #[test]
338 fn test_find_all_refs_for_fn_param() { 341 fn test_find_all_refs_for_fn_param() {
339 let refs = get_all_refs( 342 check(
340 r#" 343 r#"
341fn foo(i<|> : u32) -> u32 { 344fn foo(i<|> : u32) -> u32 { i }
342 i
343}
344"#, 345"#,
346 expect![[r#"
347 i IDENT_PAT FileId(0) 7..8 Other
348
349 FileId(0) 25..26 Other Read
350 "#]],
345 ); 351 );
346 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
347 } 352 }
348 353
349 #[test] 354 #[test]
350 fn test_find_all_refs_field_name() { 355 fn test_find_all_refs_field_name() {
351 let refs = get_all_refs( 356 check(
352 r#" 357 r#"
353//- /lib.rs 358//- /lib.rs
354struct Foo { 359struct Foo {
@@ -359,30 +364,33 @@ fn main(s: Foo) {
359 let f = s.spam; 364 let f = s.spam;
360} 365}
361"#, 366"#,
362 ); 367 expect![[r#"
363 check_result( 368 spam RECORD_FIELD FileId(0) 17..30 21..25 Other
364 refs, 369
365 "spam RECORD_FIELD FileId(1) 17..30 21..25 Other", 370 FileId(0) 67..71 Other Read
366 &["FileId(1) 67..71 Other Read"], 371 "#]],
367 ); 372 );
368 } 373 }
369 374
370 #[test] 375 #[test]
371 fn test_find_all_refs_impl_item_name() { 376 fn test_find_all_refs_impl_item_name() {
372 let refs = get_all_refs( 377 check(
373 r#" 378 r#"
374struct Foo; 379struct Foo;
375impl Foo { 380impl Foo {
376 fn f<|>(&self) { } 381 fn f<|>(&self) { }
377} 382}
378"#, 383"#,
384 expect![[r#"
385 f FN FileId(0) 27..43 30..31 Other
386
387 "#]],
379 ); 388 );
380 check_result(refs, "f FN FileId(1) 27..43 30..31 Other", &[]);
381 } 389 }
382 390
383 #[test] 391 #[test]
384 fn test_find_all_refs_enum_var_name() { 392 fn test_find_all_refs_enum_var_name() {
385 let refs = get_all_refs( 393 check(
386 r#" 394 r#"
387enum Foo { 395enum Foo {
388 A, 396 A,
@@ -390,13 +398,16 @@ enum Foo {
390 C, 398 C,
391} 399}
392"#, 400"#,
401 expect![[r#"
402 B VARIANT FileId(0) 22..23 22..23 Other
403
404 "#]],
393 ); 405 );
394 check_result(refs, "B VARIANT FileId(1) 22..23 22..23 Other", &[]);
395 } 406 }
396 407
397 #[test] 408 #[test]
398 fn test_find_all_refs_two_modules() { 409 fn test_find_all_refs_two_modules() {
399 let (analysis, pos) = analysis_and_position( 410 check(
400 r#" 411 r#"
401//- /lib.rs 412//- /lib.rs
402pub mod foo; 413pub mod foo;
@@ -428,12 +439,12 @@ fn f() {
428 let i = foo::Foo<|> { n: 5 }; 439 let i = foo::Foo<|> { n: 5 };
429} 440}
430"#, 441"#,
431 ); 442 expect![[r#"
432 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 443 Foo STRUCT FileId(1) 17..51 28..31 Other
433 check_result( 444
434 refs, 445 FileId(0) 53..56 StructLiteral
435 "Foo STRUCT FileId(2) 17..51 28..31 Other", 446 FileId(2) 79..82 StructLiteral
436 &["FileId(1) 53..56 StructLiteral", "FileId(3) 79..82 StructLiteral"], 447 "#]],
437 ); 448 );
438 } 449 }
439 450
@@ -442,7 +453,7 @@ fn f() {
442 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`. 453 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
443 #[test] 454 #[test]
444 fn test_find_all_refs_decl_module() { 455 fn test_find_all_refs_decl_module() {
445 let (analysis, pos) = analysis_and_position( 456 check(
446 r#" 457 r#"
447//- /lib.rs 458//- /lib.rs
448mod foo<|>; 459mod foo<|>;
@@ -458,14 +469,17 @@ pub struct Foo {
458 pub n: u32, 469 pub n: u32,
459} 470}
460"#, 471"#,
472 expect![[r#"
473 foo SOURCE_FILE FileId(1) 0..35 Other
474
475 FileId(0) 14..17 Other
476 "#]],
461 ); 477 );
462 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
463 check_result(refs, "foo SOURCE_FILE FileId(2) 0..35 Other", &["FileId(1) 14..17 Other"]);
464 } 478 }
465 479
466 #[test] 480 #[test]
467 fn test_find_all_refs_super_mod_vis() { 481 fn test_find_all_refs_super_mod_vis() {
468 let (analysis, pos) = analysis_and_position( 482 check(
469 r#" 483 r#"
470//- /lib.rs 484//- /lib.rs
471mod foo; 485mod foo;
@@ -483,12 +497,12 @@ pub(super) struct Foo<|> {
483 pub n: u32, 497 pub n: u32,
484} 498}
485"#, 499"#,
486 ); 500 expect![[r#"
487 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 501 Foo STRUCT FileId(2) 0..41 18..21 Other
488 check_result( 502
489 refs, 503 FileId(1) 20..23 Other
490 "Foo STRUCT FileId(3) 0..41 18..21 Other", 504 FileId(1) 47..50 StructLiteral
491 &["FileId(2) 20..23 Other", "FileId(2) 47..50 StructLiteral"], 505 "#]],
492 ); 506 );
493 } 507 }
494 508
@@ -508,29 +522,31 @@ pub(super) struct Foo<|> {
508 fn f() { super::quux(); } 522 fn f() { super::quux(); }
509 "#; 523 "#;
510 524
511 let (mock, pos) = MockAnalysis::with_files_and_position(code); 525 check_with_scope(
512 let bar = mock.id_of("/bar.rs"); 526 code,
513 let analysis = mock.analysis(); 527 None,
528 expect![[r#"
529 quux FN FileId(0) 19..35 26..30 Other
514 530
515 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 531 FileId(1) 16..20 StructLiteral
516 check_result( 532 FileId(2) 16..20 StructLiteral
517 refs, 533 "#]],
518 "quux FN FileId(1) 19..35 26..30 Other",
519 &["FileId(2) 16..20 StructLiteral", "FileId(3) 16..20 StructLiteral"],
520 ); 534 );
521 535
522 let refs = 536 check_with_scope(
523 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); 537 code,
524 check_result( 538 Some(SearchScope::single_file(FileId(2))),
525 refs, 539 expect![[r#"
526 "quux FN FileId(1) 19..35 26..30 Other", 540 quux FN FileId(0) 19..35 26..30 Other
527 &["FileId(3) 16..20 StructLiteral"], 541
542 FileId(2) 16..20 StructLiteral
543 "#]],
528 ); 544 );
529 } 545 }
530 546
531 #[test] 547 #[test]
532 fn test_find_all_refs_macro_def() { 548 fn test_find_all_refs_macro_def() {
533 let refs = get_all_refs( 549 check(
534 r#" 550 r#"
535#[macro_export] 551#[macro_export]
536macro_rules! m1<|> { () => (()) } 552macro_rules! m1<|> { () => (()) }
@@ -540,34 +556,36 @@ fn foo() {
540 m1(); 556 m1();
541} 557}
542"#, 558"#,
543 ); 559 expect![[r#"
544 check_result( 560 m1 MACRO_CALL FileId(0) 0..46 29..31 Other
545 refs, 561
546 "m1 MACRO_CALL FileId(1) 0..46 29..31 Other", 562 FileId(0) 63..65 StructLiteral
547 &["FileId(1) 63..65 StructLiteral", "FileId(1) 73..75 StructLiteral"], 563 FileId(0) 73..75 StructLiteral
564 "#]],
548 ); 565 );
549 } 566 }
550 567
551 #[test] 568 #[test]
552 fn test_basic_highlight_read_write() { 569 fn test_basic_highlight_read_write() {
553 let refs = get_all_refs( 570 check(
554 r#" 571 r#"
555fn foo() { 572fn foo() {
556 let mut i<|> = 0; 573 let mut i<|> = 0;
557 i = i + 1; 574 i = i + 1;
558} 575}
559"#, 576"#,
560 ); 577 expect![[r#"
561 check_result( 578 i IDENT_PAT FileId(0) 23..24 Other Write
562 refs, 579
563 "i IDENT_PAT FileId(1) 23..24 Other Write", 580 FileId(0) 34..35 Other Write
564 &["FileId(1) 34..35 Other Write", "FileId(1) 38..39 Other Read"], 581 FileId(0) 38..39 Other Read
582 "#]],
565 ); 583 );
566 } 584 }
567 585
568 #[test] 586 #[test]
569 fn test_basic_highlight_field_read_write() { 587 fn test_basic_highlight_field_read_write() {
570 let refs = get_all_refs( 588 check(
571 r#" 589 r#"
572struct S { 590struct S {
573 f: u32, 591 f: u32,
@@ -578,38 +596,41 @@ fn foo() {
578 s.f<|> = 0; 596 s.f<|> = 0;
579} 597}
580"#, 598"#,
581 ); 599 expect![[r#"
582 check_result( 600 f RECORD_FIELD FileId(0) 15..21 15..16 Other
583 refs, 601
584 "f RECORD_FIELD FileId(1) 15..21 15..16 Other", 602 FileId(0) 55..56 Other Read
585 &["FileId(1) 55..56 Other Read", "FileId(1) 68..69 Other Write"], 603 FileId(0) 68..69 Other Write
604 "#]],
586 ); 605 );
587 } 606 }
588 607
589 #[test] 608 #[test]
590 fn test_basic_highlight_decl_no_write() { 609 fn test_basic_highlight_decl_no_write() {
591 let refs = get_all_refs( 610 check(
592 r#" 611 r#"
593fn foo() { 612fn foo() {
594 let i<|>; 613 let i<|>;
595 i = 1; 614 i = 1;
596} 615}
597"#, 616"#,
617 expect![[r#"
618 i IDENT_PAT FileId(0) 19..20 Other
619
620 FileId(0) 26..27 Other Write
621 "#]],
598 ); 622 );
599 check_result(refs, "i IDENT_PAT FileId(1) 19..20 Other", &["FileId(1) 26..27 Other Write"]);
600 } 623 }
601 624
602 #[test] 625 #[test]
603 fn test_find_struct_function_refs_outside_module() { 626 fn test_find_struct_function_refs_outside_module() {
604 let refs = get_all_refs( 627 check(
605 r#" 628 r#"
606mod foo { 629mod foo {
607 pub struct Foo; 630 pub struct Foo;
608 631
609 impl Foo { 632 impl Foo {
610 pub fn new<|>() -> Foo { 633 pub fn new<|>() -> Foo { Foo }
611 Foo
612 }
613 } 634 }
614} 635}
615 636
@@ -617,80 +638,62 @@ fn main() {
617 let _f = foo::Foo::new(); 638 let _f = foo::Foo::new();
618} 639}
619"#, 640"#,
620 ); 641 expect![[r#"
621 check_result( 642 new FN FileId(0) 54..81 61..64 Other
622 refs, 643
623 "new FN FileId(1) 54..101 61..64 Other", 644 FileId(0) 126..129 StructLiteral
624 &["FileId(1) 146..149 StructLiteral"], 645 "#]],
625 ); 646 );
626 } 647 }
627 648
628 #[test] 649 #[test]
629 fn test_find_all_refs_nested_module() { 650 fn test_find_all_refs_nested_module() {
630 let code = r#" 651 check(
631 //- /lib.rs 652 r#"
632 mod foo { 653//- /lib.rs
633 mod bar; 654mod foo { mod bar; }
634 }
635 655
636 fn f<|>() {} 656fn f<|>() {}
637 657
638 //- /foo/bar.rs 658//- /foo/bar.rs
639 use crate::f; 659use crate::f;
640 660
641 fn g() { 661fn g() { f(); }
642 f(); 662"#,
643 } 663 expect![[r#"
644 "#; 664 f FN FileId(0) 22..31 25..26 Other
645 665
646 let (analysis, pos) = analysis_and_position(code); 666 FileId(1) 11..12 Other
647 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); 667 FileId(1) 24..25 StructLiteral
648 check_result( 668 "#]],
649 refs,
650 "f FN FileId(1) 26..35 29..30 Other",
651 &["FileId(2) 11..12 Other", "FileId(2) 28..29 StructLiteral"],
652 ); 669 );
653 } 670 }
654 671
655 fn get_all_refs(ra_fixture: &str) -> ReferenceSearchResult { 672 fn check(ra_fixture: &str, expect: Expect) {
656 let (analysis, position) = analysis_and_position(ra_fixture); 673 check_with_scope(ra_fixture, None, expect)
657 analysis.find_all_refs(position, None).unwrap().unwrap()
658 } 674 }
659 675
660 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) { 676 fn check_with_scope(ra_fixture: &str, search_scope: Option<SearchScope>, expect: Expect) {
661 res.declaration().assert_match(expected_decl); 677 let (analysis, pos) = fixture::position(ra_fixture);
662 assert_eq!(res.references.len(), expected_refs.len()); 678 let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap();
663 res.references()
664 .iter()
665 .enumerate()
666 .for_each(|(i, r)| ref_assert_match(r, expected_refs[i]));
667 }
668 679
669 impl Declaration { 680 let mut actual = String::new();
670 fn debug_render(&self) -> String { 681 {
671 let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind); 682 let decl = refs.declaration;
672 if let Some(access) = self.access { 683 format_to!(actual, "{} {:?}", decl.nav.debug_render(), decl.kind);
673 s.push_str(&format!(" {:?}", access)); 684 if let Some(access) = decl.access {
685 format_to!(actual, " {:?}", access)
674 } 686 }
675 s 687 actual += "\n\n";
676 } 688 }
677 689
678 fn assert_match(&self, expected: &str) { 690 for r in &refs.references {
679 let actual = self.debug_render(); 691 format_to!(actual, "{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
680 test_utils::assert_eq_text!(expected.trim(), actual.trim(),); 692 if let Some(access) = r.access {
681 } 693 format_to!(actual, " {:?}", access);
682 } 694 }
683 695 actual += "\n";
684 fn ref_debug_render(r: &Reference) -> String {
685 let mut s = format!("{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
686 if let Some(access) = r.access {
687 s.push_str(&format!(" {:?}", access));
688 } 696 }
689 s 697 expect.assert_eq(&actual)
690 }
691
692 fn ref_assert_match(r: &Reference, expected: &str) {
693 let actual = ref_debug_render(r);
694 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
695 } 698 }
696} 699}
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 301629763..8cbe1ae5a 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -275,11 +275,11 @@ mod tests {
275 use test_utils::{assert_eq_text, mark}; 275 use test_utils::{assert_eq_text, mark};
276 use text_edit::TextEdit; 276 use text_edit::TextEdit;
277 277
278 use crate::{mock_analysis::analysis_and_position, FileId}; 278 use crate::{fixture, FileId};
279 279
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after); 281 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = analysis_and_position(ra_fixture_before); 282 let (analysis, position) = fixture::position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap(); 283 let source_change = analysis.rename(position, new_name).unwrap();
284 let mut text_edit_builder = TextEdit::builder(); 284 let mut text_edit_builder = TextEdit::builder();
285 let mut file_id: Option<FileId> = None; 285 let mut file_id: Option<FileId> = None;
@@ -297,7 +297,7 @@ mod tests {
297 } 297 }
298 298
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = analysis_and_position(ra_fixture); 300 let (analysis, position) = fixture::position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap(); 301 let source_change = analysis.rename(position, new_name).unwrap().unwrap();
302 expect.assert_debug_eq(&source_change) 302 expect.assert_debug_eq(&source_change)
303 } 303 }
@@ -314,7 +314,7 @@ mod tests {
314 314
315 #[test] 315 #[test]
316 fn test_rename_to_invalid_identifier() { 316 fn test_rename_to_invalid_identifier() {
317 let (analysis, position) = analysis_and_position(r#"fn main() { let i<|> = 1; }"#); 317 let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#);
318 let new_name = "invalid!"; 318 let new_name = "invalid!";
319 let source_change = analysis.rename(position, new_name).unwrap(); 319 let source_change = analysis.rename(position, new_name).unwrap();
320 assert!(source_change.is_none()); 320 assert!(source_change.is_none());
@@ -602,7 +602,7 @@ mod foo<|>;
602 source_file_edits: [ 602 source_file_edits: [
603 SourceFileEdit { 603 SourceFileEdit {
604 file_id: FileId( 604 file_id: FileId(
605 2, 605 1,
606 ), 606 ),
607 edit: TextEdit { 607 edit: TextEdit {
608 indels: [ 608 indels: [
@@ -617,10 +617,10 @@ mod foo<|>;
617 file_system_edits: [ 617 file_system_edits: [
618 MoveFile { 618 MoveFile {
619 src: FileId( 619 src: FileId(
620 3, 620 2,
621 ), 621 ),
622 anchor: FileId( 622 anchor: FileId(
623 3, 623 2,
624 ), 624 ),
625 dst: "foo2.rs", 625 dst: "foo2.rs",
626 }, 626 },
@@ -655,7 +655,7 @@ use crate::foo<|>::FooContent;
655 source_file_edits: [ 655 source_file_edits: [
656 SourceFileEdit { 656 SourceFileEdit {
657 file_id: FileId( 657 file_id: FileId(
658 1, 658 0,
659 ), 659 ),
660 edit: TextEdit { 660 edit: TextEdit {
661 indels: [ 661 indels: [
@@ -668,7 +668,7 @@ use crate::foo<|>::FooContent;
668 }, 668 },
669 SourceFileEdit { 669 SourceFileEdit {
670 file_id: FileId( 670 file_id: FileId(
671 3, 671 2,
672 ), 672 ),
673 edit: TextEdit { 673 edit: TextEdit {
674 indels: [ 674 indels: [
@@ -683,10 +683,10 @@ use crate::foo<|>::FooContent;
683 file_system_edits: [ 683 file_system_edits: [
684 MoveFile { 684 MoveFile {
685 src: FileId( 685 src: FileId(
686 2, 686 1,
687 ), 687 ),
688 anchor: FileId( 688 anchor: FileId(
689 2, 689 1,
690 ), 690 ),
691 dst: "quux.rs", 691 dst: "quux.rs",
692 }, 692 },
@@ -715,7 +715,7 @@ mod fo<|>o;
715 source_file_edits: [ 715 source_file_edits: [
716 SourceFileEdit { 716 SourceFileEdit {
717 file_id: FileId( 717 file_id: FileId(
718 1, 718 0,
719 ), 719 ),
720 edit: TextEdit { 720 edit: TextEdit {
721 indels: [ 721 indels: [
@@ -730,10 +730,10 @@ mod fo<|>o;
730 file_system_edits: [ 730 file_system_edits: [
731 MoveFile { 731 MoveFile {
732 src: FileId( 732 src: FileId(
733 2, 733 1,
734 ), 734 ),
735 anchor: FileId( 735 anchor: FileId(
736 2, 736 1,
737 ), 737 ),
738 dst: "../foo2/mod.rs", 738 dst: "../foo2/mod.rs",
739 }, 739 },
@@ -763,7 +763,7 @@ mod outer { mod fo<|>o; }
763 source_file_edits: [ 763 source_file_edits: [
764 SourceFileEdit { 764 SourceFileEdit {
765 file_id: FileId( 765 file_id: FileId(
766 1, 766 0,
767 ), 767 ),
768 edit: TextEdit { 768 edit: TextEdit {
769 indels: [ 769 indels: [
@@ -778,10 +778,10 @@ mod outer { mod fo<|>o; }
778 file_system_edits: [ 778 file_system_edits: [
779 MoveFile { 779 MoveFile {
780 src: FileId( 780 src: FileId(
781 2, 781 1,
782 ), 782 ),
783 anchor: FileId( 783 anchor: FileId(
784 2, 784 1,
785 ), 785 ),
786 dst: "bar.rs", 786 dst: "bar.rs",
787 }, 787 },
@@ -834,7 +834,7 @@ pub mod foo<|>;
834 source_file_edits: [ 834 source_file_edits: [
835 SourceFileEdit { 835 SourceFileEdit {
836 file_id: FileId( 836 file_id: FileId(
837 2, 837 1,
838 ), 838 ),
839 edit: TextEdit { 839 edit: TextEdit {
840 indels: [ 840 indels: [
@@ -847,7 +847,7 @@ pub mod foo<|>;
847 }, 847 },
848 SourceFileEdit { 848 SourceFileEdit {
849 file_id: FileId( 849 file_id: FileId(
850 1, 850 0,
851 ), 851 ),
852 edit: TextEdit { 852 edit: TextEdit {
853 indels: [ 853 indels: [
@@ -862,10 +862,10 @@ pub mod foo<|>;
862 file_system_edits: [ 862 file_system_edits: [
863 MoveFile { 863 MoveFile {
864 src: FileId( 864 src: FileId(
865 3, 865 2,
866 ), 866 ),
867 anchor: FileId( 867 anchor: FileId(
868 3, 868 2,
869 ), 869 ),
870 dst: "foo2.rs", 870 dst: "foo2.rs",
871 }, 871 },
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index cfeff40c1..752ef2f21 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -292,7 +292,7 @@ fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool {
292mod tests { 292mod tests {
293 use expect_test::{expect, Expect}; 293 use expect_test::{expect, Expect};
294 294
295 use crate::mock_analysis::analysis_and_position; 295 use crate::fixture;
296 296
297 use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST}; 297 use super::{RunnableAction, BENCH, BIN, DOCTEST, TEST};
298 298
@@ -302,7 +302,7 @@ mod tests {
302 actions: &[&RunnableAction], 302 actions: &[&RunnableAction],
303 expect: Expect, 303 expect: Expect,
304 ) { 304 ) {
305 let (analysis, position) = analysis_and_position(ra_fixture); 305 let (analysis, position) = fixture::position(ra_fixture);
306 let runnables = analysis.runnables(position.file_id).unwrap(); 306 let runnables = analysis.runnables(position.file_id).unwrap();
307 expect.assert_debug_eq(&runnables); 307 expect.assert_debug_eq(&runnables);
308 assert_eq!( 308 assert_eq!(
@@ -335,7 +335,7 @@ fn bench() {}
335 Runnable { 335 Runnable {
336 nav: NavigationTarget { 336 nav: NavigationTarget {
337 file_id: FileId( 337 file_id: FileId(
338 1, 338 0,
339 ), 339 ),
340 full_range: 1..13, 340 full_range: 1..13,
341 focus_range: Some( 341 focus_range: Some(
@@ -353,7 +353,7 @@ fn bench() {}
353 Runnable { 353 Runnable {
354 nav: NavigationTarget { 354 nav: NavigationTarget {
355 file_id: FileId( 355 file_id: FileId(
356 1, 356 0,
357 ), 357 ),
358 full_range: 15..39, 358 full_range: 15..39,
359 focus_range: Some( 359 focus_range: Some(
@@ -378,7 +378,7 @@ fn bench() {}
378 Runnable { 378 Runnable {
379 nav: NavigationTarget { 379 nav: NavigationTarget {
380 file_id: FileId( 380 file_id: FileId(
381 1, 381 0,
382 ), 382 ),
383 full_range: 41..75, 383 full_range: 41..75,
384 focus_range: Some( 384 focus_range: Some(
@@ -403,7 +403,7 @@ fn bench() {}
403 Runnable { 403 Runnable {
404 nav: NavigationTarget { 404 nav: NavigationTarget {
405 file_id: FileId( 405 file_id: FileId(
406 1, 406 0,
407 ), 407 ),
408 full_range: 77..99, 408 full_range: 77..99,
409 focus_range: Some( 409 focus_range: Some(
@@ -494,7 +494,7 @@ fn should_have_no_runnable_6() {}
494 Runnable { 494 Runnable {
495 nav: NavigationTarget { 495 nav: NavigationTarget {
496 file_id: FileId( 496 file_id: FileId(
497 1, 497 0,
498 ), 498 ),
499 full_range: 1..13, 499 full_range: 1..13,
500 focus_range: Some( 500 focus_range: Some(
@@ -512,7 +512,7 @@ fn should_have_no_runnable_6() {}
512 Runnable { 512 Runnable {
513 nav: NavigationTarget { 513 nav: NavigationTarget {
514 file_id: FileId( 514 file_id: FileId(
515 1, 515 0,
516 ), 516 ),
517 full_range: 15..74, 517 full_range: 15..74,
518 focus_range: None, 518 focus_range: None,
@@ -532,7 +532,7 @@ fn should_have_no_runnable_6() {}
532 Runnable { 532 Runnable {
533 nav: NavigationTarget { 533 nav: NavigationTarget {
534 file_id: FileId( 534 file_id: FileId(
535 1, 535 0,
536 ), 536 ),
537 full_range: 76..148, 537 full_range: 76..148,
538 focus_range: None, 538 focus_range: None,
@@ -552,7 +552,7 @@ fn should_have_no_runnable_6() {}
552 Runnable { 552 Runnable {
553 nav: NavigationTarget { 553 nav: NavigationTarget {
554 file_id: FileId( 554 file_id: FileId(
555 1, 555 0,
556 ), 556 ),
557 full_range: 150..254, 557 full_range: 150..254,
558 focus_range: None, 558 focus_range: None,
@@ -596,7 +596,7 @@ impl Data {
596 Runnable { 596 Runnable {
597 nav: NavigationTarget { 597 nav: NavigationTarget {
598 file_id: FileId( 598 file_id: FileId(
599 1, 599 0,
600 ), 600 ),
601 full_range: 1..13, 601 full_range: 1..13,
602 focus_range: Some( 602 focus_range: Some(
@@ -614,7 +614,7 @@ impl Data {
614 Runnable { 614 Runnable {
615 nav: NavigationTarget { 615 nav: NavigationTarget {
616 file_id: FileId( 616 file_id: FileId(
617 1, 617 0,
618 ), 618 ),
619 full_range: 44..98, 619 full_range: 44..98,
620 focus_range: None, 620 focus_range: None,
@@ -653,7 +653,7 @@ mod test_mod {
653 Runnable { 653 Runnable {
654 nav: NavigationTarget { 654 nav: NavigationTarget {
655 file_id: FileId( 655 file_id: FileId(
656 1, 656 0,
657 ), 657 ),
658 full_range: 1..51, 658 full_range: 1..51,
659 focus_range: Some( 659 focus_range: Some(
@@ -673,7 +673,7 @@ mod test_mod {
673 Runnable { 673 Runnable {
674 nav: NavigationTarget { 674 nav: NavigationTarget {
675 file_id: FileId( 675 file_id: FileId(
676 1, 676 0,
677 ), 677 ),
678 full_range: 20..49, 678 full_range: 20..49,
679 focus_range: Some( 679 focus_range: Some(
@@ -733,7 +733,7 @@ mod root_tests {
733 Runnable { 733 Runnable {
734 nav: NavigationTarget { 734 nav: NavigationTarget {
735 file_id: FileId( 735 file_id: FileId(
736 1, 736 0,
737 ), 737 ),
738 full_range: 22..323, 738 full_range: 22..323,
739 focus_range: Some( 739 focus_range: Some(
@@ -753,7 +753,7 @@ mod root_tests {
753 Runnable { 753 Runnable {
754 nav: NavigationTarget { 754 nav: NavigationTarget {
755 file_id: FileId( 755 file_id: FileId(
756 1, 756 0,
757 ), 757 ),
758 full_range: 51..192, 758 full_range: 51..192,
759 focus_range: Some( 759 focus_range: Some(
@@ -773,7 +773,7 @@ mod root_tests {
773 Runnable { 773 Runnable {
774 nav: NavigationTarget { 774 nav: NavigationTarget {
775 file_id: FileId( 775 file_id: FileId(
776 1, 776 0,
777 ), 777 ),
778 full_range: 84..126, 778 full_range: 84..126,
779 focus_range: Some( 779 focus_range: Some(
@@ -798,7 +798,7 @@ mod root_tests {
798 Runnable { 798 Runnable {
799 nav: NavigationTarget { 799 nav: NavigationTarget {
800 file_id: FileId( 800 file_id: FileId(
801 1, 801 0,
802 ), 802 ),
803 full_range: 140..182, 803 full_range: 140..182,
804 focus_range: Some( 804 focus_range: Some(
@@ -823,7 +823,7 @@ mod root_tests {
823 Runnable { 823 Runnable {
824 nav: NavigationTarget { 824 nav: NavigationTarget {
825 file_id: FileId( 825 file_id: FileId(
826 1, 826 0,
827 ), 827 ),
828 full_range: 202..286, 828 full_range: 202..286,
829 focus_range: Some( 829 focus_range: Some(
@@ -843,7 +843,7 @@ mod root_tests {
843 Runnable { 843 Runnable {
844 nav: NavigationTarget { 844 nav: NavigationTarget {
845 file_id: FileId( 845 file_id: FileId(
846 1, 846 0,
847 ), 847 ),
848 full_range: 235..276, 848 full_range: 235..276,
849 focus_range: Some( 849 focus_range: Some(
@@ -886,7 +886,7 @@ fn test_foo1() {}
886 Runnable { 886 Runnable {
887 nav: NavigationTarget { 887 nav: NavigationTarget {
888 file_id: FileId( 888 file_id: FileId(
889 1, 889 0,
890 ), 890 ),
891 full_range: 1..50, 891 full_range: 1..50,
892 focus_range: Some( 892 focus_range: Some(
@@ -934,7 +934,7 @@ fn test_foo1() {}
934 Runnable { 934 Runnable {
935 nav: NavigationTarget { 935 nav: NavigationTarget {
936 file_id: FileId( 936 file_id: FileId(
937 1, 937 0,
938 ), 938 ),
939 full_range: 1..72, 939 full_range: 1..72,
940 focus_range: Some( 940 focus_range: Some(
diff --git a/crates/ide/src/status.rs b/crates/ide/src/status.rs
index c23708181..0af84daa0 100644
--- a/crates/ide/src/status.rs
+++ b/crates/ide/src/status.rs
@@ -2,19 +2,19 @@ use std::{fmt, iter::FromIterator, sync::Arc};
2 2
3use base_db::{ 3use base_db::{
4 salsa::debug::{DebugQueryTable, TableEntry}, 4 salsa::debug::{DebugQueryTable, TableEntry},
5 FileTextQuery, SourceRootId, 5 CrateId, FileId, FileTextQuery, SourceDatabase, SourceRootId,
6}; 6};
7use hir::MacroFile; 7use hir::MacroFile;
8use ide_db::{ 8use ide_db::{
9 symbol_index::{LibrarySymbolsQuery, SymbolIndex}, 9 symbol_index::{LibrarySymbolsQuery, SymbolIndex},
10 RootDatabase, 10 RootDatabase,
11}; 11};
12use itertools::Itertools;
12use profile::{memory_usage, Bytes}; 13use profile::{memory_usage, Bytes};
13use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
15use stdx::format_to;
14use syntax::{ast, Parse, SyntaxNode}; 16use syntax::{ast, Parse, SyntaxNode};
15 17
16use crate::FileId;
17
18fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { 18fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
19 base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>() 19 base_db::ParseQuery.in_db(db).entries::<SyntaxTreeStats>()
20} 20}
@@ -31,20 +31,36 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
31// 31//
32// | VS Code | **Rust Analyzer: Status** 32// | VS Code | **Rust Analyzer: Status**
33// |=== 33// |===
34pub(crate) fn status(db: &RootDatabase) -> String { 34pub(crate) fn status(db: &RootDatabase, file_id: Option<FileId>) -> String {
35 let files_stats = FileTextQuery.in_db(db).entries::<FilesStats>(); 35 let mut buf = String::new();
36 let syntax_tree_stats = syntax_tree_stats(db); 36 format_to!(buf, "{}\n", FileTextQuery.in_db(db).entries::<FilesStats>());
37 let macro_syntax_tree_stats = macro_syntax_tree_stats(db); 37 format_to!(buf, "{}\n", LibrarySymbolsQuery.in_db(db).entries::<LibrarySymbolsStats>());
38 let symbols_stats = LibrarySymbolsQuery.in_db(db).entries::<LibrarySymbolsStats>(); 38 format_to!(buf, "{}\n", syntax_tree_stats(db));
39 format!( 39 format_to!(buf, "{} (macros)\n", macro_syntax_tree_stats(db));
40 "{}\n{}\n{}\n{} (macros)\n\n\nmemory:\n{}\ngc {:?} seconds ago", 40 format_to!(buf, "{} total\n", memory_usage());
41 files_stats, 41
42 symbols_stats, 42 if let Some(file_id) = file_id {
43 syntax_tree_stats, 43 format_to!(buf, "\nfile info:\n");
44 macro_syntax_tree_stats, 44 let krate = crate::parent_module::crate_for(db, file_id).pop();
45 memory_usage(), 45 match krate {
46 db.last_gc.elapsed().as_secs(), 46 Some(krate) => {
47 ) 47 let crate_graph = db.crate_graph();
48 let display_crate = |krate: CrateId| match &crate_graph[krate].display_name {
49 Some(it) => format!("{}({:?})", it, krate),
50 None => format!("{:?}", krate),
51 };
52 format_to!(buf, "crate: {}\n", display_crate(krate));
53 let deps = crate_graph[krate]
54 .dependencies
55 .iter()
56 .map(|dep| format!("{}={:?}", dep.name, dep.crate_id))
57 .format(", ");
58 format_to!(buf, "deps: {}\n", deps);
59 }
60 None => format_to!(buf, "does not belong to any crate"),
61 }
62 }
63 buf
48} 64}
49 65
50#[derive(Default)] 66#[derive(Default)]
@@ -121,7 +137,7 @@ struct LibrarySymbolsStats {
121 137
122impl fmt::Display for LibrarySymbolsStats { 138impl fmt::Display for LibrarySymbolsStats {
123 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 139 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
124 write!(fmt, "{} ({}) symbols", self.total, self.size) 140 write!(fmt, "{} ({}) index symbols", self.total, self.size)
125 } 141 }
126} 142}
127 143
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 211e62ea1..694c4b7fa 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -3,7 +3,7 @@ use std::fs;
3use expect_test::{expect_file, ExpectFile}; 3use expect_test::{expect_file, ExpectFile};
4use test_utils::project_dir; 4use test_utils::project_dir;
5 5
6use crate::{mock_analysis::single_file, FileRange, TextRange}; 6use crate::{fixture, FileRange, TextRange};
7 7
8#[test] 8#[test]
9fn test_highlighting() { 9fn test_highlighting() {
@@ -178,7 +178,7 @@ fn accidentally_quadratic() {
178 let file = project_dir().join("crates/syntax/test_data/accidentally_quadratic"); 178 let file = project_dir().join("crates/syntax/test_data/accidentally_quadratic");
179 let src = fs::read_to_string(file).unwrap(); 179 let src = fs::read_to_string(file).unwrap();
180 180
181 let (analysis, file_id) = single_file(&src); 181 let (analysis, file_id) = fixture::file(&src);
182 182
183 // let t = std::time::Instant::now(); 183 // let t = std::time::Instant::now();
184 let _ = analysis.highlight(file_id).unwrap(); 184 let _ = analysis.highlight(file_id).unwrap();
@@ -187,7 +187,7 @@ fn accidentally_quadratic() {
187 187
188#[test] 188#[test]
189fn test_ranges() { 189fn test_ranges() {
190 let (analysis, file_id) = single_file( 190 let (analysis, file_id) = fixture::file(
191 r#" 191 r#"
192#[derive(Clone, Debug)] 192#[derive(Clone, Debug)]
193struct Foo { 193struct Foo {
@@ -228,7 +228,7 @@ fn main() {
228 228
229#[test] 229#[test]
230fn ranges_sorted() { 230fn ranges_sorted() {
231 let (analysis, file_id) = single_file( 231 let (analysis, file_id) = fixture::file(
232 r#" 232 r#"
233#[foo(bar = "bar")] 233#[foo(bar = "bar")]
234macro_rules! test {} 234macro_rules! test {}
@@ -462,12 +462,12 @@ macro_rules! noop {
462fn test_extern_crate() { 462fn test_extern_crate() {
463 check_highlighting( 463 check_highlighting(
464 r#" 464 r#"
465 //- /main.rs 465 //- /main.rs crate:main deps:std,alloc
466 extern crate std; 466 extern crate std;
467 extern crate alloc as abc; 467 extern crate alloc as abc;
468 //- /std/lib.rs 468 //- /std/lib.rs crate:std
469 pub struct S; 469 pub struct S;
470 //- /alloc/lib.rs 470 //- /alloc/lib.rs crate:alloc
471 pub struct A 471 pub struct A
472 "#, 472 "#,
473 expect_file!["./test_data/highlight_extern_crate.html"], 473 expect_file!["./test_data/highlight_extern_crate.html"],
@@ -479,7 +479,7 @@ fn test_extern_crate() {
479/// result as HTML, and compares it with the HTML file given as `snapshot`. 479/// result as HTML, and compares it with the HTML file given as `snapshot`.
480/// Note that the `snapshot` file is overwritten by the rendered HTML. 480/// Note that the `snapshot` file is overwritten by the rendered HTML.
481fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) { 481fn check_highlighting(ra_fixture: &str, expect: ExpectFile, rainbow: bool) {
482 let (analysis, file_id) = single_file(ra_fixture); 482 let (analysis, file_id) = fixture::file(ra_fixture);
483 let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap(); 483 let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap();
484 expect.assert_eq(actual_html) 484 expect.assert_eq(actual_html)
485} 485}
diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs
index f80044959..0eed2dbd7 100644
--- a/crates/ide/src/syntax_tree.rs
+++ b/crates/ide/src/syntax_tree.rs
@@ -104,12 +104,12 @@ fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option<St
104mod tests { 104mod tests {
105 use test_utils::assert_eq_text; 105 use test_utils::assert_eq_text;
106 106
107 use crate::mock_analysis::{analysis_and_range, single_file}; 107 use crate::fixture;
108 108
109 #[test] 109 #[test]
110 fn test_syntax_tree_without_range() { 110 fn test_syntax_tree_without_range() {
111 // Basic syntax 111 // Basic syntax
112 let (analysis, file_id) = single_file(r#"fn foo() {}"#); 112 let (analysis, file_id) = fixture::file(r#"fn foo() {}"#);
113 let syn = analysis.syntax_tree(file_id, None).unwrap(); 113 let syn = analysis.syntax_tree(file_id, None).unwrap();
114 114
115 assert_eq_text!( 115 assert_eq_text!(
@@ -132,7 +132,7 @@ [email protected]
132 .trim() 132 .trim()
133 ); 133 );
134 134
135 let (analysis, file_id) = single_file( 135 let (analysis, file_id) = fixture::file(
136 r#" 136 r#"
137fn test() { 137fn test() {
138 assert!(" 138 assert!("
@@ -184,7 +184,7 @@ [email protected]
184 184
185 #[test] 185 #[test]
186 fn test_syntax_tree_with_range() { 186 fn test_syntax_tree_with_range() {
187 let (analysis, range) = analysis_and_range(r#"<|>fn foo() {}<|>"#.trim()); 187 let (analysis, range) = fixture::range(r#"<|>fn foo() {}<|>"#.trim());
188 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); 188 let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap();
189 189
190 assert_eq_text!( 190 assert_eq_text!(
@@ -206,7 +206,7 @@ [email protected]
206 .trim() 206 .trim()
207 ); 207 );
208 208
209 let (analysis, range) = analysis_and_range( 209 let (analysis, range) = fixture::range(
210 r#"fn test() { 210 r#"fn test() {
211 <|>assert!(" 211 <|>assert!("
212 fn foo() { 212 fn foo() {
@@ -242,7 +242,7 @@ [email protected]
242 242
243 #[test] 243 #[test]
244 fn test_syntax_tree_inside_string() { 244 fn test_syntax_tree_inside_string() {
245 let (analysis, range) = analysis_and_range( 245 let (analysis, range) = fixture::range(
246 r#"fn test() { 246 r#"fn test() {
247 assert!(" 247 assert!("
248<|>fn foo() { 248<|>fn foo() {
@@ -276,7 +276,7 @@ [email protected]
276 ); 276 );
277 277
278 // With a raw string 278 // With a raw string
279 let (analysis, range) = analysis_and_range( 279 let (analysis, range) = fixture::range(
280 r###"fn test() { 280 r###"fn test() {
281 assert!(r#" 281 assert!(r#"
282<|>fn foo() { 282<|>fn foo() {
@@ -310,7 +310,7 @@ [email protected]
310 ); 310 );
311 311
312 // With a raw string 312 // With a raw string
313 let (analysis, range) = analysis_and_range( 313 let (analysis, range) = fixture::range(
314 r###"fn test() { 314 r###"fn test() {
315 assert!(r<|>#" 315 assert!(r<|>#"
316fn foo() { 316fn foo() {
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs
index f7d46146c..a0dc4b9df 100644
--- a/crates/ide/src/typing/on_enter.rs
+++ b/crates/ide/src/typing/on_enter.rs
@@ -109,10 +109,10 @@ mod tests {
109 use stdx::trim_indent; 109 use stdx::trim_indent;
110 use test_utils::{assert_eq_text, mark}; 110 use test_utils::{assert_eq_text, mark};
111 111
112 use crate::mock_analysis::analysis_and_position; 112 use crate::fixture;
113 113
114 fn apply_on_enter(before: &str) -> Option<String> { 114 fn apply_on_enter(before: &str) -> Option<String> {
115 let (analysis, position) = analysis_and_position(&before); 115 let (analysis, position) = fixture::position(&before);
116 let result = analysis.on_enter(position).unwrap()?; 116 let result = analysis.on_enter(position).unwrap()?;
117 117
118 let mut actual = analysis.file_text(position.file_id).unwrap().to_string(); 118 let mut actual = analysis.file_text(position.file_id).unwrap().to_string();
diff --git a/crates/ide_db/src/change.rs b/crates/ide_db/src/apply_change.rs
index 8b4fd7ab8..da16fa21d 100644
--- a/crates/ide_db/src/change.rs
+++ b/crates/ide_db/src/apply_change.rs
@@ -1,58 +1,16 @@
1//! Defines a unit of change that can applied to a state of IDE to get the next 1//! Applies changes to the IDE state transactionally.
2//! state. Changes are transactional.
3 2
4use std::{fmt, sync::Arc, time}; 3use std::{fmt, sync::Arc};
5 4
6use base_db::{ 5use base_db::{
7 salsa::{Database, Durability, SweepStrategy}, 6 salsa::{Database, Durability, SweepStrategy},
8 CrateGraph, FileId, SourceDatabase, SourceDatabaseExt, SourceRoot, SourceRootId, 7 Change, FileId, SourceRootId,
9}; 8};
10use profile::{memory_usage, Bytes}; 9use profile::{memory_usage, Bytes};
11use rustc_hash::FxHashSet; 10use rustc_hash::FxHashSet;
12 11
13use crate::{symbol_index::SymbolsDatabase, RootDatabase}; 12use crate::{symbol_index::SymbolsDatabase, RootDatabase};
14 13
15#[derive(Default)]
16pub struct AnalysisChange {
17 roots: Option<Vec<SourceRoot>>,
18 files_changed: Vec<(FileId, Option<Arc<String>>)>,
19 crate_graph: Option<CrateGraph>,
20}
21
22impl fmt::Debug for AnalysisChange {
23 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
24 let mut d = fmt.debug_struct("AnalysisChange");
25 if let Some(roots) = &self.roots {
26 d.field("roots", roots);
27 }
28 if !self.files_changed.is_empty() {
29 d.field("files_changed", &self.files_changed.len());
30 }
31 if self.crate_graph.is_some() {
32 d.field("crate_graph", &self.crate_graph);
33 }
34 d.finish()
35 }
36}
37
38impl AnalysisChange {
39 pub fn new() -> AnalysisChange {
40 AnalysisChange::default()
41 }
42
43 pub fn set_roots(&mut self, roots: Vec<SourceRoot>) {
44 self.roots = Some(roots);
45 }
46
47 pub fn change_file(&mut self, file_id: FileId, new_text: Option<Arc<String>>) {
48 self.files_changed.push((file_id, new_text))
49 }
50
51 pub fn set_crate_graph(&mut self, graph: CrateGraph) {
52 self.crate_graph = Some(graph);
53 }
54}
55
56#[derive(Debug)] 14#[derive(Debug)]
57struct AddFile { 15struct AddFile {
58 file_id: FileId, 16 file_id: FileId,
@@ -81,59 +39,31 @@ impl fmt::Debug for RootChange {
81 } 39 }
82} 40}
83 41
84const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100);
85
86impl RootDatabase { 42impl RootDatabase {
87 pub fn request_cancellation(&mut self) { 43 pub fn request_cancellation(&mut self) {
88 let _p = profile::span("RootDatabase::request_cancellation"); 44 let _p = profile::span("RootDatabase::request_cancellation");
89 self.salsa_runtime_mut().synthetic_write(Durability::LOW); 45 self.salsa_runtime_mut().synthetic_write(Durability::LOW);
90 } 46 }
91 47
92 pub fn apply_change(&mut self, change: AnalysisChange) { 48 pub fn apply_change(&mut self, change: Change) {
93 let _p = profile::span("RootDatabase::apply_change"); 49 let _p = profile::span("RootDatabase::apply_change");
94 self.request_cancellation(); 50 self.request_cancellation();
95 log::info!("apply_change {:?}", change); 51 log::info!("apply_change {:?}", change);
96 if let Some(roots) = change.roots { 52 if let Some(roots) = &change.roots {
97 let mut local_roots = FxHashSet::default(); 53 let mut local_roots = FxHashSet::default();
98 let mut library_roots = FxHashSet::default(); 54 let mut library_roots = FxHashSet::default();
99 for (idx, root) in roots.into_iter().enumerate() { 55 for (idx, root) in roots.iter().enumerate() {
100 let root_id = SourceRootId(idx as u32); 56 let root_id = SourceRootId(idx as u32);
101 let durability = durability(&root);
102 if root.is_library { 57 if root.is_library {
103 library_roots.insert(root_id); 58 library_roots.insert(root_id);
104 } else { 59 } else {
105 local_roots.insert(root_id); 60 local_roots.insert(root_id);
106 } 61 }
107 for file_id in root.iter() {
108 self.set_file_source_root_with_durability(file_id, root_id, durability);
109 }
110 self.set_source_root_with_durability(root_id, Arc::new(root), durability);
111 } 62 }
112 self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); 63 self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
113 self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH); 64 self.set_library_roots_with_durability(Arc::new(library_roots), Durability::HIGH);
114 } 65 }
115 66 change.apply(self);
116 for (file_id, text) in change.files_changed {
117 let source_root_id = self.file_source_root(file_id);
118 let source_root = self.source_root(source_root_id);
119 let durability = durability(&source_root);
120 // XXX: can't actually remove the file, just reset the text
121 let text = text.unwrap_or_default();
122 self.set_file_text_with_durability(file_id, text, durability)
123 }
124 if let Some(crate_graph) = change.crate_graph {
125 self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
126 }
127 }
128
129 pub fn maybe_collect_garbage(&mut self) {
130 if cfg!(feature = "wasm") {
131 return;
132 }
133
134 if self.last_gc_check.elapsed() > GC_COOLDOWN {
135 self.last_gc_check = crate::wasm_shims::Instant::now();
136 }
137 } 67 }
138 68
139 pub fn collect_garbage(&mut self) { 69 pub fn collect_garbage(&mut self) {
@@ -142,7 +72,6 @@ impl RootDatabase {
142 } 72 }
143 73
144 let _p = profile::span("RootDatabase::collect_garbage"); 74 let _p = profile::span("RootDatabase::collect_garbage");
145 self.last_gc = crate::wasm_shims::Instant::now();
146 75
147 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); 76 let sweep = SweepStrategy::default().discard_values().sweep_all_revisions();
148 77
@@ -308,11 +237,3 @@ impl RootDatabase {
308 acc 237 acc
309 } 238 }
310} 239}
311
312fn durability(source_root: &SourceRoot) -> Durability {
313 if source_root.is_library {
314 Durability::HIGH
315 } else {
316 Durability::LOW
317 }
318}
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index 70ada02f3..7eff247c7 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -2,15 +2,14 @@
2//! 2//!
3//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. 3//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.
4 4
5mod apply_change;
5pub mod label; 6pub mod label;
6pub mod line_index; 7pub mod line_index;
7pub mod symbol_index; 8pub mod symbol_index;
8pub mod change;
9pub mod defs; 9pub mod defs;
10pub mod search; 10pub mod search;
11pub mod imports_locator; 11pub mod imports_locator;
12pub mod source_change; 12pub mod source_change;
13mod wasm_shims;
14 13
15use std::{fmt, sync::Arc}; 14use std::{fmt, sync::Arc};
16 15
@@ -36,8 +35,6 @@ use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase};
36)] 35)]
37pub struct RootDatabase { 36pub struct RootDatabase {
38 storage: salsa::Storage<RootDatabase>, 37 storage: salsa::Storage<RootDatabase>,
39 pub last_gc: crate::wasm_shims::Instant,
40 pub last_gc_check: crate::wasm_shims::Instant,
41} 38}
42 39
43impl fmt::Debug for RootDatabase { 40impl fmt::Debug for RootDatabase {
@@ -99,11 +96,7 @@ impl Default for RootDatabase {
99 96
100impl RootDatabase { 97impl RootDatabase {
101 pub fn new(lru_capacity: Option<usize>) -> RootDatabase { 98 pub fn new(lru_capacity: Option<usize>) -> RootDatabase {
102 let mut db = RootDatabase { 99 let mut db = RootDatabase { storage: salsa::Storage::default() };
103 storage: salsa::Storage::default(),
104 last_gc: crate::wasm_shims::Instant::now(),
105 last_gc_check: crate::wasm_shims::Instant::now(),
106 };
107 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); 100 db.set_crate_graph_with_durability(Default::default(), Durability::HIGH);
108 db.set_local_roots_with_durability(Default::default(), Durability::HIGH); 101 db.set_local_roots_with_durability(Default::default(), Durability::HIGH);
109 db.set_library_roots_with_durability(Default::default(), Durability::HIGH); 102 db.set_library_roots_with_durability(Default::default(), Durability::HIGH);
@@ -121,11 +114,7 @@ impl RootDatabase {
121 114
122impl salsa::ParallelDatabase for RootDatabase { 115impl salsa::ParallelDatabase for RootDatabase {
123 fn snapshot(&self) -> salsa::Snapshot<RootDatabase> { 116 fn snapshot(&self) -> salsa::Snapshot<RootDatabase> {
124 salsa::Snapshot::new(RootDatabase { 117 salsa::Snapshot::new(RootDatabase { storage: self.storage.snapshot() })
125 storage: self.storage.snapshot(),
126 last_gc: self.last_gc,
127 last_gc_check: self.last_gc_check,
128 })
129 } 118 }
130} 119}
131 120
diff --git a/crates/ide_db/src/wasm_shims.rs b/crates/ide_db/src/wasm_shims.rs
deleted file mode 100644
index 7af9f9d9b..000000000
--- a/crates/ide_db/src/wasm_shims.rs
+++ /dev/null
@@ -1,19 +0,0 @@
1//! A version of `std::time::Instant` that doesn't panic in WASM.
2
3#[cfg(not(feature = "wasm"))]
4pub use std::time::Instant;
5
6#[cfg(feature = "wasm")]
7#[derive(Clone, Copy, Debug)]
8pub struct Instant;
9
10#[cfg(feature = "wasm")]
11impl Instant {
12 pub fn now() -> Self {
13 Self
14 }
15
16 pub fn elapsed(&self) -> std::time::Duration {
17 std::time::Duration::new(0, 0)
18 }
19}
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index c312e0a2e..d1c095ba5 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -8,8 +8,7 @@ use base_db::{
8 FileId, 8 FileId,
9}; 9};
10use ide::{ 10use ide::{
11 Analysis, AnalysisChange, AnalysisHost, CompletionConfig, DiagnosticsConfig, FilePosition, 11 Analysis, AnalysisHost, Change, CompletionConfig, DiagnosticsConfig, FilePosition, LineCol,
12 LineCol,
13}; 12};
14use vfs::AbsPathBuf; 13use vfs::AbsPathBuf;
15 14
@@ -143,7 +142,7 @@ fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, w
143 { 142 {
144 let mut text = host.analysis().file_text(file_id).unwrap().to_string(); 143 let mut text = host.analysis().file_text(file_id).unwrap().to_string();
145 text.push_str("\n/* Hello world */\n"); 144 text.push_str("\n/* Hello world */\n");
146 let mut change = AnalysisChange::new(); 145 let mut change = Change::new();
147 change.change_file(file_id, Some(Arc::new(text))); 146 change.change_file(file_id, Some(Arc::new(text)));
148 host.apply_change(change); 147 host.apply_change(change);
149 } 148 }
@@ -156,7 +155,7 @@ fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, w
156 { 155 {
157 let mut text = host.analysis().file_text(file_id).unwrap().to_string(); 156 let mut text = host.analysis().file_text(file_id).unwrap().to_string();
158 text.push_str("\npub fn _dummy() {}\n"); 157 text.push_str("\npub fn _dummy() {}\n");
159 let mut change = AnalysisChange::new(); 158 let mut change = Change::new();
160 change.change_file(file_id, Some(Arc::new(text))); 159 change.change_file(file_id, Some(Arc::new(text)));
161 host.apply_change(change); 160 host.apply_change(change);
162 } 161 }
diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs
index c47cf6ef3..7ae1c9055 100644
--- a/crates/rust-analyzer/src/cli/load_cargo.rs
+++ b/crates/rust-analyzer/src/cli/load_cargo.rs
@@ -5,7 +5,7 @@ use std::{path::Path, sync::Arc};
5use anyhow::Result; 5use anyhow::Result;
6use base_db::CrateGraph; 6use base_db::CrateGraph;
7use crossbeam_channel::{unbounded, Receiver}; 7use crossbeam_channel::{unbounded, Receiver};
8use ide::{AnalysisChange, AnalysisHost}; 8use ide::{AnalysisHost, Change};
9use project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace}; 9use project_model::{CargoConfig, ProcMacroClient, ProjectManifest, ProjectWorkspace};
10use vfs::{loader::Handle, AbsPath, AbsPathBuf}; 10use vfs::{loader::Handle, AbsPath, AbsPathBuf};
11 11
@@ -62,7 +62,7 @@ fn load(
62) -> AnalysisHost { 62) -> AnalysisHost {
63 let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok()); 63 let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
64 let mut host = AnalysisHost::new(lru_cap); 64 let mut host = AnalysisHost::new(lru_cap);
65 let mut analysis_change = AnalysisChange::new(); 65 let mut analysis_change = Change::new();
66 66
67 // wait until Vfs has loaded all roots 67 // wait until Vfs has loaded all roots
68 for task in receiver { 68 for task in receiver {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 42e1ad376..0ab4c37bf 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -38,6 +38,7 @@ pub struct Config {
38 pub cargo: CargoConfig, 38 pub cargo: CargoConfig,
39 pub rustfmt: RustfmtConfig, 39 pub rustfmt: RustfmtConfig,
40 pub flycheck: Option<FlycheckConfig>, 40 pub flycheck: Option<FlycheckConfig>,
41 pub runnables: RunnablesConfig,
41 42
42 pub inlay_hints: InlayHintsConfig, 43 pub inlay_hints: InlayHintsConfig,
43 pub completion: CompletionConfig, 44 pub completion: CompletionConfig,
@@ -124,6 +125,15 @@ pub enum RustfmtConfig {
124 CustomCommand { command: String, args: Vec<String> }, 125 CustomCommand { command: String, args: Vec<String> },
125} 126}
126 127
128/// Configuration for runnable items, such as `main` function or tests.
129#[derive(Debug, Clone, Default)]
130pub struct RunnablesConfig {
131 /// Custom command to be executed instead of `cargo` for runnables.
132 pub override_cargo: Option<String>,
133 /// Additional arguments for the `cargo`, e.g. `--release`.
134 pub cargo_extra_args: Vec<String>,
135}
136
127#[derive(Debug, Clone, Default)] 137#[derive(Debug, Clone, Default)]
128pub struct ClientCapsConfig { 138pub struct ClientCapsConfig {
129 pub location_link: bool, 139 pub location_link: bool,
@@ -164,6 +174,7 @@ impl Config {
164 extra_args: Vec::new(), 174 extra_args: Vec::new(),
165 features: Vec::new(), 175 features: Vec::new(),
166 }), 176 }),
177 runnables: RunnablesConfig::default(),
167 178
168 inlay_hints: InlayHintsConfig { 179 inlay_hints: InlayHintsConfig {
169 type_hints: true, 180 type_hints: true,
@@ -220,6 +231,10 @@ impl Config {
220 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck, 231 load_out_dirs_from_check: data.cargo_loadOutDirsFromCheck,
221 target: data.cargo_target.clone(), 232 target: data.cargo_target.clone(),
222 }; 233 };
234 self.runnables = RunnablesConfig {
235 override_cargo: data.runnables_overrideCargo,
236 cargo_extra_args: data.runnables_cargoExtraArgs,
237 };
223 238
224 self.proc_macro_srv = if data.procMacro_enable { 239 self.proc_macro_srv = if data.procMacro_enable {
225 std::env::current_exe().ok().map(|path| (path, vec!["proc-macro".into()])) 240 std::env::current_exe().ok().map(|path| (path, vec!["proc-macro".into()]))
@@ -474,6 +489,9 @@ config_data! {
474 notifications_cargoTomlNotFound: bool = true, 489 notifications_cargoTomlNotFound: bool = true,
475 procMacro_enable: bool = false, 490 procMacro_enable: bool = false,
476 491
492 runnables_overrideCargo: Option<String> = None,
493 runnables_cargoExtraArgs: Vec<String> = Vec::new(),
494
477 rustfmt_extraArgs: Vec<String> = Vec::new(), 495 rustfmt_extraArgs: Vec<String> = Vec::new(),
478 rustfmt_overrideCommand: Option<Vec<String>> = None, 496 rustfmt_overrideCommand: Option<Vec<String>> = None,
479 497
diff --git a/crates/rust-analyzer/src/dispatch.rs b/crates/rust-analyzer/src/dispatch.rs
index 891fdb96d..9c8815e29 100644
--- a/crates/rust-analyzer/src/dispatch.rs
+++ b/crates/rust-analyzer/src/dispatch.rs
@@ -1,5 +1,5 @@
1//! A visitor for downcasting arbitrary request (JSON) into a specific type. 1//! A visitor for downcasting arbitrary request (JSON) into a specific type.
2use std::panic; 2use std::{fmt, panic};
3 3
4use serde::{de::DeserializeOwned, Serialize}; 4use serde::{de::DeserializeOwned, Serialize};
5 5
@@ -23,7 +23,7 @@ impl<'a> RequestDispatcher<'a> {
23 ) -> Result<&mut Self> 23 ) -> Result<&mut Self>
24 where 24 where
25 R: lsp_types::request::Request + 'static, 25 R: lsp_types::request::Request + 'static,
26 R::Params: DeserializeOwned + panic::UnwindSafe + 'static, 26 R::Params: DeserializeOwned + panic::UnwindSafe + fmt::Debug + 'static,
27 R::Result: Serialize + 'static, 27 R::Result: Serialize + 'static,
28 { 28 {
29 let (id, params) = match self.parse::<R>() { 29 let (id, params) = match self.parse::<R>() {
@@ -34,6 +34,7 @@ impl<'a> RequestDispatcher<'a> {
34 }; 34 };
35 let world = panic::AssertUnwindSafe(&mut *self.global_state); 35 let world = panic::AssertUnwindSafe(&mut *self.global_state);
36 let response = panic::catch_unwind(move || { 36 let response = panic::catch_unwind(move || {
37 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params));
37 let result = f(world.0, params); 38 let result = f(world.0, params);
38 result_to_response::<R>(id, result) 39 result_to_response::<R>(id, result)
39 }) 40 })
@@ -49,7 +50,7 @@ impl<'a> RequestDispatcher<'a> {
49 ) -> Result<&mut Self> 50 ) -> Result<&mut Self>
50 where 51 where
51 R: lsp_types::request::Request + 'static, 52 R: lsp_types::request::Request + 'static,
52 R::Params: DeserializeOwned + Send + 'static, 53 R::Params: DeserializeOwned + Send + fmt::Debug + 'static,
53 R::Result: Serialize + 'static, 54 R::Result: Serialize + 'static,
54 { 55 {
55 let (id, params) = match self.parse::<R>() { 56 let (id, params) = match self.parse::<R>() {
@@ -61,7 +62,10 @@ impl<'a> RequestDispatcher<'a> {
61 62
62 self.global_state.task_pool.handle.spawn({ 63 self.global_state.task_pool.handle.spawn({
63 let world = self.global_state.snapshot(); 64 let world = self.global_state.snapshot();
65
64 move || { 66 move || {
67 let _ctx =
68 stdx::panic_context::enter(format!("request: {} {:#?}", R::METHOD, params));
65 let result = f(world, params); 69 let result = f(world, params);
66 Task::Response(result_to_response::<R>(id, result)) 70 Task::Response(result_to_response::<R>(id, result))
67 } 71 }
@@ -156,6 +160,7 @@ impl<'a> NotificationDispatcher<'a> {
156 return Ok(self); 160 return Ok(self);
157 } 161 }
158 }; 162 };
163 stdx::panic_context::enter(format!("notification: {}", N::METHOD));
159 f(self.global_state, params)?; 164 f(self.global_state, params)?;
160 Ok(self) 165 Ok(self)
161 } 166 }
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 96313aaec..dafab6a6a 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -8,7 +8,7 @@ use std::{sync::Arc, time::Instant};
8use base_db::{CrateId, VfsPath}; 8use base_db::{CrateId, VfsPath};
9use crossbeam_channel::{unbounded, Receiver, Sender}; 9use crossbeam_channel::{unbounded, Receiver, Sender};
10use flycheck::FlycheckHandle; 10use flycheck::FlycheckHandle;
11use ide::{Analysis, AnalysisChange, AnalysisHost, FileId}; 11use ide::{Analysis, AnalysisHost, Change, FileId};
12use lsp_types::{SemanticTokens, Url}; 12use lsp_types::{SemanticTokens, Url};
13use parking_lot::{Mutex, RwLock}; 13use parking_lot::{Mutex, RwLock};
14use project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; 14use project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
@@ -139,7 +139,7 @@ impl GlobalState {
139 let mut has_fs_changes = false; 139 let mut has_fs_changes = false;
140 140
141 let change = { 141 let change = {
142 let mut change = AnalysisChange::new(); 142 let mut change = Change::new();
143 let (vfs, line_endings_map) = &mut *self.vfs.write(); 143 let (vfs, line_endings_map) = &mut *self.vfs.write();
144 let changed_files = vfs.take_changes(); 144 let changed_files = vfs.take_changes();
145 if changed_files.is_empty() { 145 if changed_files.is_empty() {
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 7ac1a30f6..e970abb7c 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -38,10 +38,22 @@ use crate::{
38 to_proto, LspError, Result, 38 to_proto, LspError, Result,
39}; 39};
40 40
41pub(crate) fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result<String> { 41pub(crate) fn handle_analyzer_status(
42 snap: GlobalStateSnapshot,
43 params: lsp_ext::AnalyzerStatusParams,
44) -> Result<String> {
42 let _p = profile::span("handle_analyzer_status"); 45 let _p = profile::span("handle_analyzer_status");
43 46
44 let mut buf = String::new(); 47 let mut buf = String::new();
48
49 let mut file_id = None;
50 if let Some(tdi) = params.text_document {
51 match from_proto::file_id(&snap, &tdi.uri) {
52 Ok(it) => file_id = Some(it),
53 Err(_) => format_to!(buf, "file {} not found in vfs", tdi.uri),
54 }
55 }
56
45 if snap.workspaces.is_empty() { 57 if snap.workspaces.is_empty() {
46 buf.push_str("no workspaces\n") 58 buf.push_str("no workspaces\n")
47 } else { 59 } else {
@@ -52,7 +64,10 @@ pub(crate) fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result
52 } 64 }
53 buf.push_str("\nanalysis:\n"); 65 buf.push_str("\nanalysis:\n");
54 buf.push_str( 66 buf.push_str(
55 &snap.analysis.status().unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()), 67 &snap
68 .analysis
69 .status(file_id)
70 .unwrap_or_else(|_| "Analysis retrieval was cancelled".to_owned()),
56 ); 71 );
57 format_to!(buf, "\n\nrequests:\n"); 72 format_to!(buf, "\n\nrequests:\n");
58 let requests = snap.latest_requests.read(); 73 let requests = snap.latest_requests.read();
@@ -476,6 +491,7 @@ pub(crate) fn handle_runnables(
476 } 491 }
477 492
478 // Add `cargo check` and `cargo test` for all targets of the whole package 493 // Add `cargo check` and `cargo test` for all targets of the whole package
494 let config = &snap.config.runnables;
479 match cargo_spec { 495 match cargo_spec {
480 Some(spec) => { 496 Some(spec) => {
481 for &cmd in ["check", "test"].iter() { 497 for &cmd in ["check", "test"].iter() {
@@ -485,12 +501,14 @@ pub(crate) fn handle_runnables(
485 kind: lsp_ext::RunnableKind::Cargo, 501 kind: lsp_ext::RunnableKind::Cargo,
486 args: lsp_ext::CargoRunnable { 502 args: lsp_ext::CargoRunnable {
487 workspace_root: Some(spec.workspace_root.clone().into()), 503 workspace_root: Some(spec.workspace_root.clone().into()),
504 override_cargo: config.override_cargo.clone(),
488 cargo_args: vec![ 505 cargo_args: vec![
489 cmd.to_string(), 506 cmd.to_string(),
490 "--package".to_string(), 507 "--package".to_string(),
491 spec.package.clone(), 508 spec.package.clone(),
492 "--all-targets".to_string(), 509 "--all-targets".to_string(),
493 ], 510 ],
511 cargo_extra_args: config.cargo_extra_args.clone(),
494 executable_args: Vec::new(), 512 executable_args: Vec::new(),
495 expect_test: None, 513 expect_test: None,
496 }, 514 },
@@ -504,7 +522,9 @@ pub(crate) fn handle_runnables(
504 kind: lsp_ext::RunnableKind::Cargo, 522 kind: lsp_ext::RunnableKind::Cargo,
505 args: lsp_ext::CargoRunnable { 523 args: lsp_ext::CargoRunnable {
506 workspace_root: None, 524 workspace_root: None,
525 override_cargo: config.override_cargo.clone(),
507 cargo_args: vec!["check".to_string(), "--workspace".to_string()], 526 cargo_args: vec!["check".to_string(), "--workspace".to_string()],
527 cargo_extra_args: config.cargo_extra_args.clone(),
508 executable_args: Vec::new(), 528 executable_args: Vec::new(),
509 expect_test: None, 529 expect_test: None,
510 }, 530 },
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index e1a28b1b4..fee0bb69c 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -11,11 +11,17 @@ use serde::{Deserialize, Serialize};
11pub enum AnalyzerStatus {} 11pub enum AnalyzerStatus {}
12 12
13impl Request for AnalyzerStatus { 13impl Request for AnalyzerStatus {
14 type Params = (); 14 type Params = AnalyzerStatusParams;
15 type Result = String; 15 type Result = String;
16 const METHOD: &'static str = "rust-analyzer/analyzerStatus"; 16 const METHOD: &'static str = "rust-analyzer/analyzerStatus";
17} 17}
18 18
19#[derive(Deserialize, Serialize, Debug)]
20#[serde(rename_all = "camelCase")]
21pub struct AnalyzerStatusParams {
22 pub text_document: Option<TextDocumentIdentifier>,
23}
24
19pub enum MemoryUsage {} 25pub enum MemoryUsage {}
20 26
21impl Request for MemoryUsage { 27impl Request for MemoryUsage {
@@ -165,10 +171,14 @@ pub enum RunnableKind {
165#[derive(Deserialize, Serialize, Debug)] 171#[derive(Deserialize, Serialize, Debug)]
166#[serde(rename_all = "camelCase")] 172#[serde(rename_all = "camelCase")]
167pub struct CargoRunnable { 173pub struct CargoRunnable {
174 // command to be executed instead of cargo
175 pub override_cargo: Option<String>,
168 #[serde(skip_serializing_if = "Option::is_none")] 176 #[serde(skip_serializing_if = "Option::is_none")]
169 pub workspace_root: Option<PathBuf>, 177 pub workspace_root: Option<PathBuf>,
170 // command, --package and --lib stuff 178 // command, --package and --lib stuff
171 pub cargo_args: Vec<String>, 179 pub cargo_args: Vec<String>,
180 // user-specified additional cargo args, like `--release`.
181 pub cargo_extra_args: Vec<String>,
172 // stuff after -- 182 // stuff after --
173 pub executable_args: Vec<String>, 183 pub executable_args: Vec<String>,
174 #[serde(skip_serializing_if = "Option::is_none")] 184 #[serde(skip_serializing_if = "Option::is_none")]
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 06ab9d508..c2d0ac791 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -189,19 +189,16 @@ impl GlobalState {
189 } 189 }
190 lsp_server::Message::Response(resp) => self.complete_request(resp), 190 lsp_server::Message::Response(resp) => self.complete_request(resp),
191 }, 191 },
192 Event::Task(task) => { 192 Event::Task(task) => match task {
193 match task { 193 Task::Response(response) => self.respond(response),
194 Task::Response(response) => self.respond(response), 194 Task::Diagnostics(diagnostics_per_file) => {
195 Task::Diagnostics(diagnostics_per_file) => { 195 for (file_id, diagnostics) in diagnostics_per_file {
196 for (file_id, diagnostics) in diagnostics_per_file { 196 self.diagnostics.set_native_diagnostics(file_id, diagnostics)
197 self.diagnostics.set_native_diagnostics(file_id, diagnostics)
198 }
199 } 197 }
200 Task::Workspaces(workspaces) => self.switch_workspaces(workspaces),
201 Task::Unit => (),
202 } 198 }
203 self.analysis_host.maybe_collect_garbage(); 199 Task::Workspaces(workspaces) => self.switch_workspaces(workspaces),
204 } 200 Task::Unit => (),
201 },
205 Event::Vfs(mut task) => { 202 Event::Vfs(mut task) => {
206 let _p = profile::span("GlobalState::handle_event/vfs"); 203 let _p = profile::span("GlobalState::handle_event/vfs");
207 loop { 204 loop {
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index de0dbcad4..f7215f129 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -3,7 +3,7 @@ use std::{mem, sync::Arc};
3 3
4use base_db::{CrateGraph, SourceRoot, VfsPath}; 4use base_db::{CrateGraph, SourceRoot, VfsPath};
5use flycheck::{FlycheckConfig, FlycheckHandle}; 5use flycheck::{FlycheckConfig, FlycheckHandle};
6use ide::AnalysisChange; 6use ide::Change;
7use project_model::{ProcMacroClient, ProjectWorkspace}; 7use project_model::{ProcMacroClient, ProjectWorkspace};
8use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind}; 8use vfs::{file_set::FileSetConfig, AbsPath, AbsPathBuf, ChangeKind};
9 9
@@ -171,7 +171,7 @@ impl GlobalState {
171 ); 171 );
172 } 172 }
173 173
174 let mut change = AnalysisChange::new(); 174 let mut change = Change::new();
175 175
176 let project_folders = ProjectFolders::new(&workspaces); 176 let project_folders = ProjectFolders::new(&workspaces);
177 177
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 59e780b7d..aeacde0f7 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -740,6 +740,7 @@ pub(crate) fn runnable(
740 file_id: FileId, 740 file_id: FileId,
741 runnable: Runnable, 741 runnable: Runnable,
742) -> Result<lsp_ext::Runnable> { 742) -> Result<lsp_ext::Runnable> {
743 let config = &snap.config.runnables;
743 let spec = CargoTargetSpec::for_file(snap, file_id)?; 744 let spec = CargoTargetSpec::for_file(snap, file_id)?;
744 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); 745 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
745 let target = spec.as_ref().map(|s| s.target.clone()); 746 let target = spec.as_ref().map(|s| s.target.clone());
@@ -754,7 +755,9 @@ pub(crate) fn runnable(
754 kind: lsp_ext::RunnableKind::Cargo, 755 kind: lsp_ext::RunnableKind::Cargo,
755 args: lsp_ext::CargoRunnable { 756 args: lsp_ext::CargoRunnable {
756 workspace_root: workspace_root.map(|it| it.into()), 757 workspace_root: workspace_root.map(|it| it.into()),
758 override_cargo: config.override_cargo.clone(),
757 cargo_args, 759 cargo_args,
760 cargo_extra_args: config.cargo_extra_args.clone(),
758 executable_args, 761 executable_args,
759 expect_test: None, 762 expect_test: None,
760 }, 763 },
diff --git a/crates/rust-analyzer/tests/rust-analyzer/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs
index 06726f957..e51eb2626 100644
--- a/crates/rust-analyzer/tests/rust-analyzer/main.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs
@@ -107,6 +107,8 @@ fn main() {}
107 "args": { 107 "args": {
108 "cargoArgs": ["test", "--package", "foo", "--test", "spam"], 108 "cargoArgs": ["test", "--package", "foo", "--test", "spam"],
109 "executableArgs": ["test_eggs", "--exact", "--nocapture"], 109 "executableArgs": ["test_eggs", "--exact", "--nocapture"],
110 "cargoExtraArgs": [],
111 "overrideCargo": null,
110 "workspaceRoot": server.path().join("foo") 112 "workspaceRoot": server.path().join("foo")
111 }, 113 },
112 "kind": "cargo", 114 "kind": "cargo",
@@ -127,6 +129,8 @@ fn main() {}
127 "args": { 129 "args": {
128 "cargoArgs": ["check", "--package", "foo", "--all-targets"], 130 "cargoArgs": ["check", "--package", "foo", "--all-targets"],
129 "executableArgs": [], 131 "executableArgs": [],
132 "cargoExtraArgs": [],
133 "overrideCargo": null,
130 "workspaceRoot": server.path().join("foo") 134 "workspaceRoot": server.path().join("foo")
131 }, 135 },
132 "kind": "cargo", 136 "kind": "cargo",
@@ -136,6 +140,8 @@ fn main() {}
136 "args": { 140 "args": {
137 "cargoArgs": ["test", "--package", "foo", "--all-targets"], 141 "cargoArgs": ["test", "--package", "foo", "--all-targets"],
138 "executableArgs": [], 142 "executableArgs": [],
143 "cargoExtraArgs": [],
144 "overrideCargo": null,
139 "workspaceRoot": server.path().join("foo") 145 "workspaceRoot": server.path().join("foo")
140 }, 146 },
141 "kind": "cargo", 147 "kind": "cargo",
diff --git a/crates/ssr/src/resolving.rs b/crates/ssr/src/resolving.rs
index 5d2cbec47..347cc4aad 100644
--- a/crates/ssr/src/resolving.rs
+++ b/crates/ssr/src/resolving.rs
@@ -205,7 +205,7 @@ impl<'db> ResolutionScope<'db> {
205 205
206 /// Returns the function in which SSR was invoked, if any. 206 /// Returns the function in which SSR was invoked, if any.
207 pub(crate) fn current_function(&self) -> Option<SyntaxNode> { 207 pub(crate) fn current_function(&self) -> Option<SyntaxNode> {
208 self.node.ancestors().find(|node| node.kind() == SyntaxKind::FN).map(|node| node.clone()) 208 self.node.ancestors().find(|node| node.kind() == SyntaxKind::FN)
209 } 209 }
210 210
211 fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> { 211 fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> {
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 273b0f55b..011935cad 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -5,6 +5,7 @@ use std::{
5}; 5};
6 6
7mod macros; 7mod macros;
8pub mod panic_context;
8 9
9#[inline(always)] 10#[inline(always)]
10pub fn is_ci() -> bool { 11pub fn is_ci() -> bool {
diff --git a/crates/stdx/src/panic_context.rs b/crates/stdx/src/panic_context.rs
new file mode 100644
index 000000000..fd232e0cc
--- /dev/null
+++ b/crates/stdx/src/panic_context.rs
@@ -0,0 +1,49 @@
1//! A micro-crate to enhance panic messages with context info.
2//!
3//! FIXME: upstream to https://github.com/kriomant/panic-context ?
4
5use std::{cell::RefCell, panic, sync::Once};
6
7pub fn enter(context: String) -> impl Drop {
8 static ONCE: Once = Once::new();
9 ONCE.call_once(PanicContext::init);
10
11 with_ctx(|ctx| ctx.push(context));
12 PanicContext { _priv: () }
13}
14
15#[must_use]
16struct PanicContext {
17 _priv: (),
18}
19
20impl PanicContext {
21 fn init() {
22 let default_hook = panic::take_hook();
23 let hook = move |panic_info: &panic::PanicInfo<'_>| {
24 with_ctx(|ctx| {
25 if !ctx.is_empty() {
26 eprintln!("Panic context:");
27 for frame in ctx.iter() {
28 eprintln!("> {}\n", frame)
29 }
30 }
31 default_hook(panic_info)
32 })
33 };
34 panic::set_hook(Box::new(hook))
35 }
36}
37
38impl Drop for PanicContext {
39 fn drop(&mut self) {
40 with_ctx(|ctx| assert!(ctx.pop().is_some()))
41 }
42}
43
44fn with_ctx(f: impl FnOnce(&mut Vec<String>)) {
45 thread_local! {
46 static CTX: RefCell<Vec<String>> = RefCell::new(Vec::new());
47 }
48 CTX.with(|ctx| f(&mut *ctx.borrow_mut()))
49}
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index af61bb658..0b15f10e9 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
13[dependencies] 13[dependencies]
14itertools = "0.9.0" 14itertools = "0.9.0"
15rowan = "0.10.0" 15rowan = "0.10.0"
16rustc_lexer = { version = "673.0.0", package = "rustc-ap-rustc_lexer" } 16rustc_lexer = { version = "681.0.0", package = "rustc-ap-rustc_lexer" }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18arrayvec = "0.5.1" 18arrayvec = "0.5.1"
19once_cell = "1.3.1" 19once_cell = "1.3.1"
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index dda0a0319..77233ab31 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -159,7 +159,7 @@ impl ast::AssocItemList {
159 let whitespace = 159 let whitespace =
160 last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?; 160 last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
161 let text = whitespace.syntax().text(); 161 let text = whitespace.syntax().text();
162 let newline = text.rfind("\n")?; 162 let newline = text.rfind('\n')?;
163 let keep = tokens::WsBuilder::new(&text[newline..]); 163 let keep = tokens::WsBuilder::new(&text[newline..]);
164 Some(self.replace_children( 164 Some(self.replace_children(
165 first_token_after_items..=last_token_before_curly, 165 first_token_after_items..=last_token_before_curly,
diff --git a/crates/syntax/src/parsing/lexer.rs b/crates/syntax/src/parsing/lexer.rs
index fa3be1016..f1202113b 100644
--- a/crates/syntax/src/parsing/lexer.rs
+++ b/crates/syntax/src/parsing/lexer.rs
@@ -120,10 +120,10 @@ fn rustc_token_kind_to_syntax_kind(
120 120
121 let syntax_kind = { 121 let syntax_kind = {
122 match rustc_token_kind { 122 match rustc_token_kind {
123 rustc_lexer::TokenKind::LineComment => COMMENT, 123 rustc_lexer::TokenKind::LineComment { doc_style: _ } => COMMENT,
124 124
125 rustc_lexer::TokenKind::BlockComment { terminated: true } => COMMENT, 125 rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated: true } => COMMENT,
126 rustc_lexer::TokenKind::BlockComment { terminated: false } => { 126 rustc_lexer::TokenKind::BlockComment { doc_style: _, terminated: false } => {
127 return ( 127 return (
128 COMMENT, 128 COMMENT,
129 Some("Missing trailing `*/` symbols to terminate the block comment"), 129 Some("Missing trailing `*/` symbols to terminate the block comment"),
@@ -164,7 +164,7 @@ fn rustc_token_kind_to_syntax_kind(
164 rustc_lexer::TokenKind::Colon => T![:], 164 rustc_lexer::TokenKind::Colon => T![:],
165 rustc_lexer::TokenKind::Dollar => T![$], 165 rustc_lexer::TokenKind::Dollar => T![$],
166 rustc_lexer::TokenKind::Eq => T![=], 166 rustc_lexer::TokenKind::Eq => T![=],
167 rustc_lexer::TokenKind::Not => T![!], 167 rustc_lexer::TokenKind::Bang => T![!],
168 rustc_lexer::TokenKind::Lt => T![<], 168 rustc_lexer::TokenKind::Lt => T![<],
169 rustc_lexer::TokenKind::Gt => T![>], 169 rustc_lexer::TokenKind::Gt => T![>],
170 rustc_lexer::TokenKind::Minus => T![-], 170 rustc_lexer::TokenKind::Minus => T![-],
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 2e3133449..f1160bb1c 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -390,7 +390,14 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
390 390
391**Method:** `rust-analyzer/analyzerStatus` 391**Method:** `rust-analyzer/analyzerStatus`
392 392
393**Request:** `null` 393**Request:**
394
395```typescript
396interface AnalyzerStatusParams {
397 /// If specified, show dependencies of the current file.
398 textDocument?: TextDocumentIdentifier;
399}
400```
394 401
395**Response:** `string` 402**Response:** `string`
396 403
diff --git a/docs/dev/style.md b/docs/dev/style.md
index bcd86fd3f..fb407afcd 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -197,7 +197,7 @@ fn frobnicate(walrus: Option<Walrus>) {
197} 197}
198``` 198```
199 199
200Avoid preconditions that spawn function boundaries: 200Avoid preconditions that span across function boundaries:
201 201
202 202
203```rust 203```rust
@@ -218,9 +218,8 @@ fn foo() {
218} 218}
219 219
220// Not as good 220// Not as good
221fn is_string_literal(s: &str) -> Option<&str> { 221fn is_string_literal(s: &str) -> bool {
222 s.starts_with('"') && s.ends_with('"') 222 s.starts_with('"') && s.ends_with('"')
223 Some()
224} 223}
225 224
226fn foo() { 225fn foo() {
@@ -231,8 +230,8 @@ fn foo() {
231} 230}
232``` 231```
233 232
234In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and utilized in `foo`. 233In the "Not as good" version, the precondition that `1` is a valid char boundary is checked in `is_string_literal` and used in `foo`.
235In the "Good" version, precondition check and usage are checked in the same block, and then encoded in the types. 234In the "Good" version, the precondition check and usage are checked in the same block, and then encoded in the types.
236 235
237# Early Returns 236# Early Returns
238 237
@@ -372,3 +371,13 @@ After you are happy with the state of the code, please use [interactive rebase](
372 371
373Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors). 372Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors).
374Such messages create a lot of duplicate notification traffic during rebases. 373Such messages create a lot of duplicate notification traffic during rebases.
374
375# Clippy
376
377We don't enforce Clippy.
378A number of default lints have high false positive rate.
379Selectively patching false-positives with `allow(clippy)` is considered worse than not using Clippy at all.
380There's `cargo xtask lint` command which runs a subset of low-FPR lints.
381Careful tweaking of `xtask lint` is welcome.
382See also [rust-lang/clippy#5537](https://github.com/rust-lang/rust-clippy/issues/5537).
383Of course, applying Clippy suggestions is welcome as long as they indeed improve the code.
diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc
index 7d85b36cb..c1a778852 100644
--- a/docs/user/manual.adoc
+++ b/docs/user/manual.adoc
@@ -113,8 +113,8 @@ Note that installing via `xtask install` does not work for VS Code Remote, inste
113 113
114Here are some useful self-diagnostic commands: 114Here are some useful self-diagnostic commands:
115 115
116* **Rust Analyzer: Show RA Version** shows the version of `rust-analyzer` binary 116* **Rust Analyzer: Show RA Version** shows the version of `rust-analyzer` binary.
117* **Rust Analyzer: Status** prints some statistics about the server, like the few latest LSP requests 117* **Rust Analyzer: Status** prints some statistics about the server, and dependency information for the current file.
118* To enable server-side logging, run with `env RA_LOG=info` and see `Output > Rust Analyzer Language Server` in VS Code's panel. 118* To enable server-side logging, run with `env RA_LOG=info` and see `Output > Rust Analyzer Language Server` in VS Code's panel.
119* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Rust Analyzer Language Server Trace` in the panel. 119* To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Rust Analyzer Language Server Trace` in the panel.
120* To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open `Output > Rust Analyzer Client` in the panel. 120* To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open `Output > Rust Analyzer Client` in the panel.
diff --git a/editors/code/package.json b/editors/code/package.json
index bdd8a0c29..cc2ac3bd2 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -651,6 +651,22 @@
651 ], 651 ],
652 "default": "full", 652 "default": "full",
653 "description": "The strategy to use when inserting new imports or merging imports." 653 "description": "The strategy to use when inserting new imports or merging imports."
654 },
655 "rust-analyzer.runnables.overrideCargo": {
656 "type": [
657 "null",
658 "string"
659 ],
660 "default": null,
661 "description": "Command to be executed instead of 'cargo' for runnables."
662 },
663 "rust-analyzer.runnables.cargoExtraArgs": {
664 "type": "array",
665 "items": {
666 "type": "string"
667 },
668 "default": [],
669 "description": "Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be '--release'"
654 } 670 }
655 } 671 }
656 }, 672 },
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index e9581a9b5..1a90f1b7d 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -21,7 +21,12 @@ export function analyzerStatus(ctx: Ctx): Cmd {
21 provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> { 21 provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
22 if (!vscode.window.activeTextEditor) return ''; 22 if (!vscode.window.activeTextEditor) return '';
23 23
24 return ctx.client.sendRequest(ra.analyzerStatus); 24 const params: ra.AnalyzerStatusParams = {};
25 const doc = ctx.activeRustEditor?.document;
26 if (doc != null) {
27 params.textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(doc);
28 }
29 return ctx.client.sendRequest(ra.analyzerStatus, params);
25 } 30 }
26 31
27 get onDidChange(): vscode.Event<vscode.Uri> { 32 get onDidChange(): vscode.Event<vscode.Uri> {
@@ -94,7 +99,7 @@ export function matchingBrace(ctx: Ctx): Cmd {
94 if (!editor || !client) return; 99 if (!editor || !client) return;
95 100
96 const response = await client.sendRequest(ra.matchingBrace, { 101 const response = await client.sendRequest(ra.matchingBrace, {
97 textDocument: { uri: editor.document.uri.toString() }, 102 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
98 positions: editor.selections.map(s => 103 positions: editor.selections.map(s =>
99 client.code2ProtocolConverter.asPosition(s.active), 104 client.code2ProtocolConverter.asPosition(s.active),
100 ), 105 ),
@@ -118,7 +123,7 @@ export function joinLines(ctx: Ctx): Cmd {
118 123
119 const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, { 124 const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
120 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), 125 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
121 textDocument: { uri: editor.document.uri.toString() }, 126 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
122 }); 127 });
123 editor.edit((builder) => { 128 editor.edit((builder) => {
124 client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => { 129 client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => {
@@ -136,7 +141,7 @@ export function onEnter(ctx: Ctx): Cmd {
136 if (!editor || !client) return false; 141 if (!editor || !client) return false;
137 142
138 const lcEdits = await client.sendRequest(ra.onEnter, { 143 const lcEdits = await client.sendRequest(ra.onEnter, {
139 textDocument: { uri: editor.document.uri.toString() }, 144 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
140 position: client.code2ProtocolConverter.asPosition( 145 position: client.code2ProtocolConverter.asPosition(
141 editor.selection.active, 146 editor.selection.active,
142 ), 147 ),
@@ -165,7 +170,7 @@ export function parentModule(ctx: Ctx): Cmd {
165 if (!editor || !client) return; 170 if (!editor || !client) return;
166 171
167 const response = await client.sendRequest(ra.parentModule, { 172 const response = await client.sendRequest(ra.parentModule, {
168 textDocument: { uri: editor.document.uri.toString() }, 173 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
169 position: client.code2ProtocolConverter.asPosition( 174 position: client.code2ProtocolConverter.asPosition(
170 editor.selection.active, 175 editor.selection.active,
171 ), 176 ),
@@ -191,7 +196,7 @@ export function ssr(ctx: Ctx): Cmd {
191 196
192 const position = editor.selection.active; 197 const position = editor.selection.active;
193 const selections = editor.selections; 198 const selections = editor.selections;
194 const textDocument = { uri: editor.document.uri.toString() }; 199 const textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document);
195 200
196 const options: vscode.InputBoxOptions = { 201 const options: vscode.InputBoxOptions = {
197 value: "() ==>> ()", 202 value: "() ==>> ()",
@@ -339,7 +344,7 @@ export function expandMacro(ctx: Ctx): Cmd {
339 const position = editor.selection.active; 344 const position = editor.selection.active;
340 345
341 const expanded = await client.sendRequest(ra.expandMacro, { 346 const expanded = await client.sendRequest(ra.expandMacro, {
342 textDocument: { uri: editor.document.uri.toString() }, 347 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
343 position, 348 position,
344 }); 349 });
345 350
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index d167041c4..f286b68a6 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -4,7 +4,10 @@
4 4
5import * as lc from "vscode-languageclient"; 5import * as lc from "vscode-languageclient";
6 6
7export const analyzerStatus = new lc.RequestType0<string, void>("rust-analyzer/analyzerStatus"); 7export interface AnalyzerStatusParams {
8 textDocument?: lc.TextDocumentIdentifier;
9}
10export const analyzerStatus = new lc.RequestType<AnalyzerStatusParams, string, void>("rust-analyzer/analyzerStatus");
8export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage"); 11export const memoryUsage = new lc.RequestType0<string, void>("rust-analyzer/memoryUsage");
9 12
10export type Status = "loading" | "ready" | "invalid" | "needsReload"; 13export type Status = "loading" | "ready" | "invalid" | "needsReload";
@@ -66,8 +69,10 @@ export interface Runnable {
66 args: { 69 args: {
67 workspaceRoot?: string; 70 workspaceRoot?: string;
68 cargoArgs: string[]; 71 cargoArgs: string[];
72 cargoExtraArgs: string[];
69 executableArgs: string[]; 73 executableArgs: string[];
70 expectTest?: boolean; 74 expectTest?: boolean;
75 overrideCargo?: string;
71 }; 76 };
72} 77}
73export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("experimental/runnables"); 78export const runnables = new lc.RequestType<RunnablesParams, Runnable[], void>("experimental/runnables");
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index de68f27ae..459b7f250 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -129,6 +129,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
129 } 129 }
130 130
131 const args = [...runnable.args.cargoArgs]; // should be a copy! 131 const args = [...runnable.args.cargoArgs]; // should be a copy!
132 args.push(...runnable.args.cargoExtraArgs); // Append user-specified cargo options.
132 if (runnable.args.executableArgs.length > 0) { 133 if (runnable.args.executableArgs.length > 0) {
133 args.push('--', ...runnable.args.executableArgs); 134 args.push('--', ...runnable.args.executableArgs);
134 } 135 }
@@ -139,6 +140,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
139 args: args.slice(1), 140 args: args.slice(1),
140 cwd: runnable.args.workspaceRoot || ".", 141 cwd: runnable.args.workspaceRoot || ".",
141 env: prepareEnv(runnable, config.runnableEnv), 142 env: prepareEnv(runnable, config.runnableEnv),
143 overrideCargo: runnable.args.overrideCargo,
142 }; 144 };
143 145
144 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() 146 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts
index 14abbd5b7..a3ff15102 100644
--- a/editors/code/src/tasks.ts
+++ b/editors/code/src/tasks.ts
@@ -13,6 +13,7 @@ export interface CargoTaskDefinition extends vscode.TaskDefinition {
13 args?: string[]; 13 args?: string[];
14 cwd?: string; 14 cwd?: string;
15 env?: { [key: string]: string }; 15 env?: { [key: string]: string };
16 overrideCargo?: string;
16} 17}
17 18
18class CargoTaskProvider implements vscode.TaskProvider { 19class CargoTaskProvider implements vscode.TaskProvider {
@@ -98,7 +99,14 @@ export async function buildCargoTask(
98 } 99 }
99 100
100 if (!exec) { 101 if (!exec) {
101 exec = new vscode.ShellExecution(toolchain.cargoPath(), args, definition); 102 // Check whether we must use a user-defined substitute for cargo.
103 const cargoCommand = definition.overrideCargo ? definition.overrideCargo : toolchain.cargoPath();
104
105 // Prepare the whole command as one line. It is required if user has provided override command which contains spaces,
106 // for example "wrapper cargo". Without manual preparation the overridden command will be quoted and fail to execute.
107 const fullCommand = [cargoCommand, ...args].join(" ");
108
109 exec = new vscode.ShellExecution(fullCommand, definition);
102 } 110 }
103 111
104 return new vscode.Task( 112 return new vscode.Task(
diff --git a/editors/code/tests/unit/runnable_env.test.ts b/editors/code/tests/unit/runnable_env.test.ts
index f2f53e91a..c5600cf64 100644
--- a/editors/code/tests/unit/runnable_env.test.ts
+++ b/editors/code/tests/unit/runnable_env.test.ts
@@ -9,7 +9,8 @@ function makeRunnable(label: string): ra.Runnable {
9 kind: "cargo", 9 kind: "cargo",
10 args: { 10 args: {
11 cargoArgs: [], 11 cargoArgs: [],
12 executableArgs: [] 12 executableArgs: [],
13 cargoExtraArgs: []
13 } 14 }
14 }; 15 };
15} 16}
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs
index 3f0013e82..341e67c73 100644
--- a/xtask/src/codegen/gen_feature_docs.rs
+++ b/xtask/src/codegen/gen_feature_docs.rs
@@ -38,7 +38,9 @@ impl Feature {
38 38
39 for block in comment_blocks { 39 for block in comment_blocks {
40 let id = block.id; 40 let id = block.id;
41 assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id); 41 if let Err(msg) = is_valid_feature_name(&id) {
42 panic!("invalid feature name: {:?}:\n {}", id, msg)
43 }
42 let doc = block.contents.join("\n"); 44 let doc = block.contents.join("\n");
43 let location = Location::new(path.clone(), block.line); 45 let location = Location::new(path.clone(), block.line);
44 acc.push(Feature { id, location, doc }) 46 acc.push(Feature { id, location, doc })
@@ -49,7 +51,7 @@ impl Feature {
49 } 51 }
50} 52}
51 53
52fn is_valid_feature_name(feature: &str) -> bool { 54fn is_valid_feature_name(feature: &str) -> Result<(), String> {
53 'word: for word in feature.split_whitespace() { 55 'word: for word in feature.split_whitespace() {
54 for &short in ["to", "and"].iter() { 56 for &short in ["to", "and"].iter() {
55 if word == short { 57 if word == short {
@@ -58,14 +60,14 @@ fn is_valid_feature_name(feature: &str) -> bool {
58 } 60 }
59 for &short in ["To", "And"].iter() { 61 for &short in ["To", "And"].iter() {
60 if word == short { 62 if word == short {
61 return false; 63 return Err(format!("Don't capitalize {:?}", word));
62 } 64 }
63 } 65 }
64 if !word.starts_with(char::is_uppercase) { 66 if !word.starts_with(char::is_uppercase) {
65 return false; 67 return Err(format!("Capitalize {:?}", word));
66 } 68 }
67 } 69 }
68 true 70 Ok(())
69} 71}
70 72
71impl fmt::Display for Feature { 73impl fmt::Display for Feature {