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,