aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yaml6
-rw-r--r--Cargo.lock46
-rw-r--r--crates/assists/src/ast_transform.rs32
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs63
-rw-r--r--crates/assists/src/handlers/auto_import.rs2
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs20
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs7
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs50
-rw-r--r--crates/assists/src/lib.rs37
-rw-r--r--crates/assists/src/tests/generated.rs3
-rw-r--r--crates/assists/src/utils/insert_use.rs5
-rw-r--r--crates/hir/src/code_model.rs9
-rw-r--r--crates/hir/src/lib.rs7
-rw-r--r--crates/hir/src/semantics.rs37
-rw-r--r--crates/hir/src/source_analyzer.rs6
-rw-r--r--crates/hir_def/src/diagnostics.rs3
-rw-r--r--crates/hir_def/src/path.rs6
-rw-r--r--crates/hir_expand/src/diagnostics.rs1
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/hir_ty/src/diagnostics.rs25
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs4
-rw-r--r--crates/hir_ty/src/diagnostics/match_check.rs17
-rw-r--r--crates/hir_ty/src/traits.rs4
-rw-r--r--crates/ide/src/diagnostics.rs82
-rw-r--r--crates/ide/src/inlay_hints.rs2
-rw-r--r--crates/ide/src/lib.rs10
-rw-r--r--crates/proc_macro_api/src/lib.rs6
-rw-r--r--crates/proc_macro_srv/Cargo.toml4
-rw-r--r--crates/proc_macro_srv/src/tests/mod.rs15
-rw-r--r--crates/proc_macro_srv/src/tests/utils.rs2
-rw-r--r--crates/proc_macro_test/Cargo.toml10
-rw-r--r--crates/proc_macro_test/src/lib.rs18
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs2
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs2
-rw-r--r--crates/rust-analyzer/src/config.rs24
-rw-r--r--crates/rust-analyzer/src/handlers.rs31
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs7
-rw-r--r--crates/rust-analyzer/src/reload.rs5
-rw-r--r--crates/rust-analyzer/src/to_proto.rs8
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/main.rs (renamed from crates/rust-analyzer/tests/heavy_tests/main.rs)10
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/support.rs (renamed from crates/rust-analyzer/tests/heavy_tests/support.rs)0
-rw-r--r--crates/rust-analyzer/tests/rust-analyzer/testdir.rs (renamed from crates/rust-analyzer/tests/heavy_tests/testdir.rs)0
-rw-r--r--crates/ssr/Cargo.toml1
-rw-r--r--crates/ssr/src/lib.rs5
-rw-r--r--crates/ssr/src/matching.rs95
-rw-r--r--crates/ssr/src/parsing.rs24
-rw-r--r--crates/ssr/src/replacing.rs50
-rw-r--r--crates/ssr/src/tests.rs109
-rw-r--r--crates/stdx/src/lib.rs2
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/ast/edit.rs29
-rw-r--r--docs/dev/lsp-extensions.md8
-rw-r--r--docs/dev/style.md29
-rw-r--r--editors/code/.eslintignore3
-rw-r--r--editors/code/package.json9
-rw-r--r--editors/code/src/ctx.ts2
-rw-r--r--editors/code/src/lsp_ext.ts5
-rw-r--r--xtask/src/codegen.rs18
-rw-r--r--xtask/src/codegen/gen_assists_docs.rs6
-rw-r--r--xtask/src/codegen/gen_feature_docs.rs4
-rw-r--r--xtask/src/codegen/gen_syntax.rs8
-rw-r--r--xtask/src/lib.rs56
-rw-r--r--xtask/src/main.rs5
-rw-r--r--xtask/src/pre_cache.rs80
-rw-r--r--xtask/tests/tidy.rs2
65 files changed, 859 insertions, 327 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 2deb009ce..fb077e28d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -25,7 +25,7 @@ jobs:
25 strategy: 25 strategy:
26 fail-fast: false 26 fail-fast: false
27 matrix: 27 matrix:
28 os: [ubuntu-latest, windows-latest] #, macos-latest] 28 os: [ubuntu-latest, windows-latest, macos-latest]
29 29
30 steps: 30 steps:
31 - name: Checkout repository 31 - name: Checkout repository
@@ -70,10 +70,6 @@ jobs:
70 - name: Prepare cache 70 - name: Prepare cache
71 run: cargo xtask pre-cache 71 run: cargo xtask pre-cache
72 72
73 - name: Prepare cache 2
74 if: matrix.os == 'windows-latest'
75 run: Remove-Item ./target/debug/xtask.exe, ./target/debug/deps/xtask.exe
76
77 # Weird targets to catch non-portable code 73 # Weird targets to catch non-portable code
78 rust-cross: 74 rust-cross:
79 name: Rust Cross 75 name: Rust Cross
diff --git a/Cargo.lock b/Cargo.lock
index 2386c8f3a..ffa385106 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -162,9 +162,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
162 162
163[[package]] 163[[package]]
164name = "chalk-derive" 164name = "chalk-derive"
165version = "0.21.0" 165version = "0.23.0"
166source = "registry+https://github.com/rust-lang/crates.io-index" 166source = "registry+https://github.com/rust-lang/crates.io-index"
167checksum = "c1df0dbb57d74b4acd20f20fa66ab2acd09776b79eaeb9d8f947b2f3e01c40bf" 167checksum = "c3cb438e961fd7f1183dc5e0bdcfd09253bf9b90592cf665d1ce6787d8a4908f"
168dependencies = [ 168dependencies = [
169 "proc-macro2", 169 "proc-macro2",
170 "quote", 170 "quote",
@@ -174,9 +174,9 @@ dependencies = [
174 174
175[[package]] 175[[package]]
176name = "chalk-ir" 176name = "chalk-ir"
177version = "0.21.0" 177version = "0.23.0"
178source = "registry+https://github.com/rust-lang/crates.io-index" 178source = "registry+https://github.com/rust-lang/crates.io-index"
179checksum = "44361a25dbdb1dc428f56ad7a3c21ba9ca12f3225c26a47919ff6fcb10a583d4" 179checksum = "bb332abfcb015b148c6fbab39b1d13282745b0f7f312019dd8e138f5f3f0855d"
180dependencies = [ 180dependencies = [
181 "chalk-derive", 181 "chalk-derive",
182 "lazy_static", 182 "lazy_static",
@@ -184,9 +184,9 @@ dependencies = [
184 184
185[[package]] 185[[package]]
186name = "chalk-recursive" 186name = "chalk-recursive"
187version = "0.21.0" 187version = "0.23.0"
188source = "registry+https://github.com/rust-lang/crates.io-index" 188source = "registry+https://github.com/rust-lang/crates.io-index"
189checksum = "dd89556b98de156d5eaf21077d297cd2198628f10f2df140798ea3a5dd84bc86" 189checksum = "e7c7673f10c5fa1acf7fa07d4f4c5917cbcf161ed3a952d14530c79950de32d2"
190dependencies = [ 190dependencies = [
191 "chalk-derive", 191 "chalk-derive",
192 "chalk-ir", 192 "chalk-ir",
@@ -197,9 +197,9 @@ dependencies = [
197 197
198[[package]] 198[[package]]
199name = "chalk-solve" 199name = "chalk-solve"
200version = "0.21.0" 200version = "0.23.0"
201source = "registry+https://github.com/rust-lang/crates.io-index" 201source = "registry+https://github.com/rust-lang/crates.io-index"
202checksum = "a886da37a0dc457057d86f78f026f7a09c6d8088aa13f4f4127fdb8dc80119a3" 202checksum = "802de4eff72e5a5d2828e6c07224c74d66949dc6308aff025d0ae2871a11b4eb"
203dependencies = [ 203dependencies = [
204 "chalk-derive", 204 "chalk-derive",
205 "chalk-ir", 205 "chalk-ir",
@@ -214,9 +214,9 @@ dependencies = [
214 214
215[[package]] 215[[package]]
216name = "chrono" 216name = "chrono"
217version = "0.4.13" 217version = "0.4.15"
218source = "registry+https://github.com/rust-lang/crates.io-index" 218source = "registry+https://github.com/rust-lang/crates.io-index"
219checksum = "c74d84029116787153e02106bf53e66828452a4b325cc8652b788b5967c0a0b6" 219checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b"
220dependencies = [ 220dependencies = [
221 "num-integer", 221 "num-integer",
222 "num-traits", 222 "num-traits",
@@ -765,9 +765,9 @@ dependencies = [
765 765
766[[package]] 766[[package]]
767name = "lsp-server" 767name = "lsp-server"
768version = "0.3.3" 768version = "0.3.4"
769source = "registry+https://github.com/rust-lang/crates.io-index" 769source = "registry+https://github.com/rust-lang/crates.io-index"
770checksum = "53b4ace8ebe5d2aff3687ce0ed507f6020d6a47a7de2b0d3d664ea237ffb0c62" 770checksum = "87fce8851309a325974ec76efe7c9d954d152c9ff4fded6520eb3c96d0aa3a96"
771dependencies = [ 771dependencies = [
772 "crossbeam-channel", 772 "crossbeam-channel",
773 "log", 773 "log",
@@ -971,9 +971,9 @@ checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
971 971
972[[package]] 972[[package]]
973name = "once_cell" 973name = "once_cell"
974version = "1.4.0" 974version = "1.4.1"
975source = "registry+https://github.com/rust-lang/crates.io-index" 975source = "registry+https://github.com/rust-lang/crates.io-index"
976checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" 976checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"
977 977
978[[package]] 978[[package]]
979name = "oorandom" 979name = "oorandom"
@@ -1036,9 +1036,9 @@ dependencies = [
1036 1036
1037[[package]] 1037[[package]]
1038name = "perf-event-open-sys" 1038name = "perf-event-open-sys"
1039version = "0.3.2" 1039version = "0.3.3"
1040source = "registry+https://github.com/rust-lang/crates.io-index" 1040source = "registry+https://github.com/rust-lang/crates.io-index"
1041checksum = "83e7183862f36d10263d0a1ccaef50fef734ade948bf026afd1bd97355c78273" 1041checksum = "d9ebe2b9ef0cb884ef778c5a533144e348e9839a9fcf67f3d24e1890ac9088d6"
1042dependencies = [ 1042dependencies = [
1043 "libc", 1043 "libc",
1044] 1044]
@@ -1097,6 +1097,7 @@ dependencies = [
1097 "mbe", 1097 "mbe",
1098 "memmap", 1098 "memmap",
1099 "proc_macro_api", 1099 "proc_macro_api",
1100 "proc_macro_test",
1100 "serde_derive", 1101 "serde_derive",
1101 "test_utils", 1102 "test_utils",
1102 "toolchain", 1103 "toolchain",
@@ -1104,6 +1105,10 @@ dependencies = [
1104] 1105]
1105 1106
1106[[package]] 1107[[package]]
1108name = "proc_macro_test"
1109version = "0.0.0"
1110
1111[[package]]
1107name = "profile" 1112name = "profile"
1108version = "0.0.0" 1113version = "0.0.0"
1109dependencies = [ 1114dependencies = [
@@ -1259,9 +1264,9 @@ dependencies = [
1259 1264
1260[[package]] 1265[[package]]
1261name = "rustc-ap-rustc_lexer" 1266name = "rustc-ap-rustc_lexer"
1262version = "671.0.0" 1267version = "673.0.0"
1263source = "registry+https://github.com/rust-lang/crates.io-index" 1268source = "registry+https://github.com/rust-lang/crates.io-index"
1264checksum = "22e1221f3bfa2943c942cf8da319ab2346887f8757778c29c7f1822cd27b521f" 1269checksum = "f6b71fa1285bdefe5fb61e59b63d6cc246abf337f4acafdd620d721bc488e671"
1265dependencies = [ 1270dependencies = [
1266 "unicode-xid", 1271 "unicode-xid",
1267] 1272]
@@ -1450,6 +1455,7 @@ dependencies = [
1450 "expect", 1455 "expect",
1451 "hir", 1456 "hir",
1452 "ide_db", 1457 "ide_db",
1458 "itertools",
1453 "rustc-hash", 1459 "rustc-hash",
1454 "syntax", 1460 "syntax",
1455 "test_utils", 1461 "test_utils",
@@ -1573,9 +1579,9 @@ dependencies = [
1573 1579
1574[[package]] 1580[[package]]
1575name = "tinyvec" 1581name = "tinyvec"
1576version = "0.3.3" 1582version = "0.3.4"
1577source = "registry+https://github.com/rust-lang/crates.io-index" 1583source = "registry+https://github.com/rust-lang/crates.io-index"
1578checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed" 1584checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
1579 1585
1580[[package]] 1586[[package]]
1581name = "toolchain" 1587name = "toolchain"
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
index 4c41c16d8..5216862ba 100644
--- a/crates/assists/src/ast_transform.rs
+++ b/crates/assists/src/ast_transform.rs
@@ -7,6 +7,17 @@ use syntax::{
7 ast::{self, AstNode}, 7 ast::{self, AstNode},
8}; 8};
9 9
10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
11 SyntaxRewriter::from_fn(|element| match element {
12 syntax::SyntaxElement::Node(n) => {
13 let replacement = transformer.get_substitution(&n)?;
14 Some(replacement.into())
15 }
16 _ => None,
17 })
18 .rewrite_ast(&node)
19}
20
10pub trait AstTransform<'a> { 21pub trait AstTransform<'a> {
11 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>; 22 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode>;
12 23
@@ -107,10 +118,7 @@ impl<'a> SubstituteTypeParams<'a> {
107 ast::Type::PathType(path_type) => path_type.path()?, 118 ast::Type::PathType(path_type) => path_type.path()?,
108 _ => return None, 119 _ => return None,
109 }; 120 };
110 // FIXME: use `hir::Path::from_src` instead. 121 let resolution = self.source_scope.speculative_resolve(&path)?;
111 #[allow(deprecated)]
112 let path = hir::Path::from_ast(path)?;
113 let resolution = self.source_scope.resolve_hir_path(&path)?;
114 match resolution { 122 match resolution {
115 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()), 123 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
116 _ => None, 124 _ => None,
@@ -146,10 +154,7 @@ impl<'a> QualifyPaths<'a> {
146 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway 154 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
147 return None; 155 return None;
148 } 156 }
149 // FIXME: use `hir::Path::from_src` instead. 157 let resolution = self.source_scope.speculative_resolve(&p)?;
150 #[allow(deprecated)]
151 let hir_path = hir::Path::from_ast(p.clone());
152 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
153 match resolution { 158 match resolution {
154 PathResolution::Def(def) => { 159 PathResolution::Def(def) => {
155 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; 160 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
@@ -175,17 +180,6 @@ impl<'a> QualifyPaths<'a> {
175 } 180 }
176} 181}
177 182
178pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
179 SyntaxRewriter::from_fn(|element| match element {
180 syntax::SyntaxElement::Node(n) => {
181 let replacement = transformer.get_substitution(&n)?;
182 Some(replacement.into())
183 }
184 _ => None,
185 })
186 .rewrite_ast(&node)
187}
188
189impl<'a> AstTransform<'a> for QualifyPaths<'a> { 183impl<'a> AstTransform<'a> for QualifyPaths<'a> {
190 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> { 184 fn get_substitution(&self, node: &syntax::SyntaxNode) -> Option<syntax::SyntaxNode> {
191 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node)) 185 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
index 81b61ebf8..83a2ada9a 100644
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ b/crates/assists/src/handlers/add_missing_impl_members.rs
@@ -48,7 +48,6 @@ enum AddMissingImplMembersMode {
48// fn foo(&self) -> u32 { 48// fn foo(&self) -> u32 {
49// ${0:todo!()} 49// ${0:todo!()}
50// } 50// }
51//
52// } 51// }
53// ``` 52// ```
54pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 53pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -89,8 +88,8 @@ pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -
89// impl Trait for () { 88// impl Trait for () {
90// Type X = (); 89// Type X = ();
91// fn foo(&self) {} 90// fn foo(&self) {}
92// $0fn bar(&self) {}
93// 91//
92// $0fn bar(&self) {}
94// } 93// }
95// ``` 94// ```
96pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 95pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
@@ -240,15 +239,18 @@ struct S;
240 239
241impl Foo for S { 240impl Foo for S {
242 fn bar(&self) {} 241 fn bar(&self) {}
242
243 $0type Output; 243 $0type Output;
244
244 const CONST: usize = 42; 245 const CONST: usize = 42;
246
245 fn foo(&self) { 247 fn foo(&self) {
246 todo!() 248 todo!()
247 } 249 }
250
248 fn baz(&self) { 251 fn baz(&self) {
249 todo!() 252 todo!()
250 } 253 }
251
252}"#, 254}"#,
253 ); 255 );
254 } 256 }
@@ -281,10 +283,10 @@ struct S;
281 283
282impl Foo for S { 284impl Foo for S {
283 fn bar(&self) {} 285 fn bar(&self) {}
286
284 fn foo(&self) { 287 fn foo(&self) {
285 ${0:todo!()} 288 ${0:todo!()}
286 } 289 }
287
288}"#, 290}"#,
289 ); 291 );
290 } 292 }
@@ -599,6 +601,7 @@ trait Foo {
599struct S; 601struct S;
600impl Foo for S { 602impl Foo for S {
601 $0type Output; 603 $0type Output;
604
602 fn foo(&self) { 605 fn foo(&self) {
603 todo!() 606 todo!()
604 } 607 }
@@ -708,4 +711,56 @@ impl Tr for () {
708}"#, 711}"#,
709 ) 712 )
710 } 713 }
714
715 #[test]
716 fn test_whitespace_fixup_preserves_bad_tokens() {
717 check_assist(
718 add_missing_impl_members,
719 r#"
720trait Tr {
721 fn foo();
722}
723
724impl Tr for ()<|> {
725 +++
726}"#,
727 r#"
728trait Tr {
729 fn foo();
730}
731
732impl Tr for () {
733 fn foo() {
734 ${0:todo!()}
735 }
736 +++
737}"#,
738 )
739 }
740
741 #[test]
742 fn test_whitespace_fixup_preserves_comments() {
743 check_assist(
744 add_missing_impl_members,
745 r#"
746trait Tr {
747 fn foo();
748}
749
750impl Tr for ()<|> {
751 // very important
752}"#,
753 r#"
754trait Tr {
755 fn foo();
756}
757
758impl Tr for () {
759 fn foo() {
760 ${0:todo!()}
761 }
762 // very important
763}"#,
764 )
765 }
711} 766}
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
index cce789972..b9ec3f10b 100644
--- a/crates/assists/src/handlers/auto_import.rs
+++ b/crates/assists/src/handlers/auto_import.rs
@@ -53,7 +53,7 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
53 |builder| { 53 |builder| {
54 insert_use_statement( 54 insert_use_statement(
55 &auto_import_assets.syntax_under_caret, 55 &auto_import_assets.syntax_under_caret,
56 &import, 56 &import.to_string(),
57 ctx, 57 ctx,
58 builder.text_edit_builder(), 58 builder.text_edit_builder(),
59 ); 59 );
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
index f690ec343..81d0af2f3 100644
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -1,3 +1,4 @@
1use either::Either;
1use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; 2use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
2use ide_db::{ 3use ide_db::{
3 defs::{classify_name_ref, Definition, NameRefClass}, 4 defs::{classify_name_ref, Definition, NameRefClass},
@@ -10,8 +11,6 @@ use crate::{
10 AssistId, AssistKind, 11 AssistId, AssistKind,
11}; 12};
12 13
13use either::Either;
14
15// Assist: expand_glob_import 14// Assist: expand_glob_import
16// 15//
17// Expands glob imports. 16// Expands glob imports.
@@ -40,11 +39,15 @@ use either::Either;
40pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 39pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41 let star = ctx.find_token_at_offset(T![*])?; 40 let star = ctx.find_token_at_offset(T![*])?;
42 let mod_path = find_mod_path(&star)?; 41 let mod_path = find_mod_path(&star)?;
42 let module = match ctx.sema.resolve_path(&mod_path)? {
43 PathResolution::Def(ModuleDef::Module(it)) => it,
44 _ => return None,
45 };
43 46
44 let source_file = ctx.source_file(); 47 let source_file = ctx.source_file();
45 let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); 48 let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());
46 49
47 let defs_in_mod = find_defs_in_mod(ctx, scope, &mod_path)?; 50 let defs_in_mod = find_defs_in_mod(ctx, scope, module)?;
48 let name_refs_in_source_file = 51 let name_refs_in_source_file =
49 source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect(); 52 source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect();
50 let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file); 53 let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file);
@@ -82,17 +85,8 @@ impl Def {
82fn find_defs_in_mod( 85fn find_defs_in_mod(
83 ctx: &AssistContext, 86 ctx: &AssistContext,
84 from: SemanticsScope<'_>, 87 from: SemanticsScope<'_>,
85 path: &ast::Path, 88 module: hir::Module,
86) -> Option<Vec<Def>> { 89) -> Option<Vec<Def>> {
87 let hir_path = ctx.sema.lower_path(&path)?;
88 let module = if let Some(PathResolution::Def(ModuleDef::Module(module))) =
89 from.resolve_hir_path_qualifier(&hir_path)
90 {
91 module
92 } else {
93 return None;
94 };
95
96 let module_scope = module.scope(ctx.db(), from.module()); 90 let module_scope = module.scope(ctx.db(), from.module());
97 91
98 let mut defs = vec![]; 92 let mut defs = vec![];
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 4bcdae7ba..d62e06b4a 100644
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -106,7 +106,12 @@ fn insert_import(
106 if let Some(mut mod_path) = mod_path { 106 if let Some(mut mod_path) = mod_path {
107 mod_path.segments.pop(); 107 mod_path.segments.pop();
108 mod_path.segments.push(variant_hir_name.clone()); 108 mod_path.segments.push(variant_hir_name.clone());
109 insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder()); 109 insert_use_statement(
110 path.syntax(),
111 &mod_path.to_string(),
112 ctx,
113 builder.text_edit_builder(),
114 );
110 } 115 }
111 Some(()) 116 Some(())
112} 117}
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
index 011bf1106..470e5f8ff 100644
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,5 +1,5 @@
1use hir; 1use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode, TextRange};
2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode}; 2use test_utils::mark;
3 3
4use crate::{ 4use crate::{
5 utils::{find_insert_use_container, insert_use_statement}, 5 utils::{find_insert_use_container, insert_use_statement},
@@ -28,12 +28,19 @@ pub(crate) fn replace_qualified_name_with_use(
28 if path.syntax().ancestors().find_map(ast::Use::cast).is_some() { 28 if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
29 return None; 29 return None;
30 } 30 }
31 31 if path.qualifier().is_none() {
32 let hir_path = ctx.sema.lower_path(&path)?; 32 mark::hit!(dont_import_trivial_paths);
33 let segments = collect_hir_path_segments(&hir_path)?;
34 if segments.len() < 2 {
35 return None; 33 return None;
36 } 34 }
35 let path_to_import = path.to_string().clone();
36 let path_to_import = match path.segment()?.generic_arg_list() {
37 Some(generic_args) => {
38 let generic_args_start =
39 generic_args.syntax().text_range().start() - path.syntax().text_range().start();
40 &path_to_import[TextRange::up_to(generic_args_start)]
41 }
42 None => path_to_import.as_str(),
43 };
37 44
38 let target = path.syntax().text_range(); 45 let target = path.syntax().text_range();
39 acc.add( 46 acc.add(
@@ -41,12 +48,16 @@ pub(crate) fn replace_qualified_name_with_use(
41 "Replace qualified path with use", 48 "Replace qualified path with use",
42 target, 49 target,
43 |builder| { 50 |builder| {
44 let path_to_import = hir_path.mod_path().clone();
45 let container = match find_insert_use_container(path.syntax(), ctx) { 51 let container = match find_insert_use_container(path.syntax(), ctx) {
46 Some(c) => c, 52 Some(c) => c,
47 None => return, 53 None => return,
48 }; 54 };
49 insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); 55 insert_use_statement(
56 path.syntax(),
57 &path_to_import.to_string(),
58 ctx,
59 builder.text_edit_builder(),
60 );
50 61
51 // Now that we've brought the name into scope, re-qualify all paths that could be 62 // Now that we've brought the name into scope, re-qualify all paths that could be
52 // affected (that is, all paths inside the node we added the `use` to). 63 // affected (that is, all paths inside the node we added the `use` to).
@@ -58,26 +69,6 @@ pub(crate) fn replace_qualified_name_with_use(
58 ) 69 )
59} 70}
60 71
61fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
62 let mut ps = Vec::<SmolStr>::with_capacity(10);
63 match path.kind() {
64 hir::PathKind::Abs => ps.push("".into()),
65 hir::PathKind::Crate => ps.push("crate".into()),
66 hir::PathKind::Plain => {}
67 hir::PathKind::Super(0) => ps.push("self".into()),
68 hir::PathKind::Super(lvl) => {
69 let mut chain = "super".to_string();
70 for _ in 0..*lvl {
71 chain += "::super";
72 }
73 ps.push(chain.into());
74 }
75 hir::PathKind::DollarCrate(_) => return None,
76 }
77 ps.extend(path.segments().iter().map(|it| it.name.to_string().into()));
78 Some(ps)
79}
80
81/// Adds replacements to `re` that shorten `path` in all descendants of `node`. 72/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
82fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) { 73fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) {
83 for child in node.children() { 74 for child in node.children() {
@@ -467,7 +458,8 @@ impl Debug for Foo {
467 } 458 }
468 459
469 #[test] 460 #[test]
470 fn test_replace_not_applicable_one_segment() { 461 fn dont_import_trivial_paths() {
462 mark::check!(dont_import_trivial_paths);
471 check_assist_not_applicable( 463 check_assist_not_applicable(
472 replace_qualified_name_with_use, 464 replace_qualified_name_with_use,
473 r" 465 r"
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index ae90d68a3..c589b08dc 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -66,13 +66,13 @@ pub struct GroupLabel(pub String);
66 66
67#[derive(Debug, Clone)] 67#[derive(Debug, Clone)]
68pub struct Assist { 68pub struct Assist {
69 id: AssistId, 69 pub id: AssistId,
70 /// Short description of the assist, as shown in the UI. 70 /// Short description of the assist, as shown in the UI.
71 label: String, 71 label: String,
72 group: Option<GroupLabel>, 72 pub group: Option<GroupLabel>,
73 /// Target ranges are used to sort assists: the smaller the target range, 73 /// Target ranges are used to sort assists: the smaller the target range,
74 /// the more specific assist is, and so it should be sorted first. 74 /// the more specific assist is, and so it should be sorted first.
75 target: TextRange, 75 pub target: TextRange,
76} 76}
77 77
78#[derive(Debug, Clone)] 78#[derive(Debug, Clone)]
@@ -82,6 +82,11 @@ pub struct ResolvedAssist {
82} 82}
83 83
84impl Assist { 84impl Assist {
85 fn new(id: AssistId, label: String, group: Option<GroupLabel>, target: TextRange) -> Assist {
86 assert!(label.starts_with(char::is_uppercase));
87 Assist { id, label, group, target }
88 }
89
85 /// Return all the assists applicable at the given position. 90 /// Return all the assists applicable at the given position.
86 /// 91 ///
87 /// Assists are returned in the "unresolved" state, that is only labels are 92 /// Assists are returned in the "unresolved" state, that is only labels are
@@ -114,30 +119,8 @@ impl Assist {
114 acc.finish_resolved() 119 acc.finish_resolved()
115 } 120 }
116 121
117 pub(crate) fn new( 122 pub fn label(&self) -> &str {
118 id: AssistId, 123 self.label.as_str()
119 label: String,
120 group: Option<GroupLabel>,
121 target: TextRange,
122 ) -> Assist {
123 assert!(label.starts_with(|c: char| c.is_uppercase()));
124 Assist { id, label, group, target }
125 }
126
127 pub fn id(&self) -> AssistId {
128 self.id
129 }
130
131 pub fn label(&self) -> String {
132 self.label.clone()
133 }
134
135 pub fn group(&self) -> Option<GroupLabel> {
136 self.group.clone()
137 }
138
139 pub fn target(&self) -> TextRange {
140 self.target
141 } 124 }
142} 125}
143 126
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index d16e6fb0a..173567003 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -82,8 +82,8 @@ trait Trait {
82impl Trait for () { 82impl Trait for () {
83 Type X = (); 83 Type X = ();
84 fn foo(&self) {} 84 fn foo(&self) {}
85 $0fn bar(&self) {}
86 85
86 $0fn bar(&self) {}
87} 87}
88"#####, 88"#####,
89 ) 89 )
@@ -115,7 +115,6 @@ impl Trait<u32> for () {
115 fn foo(&self) -> u32 { 115 fn foo(&self) -> u32 {
116 ${0:todo!()} 116 ${0:todo!()}
117 } 117 }
118
119} 118}
120"#####, 119"#####,
121 ) 120 )
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs
index 50a62ee82..49096a67c 100644
--- a/crates/assists/src/utils/insert_use.rs
+++ b/crates/assists/src/utils/insert_use.rs
@@ -5,7 +5,6 @@
5use std::iter::successors; 5use std::iter::successors;
6 6
7use either::Either; 7use either::Either;
8use hir::{self, ModPath};
9use syntax::{ 8use syntax::{
10 ast::{self, NameOwner, VisibilityOwner}, 9 ast::{self, NameOwner, VisibilityOwner},
11 AstNode, AstToken, Direction, SmolStr, 10 AstNode, AstToken, Direction, SmolStr,
@@ -35,11 +34,11 @@ pub(crate) fn find_insert_use_container(
35pub(crate) fn insert_use_statement( 34pub(crate) fn insert_use_statement(
36 // Ideally the position of the cursor, used to 35 // Ideally the position of the cursor, used to
37 position: &SyntaxNode, 36 position: &SyntaxNode,
38 path_to_import: &ModPath, 37 path_to_import: &str,
39 ctx: &AssistContext, 38 ctx: &AssistContext,
40 builder: &mut TextEditBuilder, 39 builder: &mut TextEditBuilder,
41) { 40) {
42 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 41 let target = path_to_import.split("::").map(SmolStr::new).collect::<Vec<_>>();
43 let container = find_insert_use_container(position, ctx); 42 let container = find_insert_use_container(position, ctx);
44 43
45 if let Some(container) = container { 44 if let Some(container) = container {
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 5dc3ae3b1..c442654dd 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -12,6 +12,7 @@ use hir_def::{
12 docs::Documentation, 12 docs::Documentation,
13 expr::{BindingAnnotation, Pat, PatId}, 13 expr::{BindingAnnotation, Pat, PatId},
14 import_map, 14 import_map,
15 path::ModPath,
15 per_ns::PerNs, 16 per_ns::PerNs,
16 resolver::{HasResolver, Resolver}, 17 resolver::{HasResolver, Resolver},
17 src::HasSource as _, 18 src::HasSource as _,
@@ -344,11 +345,7 @@ impl Module {
344 345
345 /// Finds a path that can be used to refer to the given item from within 346 /// Finds a path that can be used to refer to the given item from within
346 /// this module, if possible. 347 /// this module, if possible.
347 pub fn find_use_path( 348 pub fn find_use_path(self, db: &dyn DefDatabase, item: impl Into<ItemInNs>) -> Option<ModPath> {
348 self,
349 db: &dyn DefDatabase,
350 item: impl Into<ItemInNs>,
351 ) -> Option<hir_def::path::ModPath> {
352 hir_def::find_path::find_path(db, item.into(), self.into()) 349 hir_def::find_path::find_path(db, item.into(), self.into())
353 } 350 }
354} 351}
@@ -1126,7 +1123,7 @@ impl ImplDef {
1126 .value 1123 .value
1127 .attrs() 1124 .attrs()
1128 .filter_map(|it| { 1125 .filter_map(|it| {
1129 let path = hir_def::path::ModPath::from_src(it.path()?, &hygenic)?; 1126 let path = ModPath::from_src(it.path()?, &hygenic)?;
1130 if path.as_ident()?.to_string() == "derive" { 1127 if path.as_ident()?.to_string() == "derive" {
1131 Some(it) 1128 Some(it)
1132 } else { 1129 } else {
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 4ae2bd085..8961ba8fd 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -48,7 +48,7 @@ pub use hir_def::{
48 builtin_type::BuiltinType, 48 builtin_type::BuiltinType,
49 docs::Documentation, 49 docs::Documentation,
50 nameres::ModuleSource, 50 nameres::ModuleSource,
51 path::{ModPath, Path, PathKind}, 51 path::ModPath,
52 type_ref::{Mutability, TypeRef}, 52 type_ref::{Mutability, TypeRef},
53}; 53};
54pub use hir_expand::{ 54pub use hir_expand::{
@@ -60,4 +60,7 @@ pub use hir_ty::display::HirDisplay;
60// These are negative re-exports: pub using these names is forbidden, they 60// These are negative re-exports: pub using these names is forbidden, they
61// should remain private to hir internals. 61// should remain private to hir internals.
62#[allow(unused)] 62#[allow(unused)]
63use hir_expand::hygiene::Hygiene; 63use {
64 hir_def::path::{Path, PathKind},
65 hir_expand::hygiene::Hygiene,
66};
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 3953017c3..c693176fa 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -6,7 +6,7 @@ use std::{cell::RefCell, fmt, iter::successors};
6 6
7use base_db::{FileId, FileRange}; 7use base_db::{FileId, FileRange};
8use hir_def::{ 8use hir_def::{
9 resolver::{self, HasResolver, Resolver}, 9 resolver::{self, HasResolver, Resolver, TypeNs},
10 AsMacroCall, FunctionId, TraitId, VariantId, 10 AsMacroCall, FunctionId, TraitId, VariantId,
11}; 11};
12use hir_expand::{hygiene::Hygiene, name::AsName, ExpansionInfo}; 12use hir_expand::{hygiene::Hygiene, name::AsName, ExpansionInfo};
@@ -22,12 +22,11 @@ use crate::{
22 db::HirDatabase, 22 db::HirDatabase,
23 diagnostics::Diagnostic, 23 diagnostics::Diagnostic,
24 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, 24 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
25 source_analyzer::{resolve_hir_path, resolve_hir_path_qualifier, SourceAnalyzer}, 25 source_analyzer::{resolve_hir_path, SourceAnalyzer},
26 AssocItem, Callable, Crate, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, 26 AssocItem, Callable, Crate, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef,
27 Module, ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, TypeRef, 27 Module, ModuleDef, Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, TypeRef,
28 VariantDef, 28 VariantDef,
29}; 29};
30use resolver::TypeNs;
31 30
32#[derive(Debug, Clone, PartialEq, Eq)] 31#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum PathResolution { 32pub enum PathResolution {
@@ -228,10 +227,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
228 self.imp.resolve_variant(record_lit).map(VariantDef::from) 227 self.imp.resolve_variant(record_lit).map(VariantDef::from)
229 } 228 }
230 229
231 pub fn lower_path(&self, path: &ast::Path) -> Option<Path> {
232 self.imp.lower_path(path)
233 }
234
235 pub fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> { 230 pub fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> {
236 self.imp.resolve_bind_pat_to_const(pat) 231 self.imp.resolve_bind_pat_to_const(pat)
237 } 232 }
@@ -467,11 +462,6 @@ impl<'db> SemanticsImpl<'db> {
467 self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit) 462 self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit)
468 } 463 }
469 464
470 fn lower_path(&self, path: &ast::Path) -> Option<Path> {
471 let src = self.find_file(path.syntax().clone());
472 Path::from_src(path.clone(), &Hygiene::new(self.db.upcast(), src.file_id.into()))
473 }
474
475 fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> { 465 fn resolve_bind_pat_to_const(&self, pat: &ast::IdentPat) -> Option<ModuleDef> {
476 self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat) 466 self.analyze(pat.syntax()).resolve_bind_pat_to_const(self.db, pat)
477 } 467 }
@@ -758,28 +748,7 @@ impl<'a> SemanticsScope<'a> {
758 pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> { 748 pub fn speculative_resolve(&self, path: &ast::Path) -> Option<PathResolution> {
759 let hygiene = Hygiene::new(self.db.upcast(), self.file_id); 749 let hygiene = Hygiene::new(self.db.upcast(), self.file_id);
760 let path = Path::from_src(path.clone(), &hygiene)?; 750 let path = Path::from_src(path.clone(), &hygiene)?;
761 self.resolve_hir_path(&path) 751 resolve_hir_path(self.db, &self.resolver, &path)
762 }
763
764 pub fn resolve_hir_path(&self, path: &Path) -> Option<PathResolution> {
765 resolve_hir_path(self.db, &self.resolver, path)
766 }
767
768 /// Resolves a path where we know it is a qualifier of another path.
769 ///
770 /// For example, if we have:
771 /// ```
772 /// mod my {
773 /// pub mod foo {
774 /// struct Bar;
775 /// }
776 ///
777 /// pub fn foo() {}
778 /// }
779 /// ```
780 /// then we know that `foo` in `my::foo::Bar` refers to the module, not the function.
781 pub fn resolve_hir_path_qualifier(&self, path: &Path) -> Option<PathResolution> {
782 resolve_hir_path_qualifier(self.db, &self.resolver, path)
783 } 752 }
784} 753}
785 754
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 8750584f9..1d13c4f1d 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -13,6 +13,7 @@ use hir_def::{
13 Body, BodySourceMap, 13 Body, BodySourceMap,
14 }, 14 },
15 expr::{ExprId, Pat, PatId}, 15 expr::{ExprId, Pat, PatId},
16 path::{ModPath, Path, PathKind},
16 resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs}, 17 resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
17 AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, VariantId, 18 AsMacroCall, DefWithBodyId, FieldId, FunctionId, LocalFieldId, VariantId,
18}; 19};
@@ -28,8 +29,7 @@ use syntax::{
28 29
29use crate::{ 30use crate::{
30 db::HirDatabase, semantics::PathResolution, Adt, Const, EnumVariant, Field, Function, Local, 31 db::HirDatabase, semantics::PathResolution, Adt, Const, EnumVariant, Field, Function, Local,
31 MacroDef, ModPath, ModuleDef, Path, PathKind, Static, Struct, Trait, Type, TypeAlias, 32 MacroDef, ModuleDef, Static, Struct, Trait, Type, TypeAlias, TypeParam,
32 TypeParam,
33}; 33};
34use base_db::CrateId; 34use base_db::CrateId;
35 35
@@ -508,7 +508,7 @@ pub(crate) fn resolve_hir_path(
508/// } 508/// }
509/// ``` 509/// ```
510/// then we know that `foo` in `my::foo::Bar` refers to the module, not the function. 510/// then we know that `foo` in `my::foo::Bar` refers to the module, not the function.
511pub(crate) fn resolve_hir_path_qualifier( 511fn resolve_hir_path_qualifier(
512 db: &dyn HirDatabase, 512 db: &dyn HirDatabase,
513 resolver: &Resolver, 513 resolver: &Resolver,
514 path: &Path, 514 path: &Path,
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
index 2e38a978f..c7723de00 100644
--- a/crates/hir_def/src/diagnostics.rs
+++ b/crates/hir_def/src/diagnostics.rs
@@ -15,6 +15,9 @@ pub struct UnresolvedModule {
15} 15}
16 16
17impl Diagnostic for UnresolvedModule { 17impl Diagnostic for UnresolvedModule {
18 fn name(&self) -> &'static str {
19 "unresolved-module"
20 }
18 fn message(&self) -> String { 21 fn message(&self) -> String {
19 "unresolved module".to_string() 22 "unresolved module".to_string()
20 } 23 }
diff --git a/crates/hir_def/src/path.rs b/crates/hir_def/src/path.rs
index 74d26f08b..99395667d 100644
--- a/crates/hir_def/src/path.rs
+++ b/crates/hir_def/src/path.rs
@@ -154,12 +154,6 @@ pub enum GenericArg {
154 154
155impl Path { 155impl Path {
156 /// Converts an `ast::Path` to `Path`. Works with use trees. 156 /// Converts an `ast::Path` to `Path`. Works with use trees.
157 #[deprecated = "Doesn't handle hygiene, don't add new calls, remove old ones"]
158 pub fn from_ast(path: ast::Path) -> Option<Path> {
159 lower::lower_path(path, &Hygiene::new_unhygienic())
160 }
161
162 /// Converts an `ast::Path` to `Path`. Works with use trees.
163 /// It correctly handles `$crate` based path from macro call. 157 /// It correctly handles `$crate` based path from macro call.
164 pub fn from_src(path: ast::Path, hygiene: &Hygiene) -> Option<Path> { 158 pub fn from_src(path: ast::Path, hygiene: &Hygiene) -> Option<Path> {
165 lower::lower_path(path, hygiene) 159 lower::lower_path(path, hygiene)
diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_expand/src/diagnostics.rs
index 59d35debe..6c81b2501 100644
--- a/crates/hir_expand/src/diagnostics.rs
+++ b/crates/hir_expand/src/diagnostics.rs
@@ -21,6 +21,7 @@ use syntax::SyntaxNodePtr;
21use crate::InFile; 21use crate::InFile;
22 22
23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { 23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
24 fn name(&self) -> &'static str;
24 fn message(&self) -> String; 25 fn message(&self) -> String;
25 /// Used in highlighting and related purposes 26 /// Used in highlighting and related purposes
26 fn display_source(&self) -> InFile<SyntaxNodePtr>; 27 fn display_source(&self) -> InFile<SyntaxNodePtr>;
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index 83b5013a9..a319b0ce8 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -16,9 +16,9 @@ ena = "0.14.0"
16log = "0.4.8" 16log = "0.4.8"
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18scoped-tls = "1" 18scoped-tls = "1"
19chalk-solve = { version = "0.21.0" } 19chalk-solve = { version = "0.23.0" }
20chalk-ir = { version = "0.21.0" } 20chalk-ir = { version = "0.23.0" }
21chalk-recursive = { version = "0.21.0" } 21chalk-recursive = { version = "0.23.0" }
22 22
23stdx = { path = "../stdx" } 23stdx = { path = "../stdx" }
24hir_def = { path = "../hir_def" } 24hir_def = { path = "../hir_def" }
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index ae0cf8d09..38fa24ee0 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -32,6 +32,10 @@ pub struct NoSuchField {
32} 32}
33 33
34impl Diagnostic for NoSuchField { 34impl Diagnostic for NoSuchField {
35 fn name(&self) -> &'static str {
36 "no-such-field"
37 }
38
35 fn message(&self) -> String { 39 fn message(&self) -> String {
36 "no such field".to_string() 40 "no such field".to_string()
37 } 41 }
@@ -54,6 +58,9 @@ pub struct MissingFields {
54} 58}
55 59
56impl Diagnostic for MissingFields { 60impl Diagnostic for MissingFields {
61 fn name(&self) -> &'static str {
62 "missing-structure-fields"
63 }
57 fn message(&self) -> String { 64 fn message(&self) -> String {
58 let mut buf = String::from("Missing structure fields:\n"); 65 let mut buf = String::from("Missing structure fields:\n");
59 for field in &self.missed_fields { 66 for field in &self.missed_fields {
@@ -87,6 +94,9 @@ pub struct MissingPatFields {
87} 94}
88 95
89impl Diagnostic for MissingPatFields { 96impl Diagnostic for MissingPatFields {
97 fn name(&self) -> &'static str {
98 "missing-pat-fields"
99 }
90 fn message(&self) -> String { 100 fn message(&self) -> String {
91 let mut buf = String::from("Missing structure fields:\n"); 101 let mut buf = String::from("Missing structure fields:\n");
92 for field in &self.missed_fields { 102 for field in &self.missed_fields {
@@ -117,6 +127,9 @@ pub struct MissingMatchArms {
117} 127}
118 128
119impl Diagnostic for MissingMatchArms { 129impl Diagnostic for MissingMatchArms {
130 fn name(&self) -> &'static str {
131 "missing-match-arm"
132 }
120 fn message(&self) -> String { 133 fn message(&self) -> String {
121 String::from("Missing match arm") 134 String::from("Missing match arm")
122 } 135 }
@@ -135,6 +148,9 @@ pub struct MissingOkInTailExpr {
135} 148}
136 149
137impl Diagnostic for MissingOkInTailExpr { 150impl Diagnostic for MissingOkInTailExpr {
151 fn name(&self) -> &'static str {
152 "missing-ok-in-tail-expr"
153 }
138 fn message(&self) -> String { 154 fn message(&self) -> String {
139 "wrap return expression in Ok".to_string() 155 "wrap return expression in Ok".to_string()
140 } 156 }
@@ -153,6 +169,9 @@ pub struct BreakOutsideOfLoop {
153} 169}
154 170
155impl Diagnostic for BreakOutsideOfLoop { 171impl Diagnostic for BreakOutsideOfLoop {
172 fn name(&self) -> &'static str {
173 "break-outside-of-loop"
174 }
156 fn message(&self) -> String { 175 fn message(&self) -> String {
157 "break outside of loop".to_string() 176 "break outside of loop".to_string()
158 } 177 }
@@ -171,6 +190,9 @@ pub struct MissingUnsafe {
171} 190}
172 191
173impl Diagnostic for MissingUnsafe { 192impl Diagnostic for MissingUnsafe {
193 fn name(&self) -> &'static str {
194 "missing-unsafe"
195 }
174 fn message(&self) -> String { 196 fn message(&self) -> String {
175 format!("This operation is unsafe and requires an unsafe function or block") 197 format!("This operation is unsafe and requires an unsafe function or block")
176 } 198 }
@@ -191,6 +213,9 @@ pub struct MismatchedArgCount {
191} 213}
192 214
193impl Diagnostic for MismatchedArgCount { 215impl Diagnostic for MismatchedArgCount {
216 fn name(&self) -> &'static str {
217 "mismatched-arg-count"
218 }
194 fn message(&self) -> String { 219 fn message(&self) -> String {
195 let s = if self.expected == 1 { "" } else { "s" }; 220 let s = if self.expected == 1 { "" } else { "s" };
196 format!("Expected {} argument{}, found {}", self.expected, s, self.found) 221 format!("Expected {} argument{}, found {}", self.expected, s, self.found)
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index fb76e2e4e..278a4b947 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -223,10 +223,10 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
223 db.body_with_source_map(self.owner.into()); 223 db.body_with_source_map(self.owner.into());
224 224
225 let match_expr_ty = match infer.type_of_expr.get(match_expr) { 225 let match_expr_ty = match infer.type_of_expr.get(match_expr) {
226 Some(ty) => ty,
227 // If we can't resolve the type of the match expression 226 // If we can't resolve the type of the match expression
228 // we cannot perform exhaustiveness checks. 227 // we cannot perform exhaustiveness checks.
229 None => return, 228 None | Some(Ty::Unknown) => return,
229 Some(ty) => ty,
230 }; 230 };
231 231
232 let cx = MatchCheckCtx { match_expr, body, infer: infer.clone(), db }; 232 let cx = MatchCheckCtx { match_expr, body, infer: infer.clone(), db };
diff --git a/crates/hir_ty/src/diagnostics/match_check.rs b/crates/hir_ty/src/diagnostics/match_check.rs
index 7f007f1d6..5bd03f2ac 100644
--- a/crates/hir_ty/src/diagnostics/match_check.rs
+++ b/crates/hir_ty/src/diagnostics/match_check.rs
@@ -1335,6 +1335,23 @@ fn panic(a: Category, b: Category) {
1335 ); 1335 );
1336 } 1336 }
1337 1337
1338 #[test]
1339 fn unknown_type() {
1340 check_diagnostics(
1341 r#"
1342enum Option<T> { Some(T), None }
1343
1344fn main() {
1345 // `Never` is deliberately not defined so that it's an uninferred type.
1346 match Option::<Never>::None {
1347 None => (),
1348 Some(never) => match never {},
1349 }
1350}
1351"#,
1352 );
1353 }
1354
1338 mod false_negatives { 1355 mod false_negatives {
1339 //! The implementation of match checking here is a work in progress. As we roll this out, we 1356 //! The implementation of match checking here is a work in progress. As we roll this out, we
1340 //! prefer false negatives to false positives (ideally there would be no false positives). This 1357 //! prefer false negatives to false positives (ideally there would be no false positives). This
diff --git a/crates/hir_ty/src/traits.rs b/crates/hir_ty/src/traits.rs
index 1c3abb18f..14cd3a2b4 100644
--- a/crates/hir_ty/src/traits.rs
+++ b/crates/hir_ty/src/traits.rs
@@ -170,11 +170,11 @@ fn solve(
170 let mut solve = || { 170 let mut solve = || {
171 if is_chalk_print() { 171 if is_chalk_print() {
172 let logging_db = LoggingRustIrDatabase::new(context); 172 let logging_db = LoggingRustIrDatabase::new(context);
173 let solution = solver.solve_limited(&logging_db, goal, should_continue); 173 let solution = solver.solve_limited(&logging_db, goal, &should_continue);
174 log::debug!("chalk program:\n{}", logging_db); 174 log::debug!("chalk program:\n{}", logging_db);
175 solution 175 solution
176 } else { 176 } else {
177 solver.solve_limited(&context, goal, should_continue) 177 solver.solve_limited(&context, goal, &should_continue)
178 } 178 }
179 }; 179 };
180 180
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index a3ec98178..606a6064b 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -4,7 +4,7 @@
4//! macro-expanded files, but we need to present them to the users in terms of 4//! macro-expanded files, but we need to present them to the users in terms of
5//! original files. So we need to map the ranges. 5//! original files. So we need to map the ranges.
6 6
7use std::cell::RefCell; 7use std::{cell::RefCell, collections::HashSet};
8 8
9use base_db::SourceDatabase; 9use base_db::SourceDatabase;
10use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; 10use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
@@ -31,6 +31,7 @@ pub(crate) fn diagnostics(
31 db: &RootDatabase, 31 db: &RootDatabase,
32 file_id: FileId, 32 file_id: FileId,
33 enable_experimental: bool, 33 enable_experimental: bool,
34 disabled_diagnostics: Option<HashSet<String>>,
34) -> Vec<Diagnostic> { 35) -> Vec<Diagnostic> {
35 let _p = profile::span("diagnostics"); 36 let _p = profile::span("diagnostics");
36 let sema = Semantics::new(db); 37 let sema = Semantics::new(db);
@@ -39,6 +40,7 @@ pub(crate) fn diagnostics(
39 40
40 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. 41 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
41 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic { 42 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic {
43 name: None,
42 range: err.range(), 44 range: err.range(),
43 message: format!("Syntax Error: {}", err), 45 message: format!("Syntax Error: {}", err),
44 severity: Severity::Error, 46 severity: Severity::Error,
@@ -50,7 +52,7 @@ pub(crate) fn diagnostics(
50 check_struct_shorthand_initialization(&mut res, file_id, &node); 52 check_struct_shorthand_initialization(&mut res, file_id, &node);
51 } 53 }
52 let res = RefCell::new(res); 54 let res = RefCell::new(res);
53 let mut sink = DiagnosticSinkBuilder::new() 55 let mut sink_builder = DiagnosticSinkBuilder::new()
54 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 56 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
55 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 57 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
56 }) 58 })
@@ -64,10 +66,19 @@ pub(crate) fn diagnostics(
64 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 66 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
65 }) 67 })
66 // Only collect experimental diagnostics when they're enabled. 68 // Only collect experimental diagnostics when they're enabled.
67 .filter(|diag| !diag.is_experimental() || enable_experimental) 69 .filter(|diag| !diag.is_experimental() || enable_experimental);
70
71 if let Some(disabled_diagnostics) = disabled_diagnostics {
72 // Do not collect disabled diagnostics.
73 sink_builder = sink_builder.filter(move |diag| !disabled_diagnostics.contains(diag.name()));
74 }
75
76 // Finalize the `DiagnosticSink` building process.
77 let mut sink = sink_builder
68 // Diagnostics not handled above get no fix and default treatment. 78 // Diagnostics not handled above get no fix and default treatment.
69 .build(|d| { 79 .build(|d| {
70 res.borrow_mut().push(Diagnostic { 80 res.borrow_mut().push(Diagnostic {
81 name: Some(d.name().into()),
71 message: d.message(), 82 message: d.message(),
72 range: sema.diagnostics_display_range(d).range, 83 range: sema.diagnostics_display_range(d).range,
73 severity: Severity::Error, 84 severity: Severity::Error,
@@ -84,6 +95,7 @@ pub(crate) fn diagnostics(
84 95
85fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 96fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
86 Diagnostic { 97 Diagnostic {
98 name: Some(d.name().into()),
87 range: sema.diagnostics_display_range(d).range, 99 range: sema.diagnostics_display_range(d).range,
88 message: d.message(), 100 message: d.message(),
89 severity: Severity::Error, 101 severity: Severity::Error,
@@ -110,6 +122,7 @@ fn check_unnecessary_braces_in_use_statement(
110 }); 122 });
111 123
112 acc.push(Diagnostic { 124 acc.push(Diagnostic {
125 name: None,
113 range: use_range, 126 range: use_range,
114 message: "Unnecessary braces in use statement".to_string(), 127 message: "Unnecessary braces in use statement".to_string(),
115 severity: Severity::WeakWarning, 128 severity: Severity::WeakWarning,
@@ -156,6 +169,7 @@ fn check_struct_shorthand_initialization(
156 169
157 let field_range = record_field.syntax().text_range(); 170 let field_range = record_field.syntax().text_range();
158 acc.push(Diagnostic { 171 acc.push(Diagnostic {
172 name: None,
159 range: field_range, 173 range: field_range,
160 message: "Shorthand struct initialization".to_string(), 174 message: "Shorthand struct initialization".to_string(),
161 severity: Severity::WeakWarning, 175 severity: Severity::WeakWarning,
@@ -173,6 +187,7 @@ fn check_struct_shorthand_initialization(
173 187
174#[cfg(test)] 188#[cfg(test)]
175mod tests { 189mod tests {
190 use std::collections::HashSet;
176 use stdx::trim_indent; 191 use stdx::trim_indent;
177 use test_utils::assert_eq_text; 192 use test_utils::assert_eq_text;
178 193
@@ -188,7 +203,8 @@ mod tests {
188 let after = trim_indent(ra_fixture_after); 203 let after = trim_indent(ra_fixture_after);
189 204
190 let (analysis, file_position) = analysis_and_position(ra_fixture_before); 205 let (analysis, file_position) = analysis_and_position(ra_fixture_before);
191 let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap(); 206 let diagnostic =
207 analysis.diagnostics(file_position.file_id, true, None).unwrap().pop().unwrap();
192 let mut fix = diagnostic.fix.unwrap(); 208 let mut fix = diagnostic.fix.unwrap();
193 let edit = fix.source_change.source_file_edits.pop().unwrap().edit; 209 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
194 let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); 210 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
@@ -214,7 +230,7 @@ mod tests {
214 let ra_fixture_after = &trim_indent(ra_fixture_after); 230 let ra_fixture_after = &trim_indent(ra_fixture_after);
215 let (analysis, file_pos) = analysis_and_position(ra_fixture_before); 231 let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
216 let current_file_id = file_pos.file_id; 232 let current_file_id = file_pos.file_id;
217 let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap(); 233 let diagnostic = analysis.diagnostics(current_file_id, true, None).unwrap().pop().unwrap();
218 let mut fix = diagnostic.fix.unwrap(); 234 let mut fix = diagnostic.fix.unwrap();
219 let edit = fix.source_change.source_file_edits.pop().unwrap(); 235 let edit = fix.source_change.source_file_edits.pop().unwrap();
220 let changed_file_id = edit.file_id; 236 let changed_file_id = edit.file_id;
@@ -235,14 +251,58 @@ mod tests {
235 let analysis = mock.analysis(); 251 let analysis = mock.analysis();
236 let diagnostics = files 252 let diagnostics = files
237 .into_iter() 253 .into_iter()
238 .flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap()) 254 .flat_map(|file_id| analysis.diagnostics(file_id, true, None).unwrap())
239 .collect::<Vec<_>>(); 255 .collect::<Vec<_>>();
240 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 256 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
241 } 257 }
242 258
259 /// Takes a multi-file input fixture with annotated cursor position and the list of disabled diagnostics,
260 /// and checks that provided diagnostics aren't spawned during analysis.
261 fn check_disabled_diagnostics(ra_fixture: &str, disabled_diagnostics: &[&'static str]) {
262 let disabled_diagnostics: HashSet<_> =
263 disabled_diagnostics.into_iter().map(|diag| diag.to_string()).collect();
264
265 let mock = MockAnalysis::with_files(ra_fixture);
266 let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
267 let analysis = mock.analysis();
268
269 let diagnostics = files
270 .clone()
271 .into_iter()
272 .flat_map(|file_id| {
273 analysis.diagnostics(file_id, true, Some(disabled_diagnostics.clone())).unwrap()
274 })
275 .collect::<Vec<_>>();
276
277 // First, we have to check that diagnostic is not emitted when it's added to the disabled diagnostics list.
278 for diagnostic in diagnostics {
279 if let Some(name) = diagnostic.name {
280 assert!(!disabled_diagnostics.contains(&name), "Diagnostic {} is disabled", name);
281 }
282 }
283
284 // Then, we must reset the config and repeat the check, so that we'll be sure that without
285 // config these diagnostics are emitted.
286 // This is required for tests to not become outdated if e.g. diagnostics name changes:
287 // without this additional run the test will pass simply because a diagnostic with an old name
288 // will no longer exist.
289 let diagnostics = files
290 .into_iter()
291 .flat_map(|file_id| analysis.diagnostics(file_id, true, None).unwrap())
292 .collect::<Vec<_>>();
293
294 assert!(
295 diagnostics
296 .into_iter()
297 .filter_map(|diag| diag.name)
298 .any(|name| disabled_diagnostics.contains(&name)),
299 "At least one of the diagnostics was not emitted even without config; are the diagnostics names correct?"
300 );
301 }
302
243 fn check_expect(ra_fixture: &str, expect: Expect) { 303 fn check_expect(ra_fixture: &str, expect: Expect) {
244 let (analysis, file_id) = single_file(ra_fixture); 304 let (analysis, file_id) = single_file(ra_fixture);
245 let diagnostics = analysis.diagnostics(file_id, true).unwrap(); 305 let diagnostics = analysis.diagnostics(file_id, true, None).unwrap();
246 expect.assert_debug_eq(&diagnostics) 306 expect.assert_debug_eq(&diagnostics)
247 } 307 }
248 308
@@ -502,6 +562,9 @@ fn test_fn() {
502 expect![[r#" 562 expect![[r#"
503 [ 563 [
504 Diagnostic { 564 Diagnostic {
565 name: Some(
566 "unresolved-module",
567 ),
505 message: "unresolved module", 568 message: "unresolved module",
506 range: 0..8, 569 range: 0..8,
507 severity: Error, 570 severity: Error,
@@ -675,4 +738,9 @@ struct Foo {
675 ", 738 ",
676 ) 739 )
677 } 740 }
741
742 #[test]
743 fn test_disabled_diagnostics() {
744 check_disabled_diagnostics(r#"mod foo;"#, &["unresolved-module"]);
745 }
678} 746}
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 002adf915..596bc872d 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -43,7 +43,7 @@ pub struct InlayHint {
43// rust-analyzer shows additional information inline with the source code. 43// rust-analyzer shows additional information inline with the source code.
44// Editors usually render this using read-only virtual text snippets interspersed with code. 44// Editors usually render this using read-only virtual text snippets interspersed with code.
45// 45//
46// rust-analyzer shows hits for 46// rust-analyzer shows hints for
47// 47//
48// * types of local variables 48// * types of local variables
49// * names of function arguments 49// * names of function arguments
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index eb6389529..4b797f374 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -44,7 +44,7 @@ mod syntax_highlighting;
44mod syntax_tree; 44mod syntax_tree;
45mod typing; 45mod typing;
46 46
47use std::sync::Arc; 47use std::{collections::HashSet, sync::Arc};
48 48
49use base_db::{ 49use base_db::{
50 salsa::{self, ParallelDatabase}, 50 salsa::{self, ParallelDatabase},
@@ -101,6 +101,7 @@ pub type Cancelable<T> = Result<T, Canceled>;
101 101
102#[derive(Debug)] 102#[derive(Debug)]
103pub struct Diagnostic { 103pub struct Diagnostic {
104 pub name: Option<String>,
104 pub message: String, 105 pub message: String,
105 pub range: TextRange, 106 pub range: TextRange,
106 pub severity: Severity, 107 pub severity: Severity,
@@ -147,7 +148,7 @@ pub struct AnalysisHost {
147} 148}
148 149
149impl AnalysisHost { 150impl AnalysisHost {
150 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost { 151 pub fn new(lru_capacity: Option<usize>) -> Self {
151 AnalysisHost { db: RootDatabase::new(lru_capacity) } 152 AnalysisHost { db: RootDatabase::new(lru_capacity) }
152 } 153 }
153 154
@@ -496,8 +497,11 @@ impl Analysis {
496 &self, 497 &self,
497 file_id: FileId, 498 file_id: FileId,
498 enable_experimental: bool, 499 enable_experimental: bool,
500 disabled_diagnostics: Option<HashSet<String>>,
499 ) -> Cancelable<Vec<Diagnostic>> { 501 ) -> Cancelable<Vec<Diagnostic>> {
500 self.with_db(|db| diagnostics::diagnostics(db, file_id, enable_experimental)) 502 self.with_db(|db| {
503 diagnostics::diagnostics(db, file_id, enable_experimental, disabled_diagnostics)
504 })
501 } 505 }
502 506
503 /// Returns the edit required to rename reference at the position to the new 507 /// Returns the edit required to rename reference at the position to the new
diff --git a/crates/proc_macro_api/src/lib.rs b/crates/proc_macro_api/src/lib.rs
index 15db57eb2..d5e87cf7d 100644
--- a/crates/proc_macro_api/src/lib.rs
+++ b/crates/proc_macro_api/src/lib.rs
@@ -89,9 +89,8 @@ impl ProcMacroClient {
89 macros 89 macros
90 .into_iter() 90 .into_iter()
91 .filter_map(|(name, kind)| { 91 .filter_map(|(name, kind)| {
92 // FIXME: Support custom derive only for now.
93 match kind { 92 match kind {
94 ProcMacroKind::CustomDerive => { 93 ProcMacroKind::CustomDerive | ProcMacroKind::FuncLike => {
95 let name = SmolStr::new(&name); 94 let name = SmolStr::new(&name);
96 let expander: Arc<dyn tt::TokenExpander> = 95 let expander: Arc<dyn tt::TokenExpander> =
97 Arc::new(ProcMacroProcessExpander { 96 Arc::new(ProcMacroProcessExpander {
@@ -101,7 +100,8 @@ impl ProcMacroClient {
101 }); 100 });
102 Some((name, expander)) 101 Some((name, expander))
103 } 102 }
104 _ => None, 103 // FIXME: Attribute macro are currently unsupported.
104 ProcMacroKind::Attr => None,
105 } 105 }
106 }) 106 })
107 .collect() 107 .collect()
diff --git a/crates/proc_macro_srv/Cargo.toml b/crates/proc_macro_srv/Cargo.toml
index 7171f0808..a468b5560 100644
--- a/crates/proc_macro_srv/Cargo.toml
+++ b/crates/proc_macro_srv/Cargo.toml
@@ -21,7 +21,9 @@ test_utils = { path = "../test_utils" }
21[dev-dependencies] 21[dev-dependencies]
22cargo_metadata = "0.11.1" 22cargo_metadata = "0.11.1"
23difference = "2.0.0" 23difference = "2.0.0"
24# used as proc macro test target 24
25# used as proc macro test targets
25serde_derive = "1.0.106" 26serde_derive = "1.0.106"
27proc_macro_test = { path = "../proc_macro_test" }
26 28
27toolchain = { path = "../toolchain" } 29toolchain = { path = "../toolchain" }
diff --git a/crates/proc_macro_srv/src/tests/mod.rs b/crates/proc_macro_srv/src/tests/mod.rs
index 8e6f28abd..1a827cbd7 100644
--- a/crates/proc_macro_srv/src/tests/mod.rs
+++ b/crates/proc_macro_srv/src/tests/mod.rs
@@ -35,7 +35,7 @@ SUBTREE $
35 35
36#[test] 36#[test]
37fn test_derive_proc_macro_list() { 37fn test_derive_proc_macro_list() {
38 let res = list("serde_derive", "1.0").join("\n"); 38 let res = list("serde_derive", "1").join("\n");
39 39
40 assert_eq_text!( 40 assert_eq_text!(
41 &res, 41 &res,
@@ -43,3 +43,16 @@ fn test_derive_proc_macro_list() {
43Deserialize [CustomDerive]"# 43Deserialize [CustomDerive]"#
44 ); 44 );
45} 45}
46
47/// Tests that we find and classify non-derive macros correctly.
48#[test]
49fn list_test_macros() {
50 let res = list("proc_macro_test", "0.0.0").join("\n");
51
52 assert_eq_text!(
53 &res,
54 r#"function_like_macro [FuncLike]
55attribute_macro [Attr]
56DummyTrait [CustomDerive]"#
57 );
58}
diff --git a/crates/proc_macro_srv/src/tests/utils.rs b/crates/proc_macro_srv/src/tests/utils.rs
index 5828512d6..36942147d 100644
--- a/crates/proc_macro_srv/src/tests/utils.rs
+++ b/crates/proc_macro_srv/src/tests/utils.rs
@@ -13,7 +13,7 @@ mod fixtures {
13 // Use current project metadata to get the proc-macro dylib path 13 // Use current project metadata to get the proc-macro dylib path
14 pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf { 14 pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf {
15 let command = Command::new(toolchain::cargo()) 15 let command = Command::new(toolchain::cargo())
16 .args(&["check", "--message-format", "json"]) 16 .args(&["check", "--tests", "--message-format", "json"])
17 .output() 17 .output()
18 .unwrap() 18 .unwrap()
19 .stdout; 19 .stdout;
diff --git a/crates/proc_macro_test/Cargo.toml b/crates/proc_macro_test/Cargo.toml
new file mode 100644
index 000000000..7b0f64f31
--- /dev/null
+++ b/crates/proc_macro_test/Cargo.toml
@@ -0,0 +1,10 @@
1[package]
2name = "proc_macro_test"
3version = "0.0.0"
4license = "MIT OR Apache-2.0"
5authors = ["rust-analyzer developers"]
6edition = "2018"
7
8[lib]
9doctest = false
10proc-macro = true
diff --git a/crates/proc_macro_test/src/lib.rs b/crates/proc_macro_test/src/lib.rs
new file mode 100644
index 000000000..ec2a114a3
--- /dev/null
+++ b/crates/proc_macro_test/src/lib.rs
@@ -0,0 +1,18 @@
1//! Exports a few trivial procedural macros for testing.
2
3use proc_macro::TokenStream;
4
5#[proc_macro]
6pub fn function_like_macro(args: TokenStream) -> TokenStream {
7 args
8}
9
10#[proc_macro_attribute]
11pub fn attribute_macro(_args: TokenStream, item: TokenStream) -> TokenStream {
12 item
13}
14
15#[proc_macro_derive(DummyTrait)]
16pub fn derive_macro(_item: TokenStream) -> TokenStream {
17 TokenStream::new()
18}
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 0f614f9e0..43f0196af 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -71,7 +71,7 @@ impl BenchCmd {
71 match &self.what { 71 match &self.what {
72 BenchWhat::Highlight { .. } => { 72 BenchWhat::Highlight { .. } => {
73 let res = do_work(&mut host, file_id, |analysis| { 73 let res = do_work(&mut host, file_id, |analysis| {
74 analysis.diagnostics(file_id, true).unwrap(); 74 analysis.diagnostics(file_id, true, None).unwrap();
75 analysis.highlight_as_html(file_id, false).unwrap() 75 analysis.highlight_as_html(file_id, false).unwrap()
76 }); 76 });
77 if verbosity.is_verbose() { 77 if verbosity.is_verbose() {
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 3371c4fd3..31eb7ff3f 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -47,7 +47,7 @@ pub fn diagnostics(
47 String::from("unknown") 47 String::from("unknown")
48 }; 48 };
49 println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); 49 println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
50 for diagnostic in analysis.diagnostics(file_id, true).unwrap() { 50 for diagnostic in analysis.diagnostics(file_id, true, None).unwrap() {
51 if matches!(diagnostic.severity, Severity::Error) { 51 if matches!(diagnostic.severity, Severity::Error) {
52 found_error = true; 52 found_error = true;
53 } 53 }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 33fb5e9c2..44fd7c286 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,7 +7,7 @@
7//! configure the server itself, feature flags are passed into analysis, and 7//! configure the server itself, feature flags are passed into analysis, and
8//! tweak things like automatic insertion of `()` in completions. 8//! tweak things like automatic insertion of `()` in completions.
9 9
10use std::{ffi::OsString, path::PathBuf}; 10use std::{collections::HashSet, ffi::OsString, path::PathBuf};
11 11
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig}; 13use ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig};
@@ -45,6 +45,14 @@ pub struct Config {
45 pub with_sysroot: bool, 45 pub with_sysroot: bool,
46 pub linked_projects: Vec<LinkedProject>, 46 pub linked_projects: Vec<LinkedProject>,
47 pub root_path: AbsPathBuf, 47 pub root_path: AbsPathBuf,
48
49 pub analysis: AnalysisConfig,
50}
51
52/// Configuration parameters for the analysis run.
53#[derive(Debug, Default, Clone)]
54pub struct AnalysisConfig {
55 pub disabled_diagnostics: HashSet<String>,
48} 56}
49 57
50#[derive(Debug, Clone, Eq, PartialEq)] 58#[derive(Debug, Clone, Eq, PartialEq)]
@@ -176,6 +184,8 @@ impl Config {
176 hover: HoverConfig::default(), 184 hover: HoverConfig::default(),
177 linked_projects: Vec::new(), 185 linked_projects: Vec::new(),
178 root_path, 186 root_path,
187
188 analysis: AnalysisConfig::default(),
179 } 189 }
180 } 190 }
181 191
@@ -293,6 +303,8 @@ impl Config {
293 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, 303 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
294 }; 304 };
295 305
306 self.analysis = AnalysisConfig { disabled_diagnostics: data.analysis_disabledDiagnostics };
307
296 log::info!("Config::update() = {:#?}", self); 308 log::info!("Config::update() = {:#?}", self);
297 } 309 }
298 310
@@ -357,6 +369,14 @@ impl Config {
357 self.client_caps.status_notification = get_bool("statusNotification"); 369 self.client_caps.status_notification = get_bool("statusNotification");
358 } 370 }
359 } 371 }
372
373 pub fn disabled_diagnostics(&self) -> Option<HashSet<String>> {
374 if self.analysis.disabled_diagnostics.is_empty() {
375 None
376 } else {
377 Some(self.analysis.disabled_diagnostics.clone())
378 }
379 }
360} 380}
361 381
362#[derive(Deserialize)] 382#[derive(Deserialize)]
@@ -444,5 +464,7 @@ config_data! {
444 rustfmt_overrideCommand: Option<Vec<String>> = None, 464 rustfmt_overrideCommand: Option<Vec<String>> = None,
445 465
446 withSysroot: bool = true, 466 withSysroot: bool = true,
467
468 analysis_disabledDiagnostics: HashSet<String> = HashSet::new(),
447 } 469 }
448} 470}
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 74f73655a..4f77b1b4d 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -272,19 +272,24 @@ pub(crate) fn handle_document_symbol(
272 parents.push((doc_symbol, symbol.parent)); 272 parents.push((doc_symbol, symbol.parent));
273 } 273 }
274 let mut document_symbols = Vec::new(); 274 let mut document_symbols = Vec::new();
275 // Constructs `document_symbols` from `parents`, in order from the end.
275 while let Some((node, parent)) = parents.pop() { 276 while let Some((node, parent)) = parents.pop() {
276 match parent { 277 match parent {
277 None => document_symbols.push(node), 278 None => document_symbols.push(node),
278 Some(i) => { 279 Some(i) => {
279 let children = &mut parents[i].0.children; 280 parents[i].0.children.get_or_insert_with(Vec::new).push(node);
280 if children.is_none() {
281 *children = Some(Vec::new());
282 }
283 children.as_mut().unwrap().push(node);
284 } 281 }
285 } 282 }
286 } 283 }
287 284
285 fn reverse(symbols: &mut Vec<DocumentSymbol>) {
286 for sym in symbols.iter_mut() {
287 sym.children.as_mut().map(|c| reverse(c));
288 }
289 symbols.reverse();
290 }
291 reverse(&mut document_symbols);
292
288 let res = if snap.config.client_caps.hierarchical_symbols { 293 let res = if snap.config.client_caps.hierarchical_symbols {
289 document_symbols.into() 294 document_symbols.into()
290 } else { 295 } else {
@@ -770,7 +775,11 @@ fn handle_fixes(
770 None => {} 775 None => {}
771 }; 776 };
772 777
773 let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?; 778 let diagnostics = snap.analysis.diagnostics(
779 file_id,
780 snap.config.experimental_diagnostics,
781 snap.config.disabled_diagnostics(),
782 )?;
774 783
775 for fix in diagnostics 784 for fix in diagnostics
776 .into_iter() 785 .into_iter()
@@ -859,10 +868,10 @@ pub(crate) fn handle_resolve_code_action(
859 .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect()); 868 .map(|it| it.into_iter().filter_map(from_proto::assist_kind).collect());
860 869
861 let assists = snap.analysis.resolved_assists(&snap.config.assist, frange)?; 870 let assists = snap.analysis.resolved_assists(&snap.config.assist, frange)?;
862 let (id_string, index) = split_once(&params.id, ':').unwrap(); 871 let (id, index) = split_once(&params.id, ':').unwrap();
863 let index = index.parse::<usize>().unwrap(); 872 let index = index.parse::<usize>().unwrap();
864 let assist = &assists[index]; 873 let assist = &assists[index];
865 assert!(assist.assist.id().0 == id_string); 874 assert!(assist.assist.id.0 == id);
866 Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit) 875 Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit)
867} 876}
868 877
@@ -1044,7 +1053,11 @@ pub(crate) fn publish_diagnostics(
1044 let line_index = snap.analysis.file_line_index(file_id)?; 1053 let line_index = snap.analysis.file_line_index(file_id)?;
1045 let diagnostics: Vec<Diagnostic> = snap 1054 let diagnostics: Vec<Diagnostic> = snap
1046 .analysis 1055 .analysis
1047 .diagnostics(file_id, snap.config.experimental_diagnostics)? 1056 .diagnostics(
1057 file_id,
1058 snap.config.experimental_diagnostics,
1059 snap.config.disabled_diagnostics(),
1060 )?
1048 .into_iter() 1061 .into_iter()
1049 .map(|d| Diagnostic { 1062 .map(|d| Diagnostic {
1050 range: to_proto::range(&line_index, d.range), 1063 range: to_proto::range(&line_index, d.range),
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 3976b6529..e1a28b1b4 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -237,8 +237,13 @@ pub enum Status {
237 Invalid, 237 Invalid,
238} 238}
239 239
240#[derive(Deserialize, Serialize)]
241pub struct StatusParams {
242 pub status: Status,
243}
244
240impl Notification for StatusNotification { 245impl Notification for StatusNotification {
241 type Params = Status; 246 type Params = StatusParams;
242 const METHOD: &'static str = "rust-analyzer/status"; 247 const METHOD: &'static str = "rust-analyzer/status";
243} 248}
244 249
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index a2cfb4e0d..505505a77 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -13,6 +13,7 @@ use crate::{
13 lsp_ext, 13 lsp_ext,
14 main_loop::Task, 14 main_loop::Task,
15}; 15};
16use lsp_ext::StatusParams;
16 17
17impl GlobalState { 18impl GlobalState {
18 pub(crate) fn update_configuration(&mut self, config: Config) { 19 pub(crate) fn update_configuration(&mut self, config: Config) {
@@ -85,7 +86,9 @@ impl GlobalState {
85 Status::Invalid => lsp_ext::Status::Invalid, 86 Status::Invalid => lsp_ext::Status::Invalid,
86 Status::NeedsReload => lsp_ext::Status::NeedsReload, 87 Status::NeedsReload => lsp_ext::Status::NeedsReload,
87 }; 88 };
88 self.send_notification::<lsp_ext::StatusNotification>(lsp_status); 89 self.send_notification::<lsp_ext::StatusNotification>(StatusParams {
90 status: lsp_status,
91 });
89 } 92 }
90 } 93 }
91 pub(crate) fn fetch_workspaces(&mut self) { 94 pub(crate) fn fetch_workspaces(&mut self) {
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 8a2cfa2ae..535de2f71 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -704,10 +704,10 @@ pub(crate) fn unresolved_code_action(
704 index: usize, 704 index: usize,
705) -> Result<lsp_ext::CodeAction> { 705) -> Result<lsp_ext::CodeAction> {
706 let res = lsp_ext::CodeAction { 706 let res = lsp_ext::CodeAction {
707 title: assist.label(), 707 title: assist.label().to_string(),
708 id: Some(format!("{}:{}", assist.id().0.to_owned(), index.to_string())), 708 id: Some(format!("{}:{}", assist.id.0, index.to_string())),
709 group: assist.group().filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0), 709 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
710 kind: Some(code_action_kind(assist.id().1)), 710 kind: Some(code_action_kind(assist.id.1)),
711 edit: None, 711 edit: None,
712 is_preferred: None, 712 is_preferred: None,
713 }; 713 };
diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/rust-analyzer/main.rs
index 7370505f8..fa315ff8e 100644
--- a/crates/rust-analyzer/tests/heavy_tests/main.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/main.rs
@@ -1,3 +1,13 @@
1//! The most high-level integrated tests for rust-analyzer.
2//!
3//! This tests run a full LSP event loop, spawn cargo and process stdlib from
4//! sysroot. For this reason, the tests here are very slow, and should be
5//! avoided unless absolutely necessary.
6//!
7//! In particular, it's fine *not* to test that client & server agree on
8//! specific JSON shapes here -- there's little value in such tests, as we can't
9//! be sure without a real client anyway.
10
1mod testdir; 11mod testdir;
2mod support; 12mod support;
3 13
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/rust-analyzer/support.rs
index 5bafeba79..5bafeba79 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/support.rs
diff --git a/crates/rust-analyzer/tests/heavy_tests/testdir.rs b/crates/rust-analyzer/tests/rust-analyzer/testdir.rs
index 7487e7429..7487e7429 100644
--- a/crates/rust-analyzer/tests/heavy_tests/testdir.rs
+++ b/crates/rust-analyzer/tests/rust-analyzer/testdir.rs
diff --git a/crates/ssr/Cargo.toml b/crates/ssr/Cargo.toml
index 56c1f7761..7c2090de3 100644
--- a/crates/ssr/Cargo.toml
+++ b/crates/ssr/Cargo.toml
@@ -12,6 +12,7 @@ doctest = false
12 12
13[dependencies] 13[dependencies]
14rustc-hash = "1.1.0" 14rustc-hash = "1.1.0"
15itertools = "0.9.0"
15 16
16text_edit = { path = "../text_edit" } 17text_edit = { path = "../text_edit" }
17syntax = { path = "../syntax" } 18syntax = { path = "../syntax" }
diff --git a/crates/ssr/src/lib.rs b/crates/ssr/src/lib.rs
index 292bd5b9a..ba669fd56 100644
--- a/crates/ssr/src/lib.rs
+++ b/crates/ssr/src/lib.rs
@@ -21,7 +21,10 @@
21// code in the `foo` module, we'll insert just `Bar`. 21// code in the `foo` module, we'll insert just `Bar`.
22// 22//
23// Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will 23// Inherent method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will
24// match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. 24// match `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. When a
25// placeholder is the receiver of a method call in the search pattern (e.g. `$s.foo()`), but not in
26// the replacement template (e.g. `bar($s)`), then *, & and &mut will be added as needed to mirror
27// whatever autoderef and autoref was happening implicitly in the matched code.
25// 28//
26// The scope of the search / replace will be restricted to the current selection if any, otherwise 29// The scope of the search / replace will be restricted to the current selection if any, otherwise
27// it will apply to the whole workspace. 30// it will apply to the whole workspace.
diff --git a/crates/ssr/src/matching.rs b/crates/ssr/src/matching.rs
index ffc7202ae..8bb5ced90 100644
--- a/crates/ssr/src/matching.rs
+++ b/crates/ssr/src/matching.rs
@@ -2,7 +2,7 @@
2//! process of matching, placeholder values are recorded. 2//! process of matching, placeholder values are recorded.
3 3
4use crate::{ 4use crate::{
5 parsing::{Constraint, NodeKind, Placeholder}, 5 parsing::{Constraint, NodeKind, Placeholder, Var},
6 resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo}, 6 resolving::{ResolvedPattern, ResolvedRule, UfcsCallInfo},
7 SsrMatches, 7 SsrMatches,
8}; 8};
@@ -56,10 +56,6 @@ pub struct Match {
56 pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>, 56 pub(crate) rendered_template_paths: FxHashMap<SyntaxNode, hir::ModPath>,
57} 57}
58 58
59/// Represents a `$var` in an SSR query.
60#[derive(Debug, Clone, PartialEq, Eq, Hash)]
61pub(crate) struct Var(pub String);
62
63/// Information about a placeholder bound in a match. 59/// Information about a placeholder bound in a match.
64#[derive(Debug)] 60#[derive(Debug)]
65pub(crate) struct PlaceholderMatch { 61pub(crate) struct PlaceholderMatch {
@@ -69,6 +65,10 @@ pub(crate) struct PlaceholderMatch {
69 pub(crate) range: FileRange, 65 pub(crate) range: FileRange,
70 /// More matches, found within `node`. 66 /// More matches, found within `node`.
71 pub(crate) inner_matches: SsrMatches, 67 pub(crate) inner_matches: SsrMatches,
68 /// How many times the code that the placeholder matched needed to be dereferenced. Will only be
69 /// non-zero if the placeholder matched to the receiver of a method call.
70 pub(crate) autoderef_count: usize,
71 pub(crate) autoref_kind: ast::SelfParamKind,
72} 72}
73 73
74#[derive(Debug)] 74#[derive(Debug)]
@@ -173,7 +173,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
173 code: &SyntaxNode, 173 code: &SyntaxNode,
174 ) -> Result<(), MatchFailed> { 174 ) -> Result<(), MatchFailed> {
175 // Handle placeholders. 175 // Handle placeholders.
176 if let Some(placeholder) = self.get_placeholder(&SyntaxElement::Node(pattern.clone())) { 176 if let Some(placeholder) = self.get_placeholder_for_node(pattern) {
177 for constraint in &placeholder.constraints { 177 for constraint in &placeholder.constraints {
178 self.check_constraint(constraint, code)?; 178 self.check_constraint(constraint, code)?;
179 } 179 }
@@ -183,8 +183,8 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
183 // probably can't fail range validation, but just to be safe... 183 // probably can't fail range validation, but just to be safe...
184 self.validate_range(&original_range)?; 184 self.validate_range(&original_range)?;
185 matches_out.placeholder_values.insert( 185 matches_out.placeholder_values.insert(
186 Var(placeholder.ident.to_string()), 186 placeholder.ident.clone(),
187 PlaceholderMatch::new(code, original_range), 187 PlaceholderMatch::new(Some(code), original_range),
188 ); 188 );
189 } 189 }
190 return Ok(()); 190 return Ok(());
@@ -487,7 +487,7 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
487 } 487 }
488 if let Phase::Second(match_out) = phase { 488 if let Phase::Second(match_out) = phase {
489 match_out.placeholder_values.insert( 489 match_out.placeholder_values.insert(
490 Var(placeholder.ident.to_string()), 490 placeholder.ident.clone(),
491 PlaceholderMatch::from_range(FileRange { 491 PlaceholderMatch::from_range(FileRange {
492 file_id: self.sema.original_range(code).file_id, 492 file_id: self.sema.original_range(code).file_id,
493 range: first_matched_token 493 range: first_matched_token
@@ -536,18 +536,40 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
536 if pattern_ufcs.function != code_resolved_function { 536 if pattern_ufcs.function != code_resolved_function {
537 fail_match!("Method call resolved to a different function"); 537 fail_match!("Method call resolved to a different function");
538 } 538 }
539 if code_resolved_function.has_self_param(self.sema.db) {
540 if let (Some(pattern_type), Some(expr)) = (&pattern_ufcs.qualifier_type, &code.expr()) {
541 self.check_expr_type(pattern_type, expr)?;
542 }
543 }
544 // Check arguments. 539 // Check arguments.
545 let mut pattern_args = pattern_ufcs 540 let mut pattern_args = pattern_ufcs
546 .call_expr 541 .call_expr
547 .arg_list() 542 .arg_list()
548 .ok_or_else(|| match_error!("Pattern function call has no args"))? 543 .ok_or_else(|| match_error!("Pattern function call has no args"))?
549 .args(); 544 .args();
550 self.attempt_match_opt(phase, pattern_args.next(), code.expr())?; 545 // If the function we're calling takes a self parameter, then we store additional
546 // information on the placeholder match about autoderef and autoref. This allows us to use
547 // the placeholder in a context where autoderef and autoref don't apply.
548 if code_resolved_function.has_self_param(self.sema.db) {
549 if let (Some(pattern_type), Some(expr)) = (&pattern_ufcs.qualifier_type, &code.expr()) {
550 let deref_count = self.check_expr_type(pattern_type, expr)?;
551 let pattern_receiver = pattern_args.next();
552 self.attempt_match_opt(phase, pattern_receiver.clone(), code.expr())?;
553 if let Phase::Second(match_out) = phase {
554 if let Some(placeholder_value) = pattern_receiver
555 .and_then(|n| self.get_placeholder_for_node(n.syntax()))
556 .and_then(|placeholder| {
557 match_out.placeholder_values.get_mut(&placeholder.ident)
558 })
559 {
560 placeholder_value.autoderef_count = deref_count;
561 placeholder_value.autoref_kind = self
562 .sema
563 .resolve_method_call_as_callable(code)
564 .and_then(|callable| callable.receiver_param(self.sema.db))
565 .map(|self_param| self_param.kind())
566 .unwrap_or(ast::SelfParamKind::Owned);
567 }
568 }
569 }
570 } else {
571 self.attempt_match_opt(phase, pattern_args.next(), code.expr())?;
572 }
551 let mut code_args = 573 let mut code_args =
552 code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args(); 574 code.arg_list().ok_or_else(|| match_error!("Code method call has no args"))?.args();
553 loop { 575 loop {
@@ -575,26 +597,35 @@ impl<'db, 'sema> Matcher<'db, 'sema> {
575 self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax()) 597 self.attempt_match_node_children(phase, pattern_ufcs.call_expr.syntax(), code.syntax())
576 } 598 }
577 599
600 /// Verifies that `expr` matches `pattern_type`, possibly after dereferencing some number of
601 /// times. Returns the number of times it needed to be dereferenced.
578 fn check_expr_type( 602 fn check_expr_type(
579 &self, 603 &self,
580 pattern_type: &hir::Type, 604 pattern_type: &hir::Type,
581 expr: &ast::Expr, 605 expr: &ast::Expr,
582 ) -> Result<(), MatchFailed> { 606 ) -> Result<usize, MatchFailed> {
583 use hir::HirDisplay; 607 use hir::HirDisplay;
584 let code_type = self.sema.type_of_expr(&expr).ok_or_else(|| { 608 let code_type = self.sema.type_of_expr(&expr).ok_or_else(|| {
585 match_error!("Failed to get receiver type for `{}`", expr.syntax().text()) 609 match_error!("Failed to get receiver type for `{}`", expr.syntax().text())
586 })?; 610 })?;
587 if !code_type 611 // Temporary needed to make the borrow checker happy.
612 let res = code_type
588 .autoderef(self.sema.db) 613 .autoderef(self.sema.db)
589 .any(|deref_code_type| *pattern_type == deref_code_type) 614 .enumerate()
590 { 615 .find(|(_, deref_code_type)| pattern_type == deref_code_type)
591 fail_match!( 616 .map(|(count, _)| count)
592 "Pattern type `{}` didn't match code type `{}`", 617 .ok_or_else(|| {
593 pattern_type.display(self.sema.db), 618 match_error!(
594 code_type.display(self.sema.db) 619 "Pattern type `{}` didn't match code type `{}`",
595 ); 620 pattern_type.display(self.sema.db),
596 } 621 code_type.display(self.sema.db)
597 Ok(()) 622 )
623 });
624 res
625 }
626
627 fn get_placeholder_for_node(&self, node: &SyntaxNode) -> Option<&Placeholder> {
628 self.get_placeholder(&SyntaxElement::Node(node.clone()))
598 } 629 }
599 630
600 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> { 631 fn get_placeholder(&self, element: &SyntaxElement) -> Option<&Placeholder> {
@@ -676,12 +707,18 @@ fn recording_match_fail_reasons() -> bool {
676} 707}
677 708
678impl PlaceholderMatch { 709impl PlaceholderMatch {
679 fn new(node: &SyntaxNode, range: FileRange) -> Self { 710 fn new(node: Option<&SyntaxNode>, range: FileRange) -> Self {
680 Self { node: Some(node.clone()), range, inner_matches: SsrMatches::default() } 711 Self {
712 node: node.cloned(),
713 range,
714 inner_matches: SsrMatches::default(),
715 autoderef_count: 0,
716 autoref_kind: ast::SelfParamKind::Owned,
717 }
681 } 718 }
682 719
683 fn from_range(range: FileRange) -> Self { 720 fn from_range(range: FileRange) -> Self {
684 Self { node: None, range, inner_matches: SsrMatches::default() } 721 Self::new(None, range)
685 } 722 }
686} 723}
687 724
diff --git a/crates/ssr/src/parsing.rs b/crates/ssr/src/parsing.rs
index 9570e96e3..05b66dcd7 100644
--- a/crates/ssr/src/parsing.rs
+++ b/crates/ssr/src/parsing.rs
@@ -8,7 +8,7 @@
8use crate::errors::bail; 8use crate::errors::bail;
9use crate::{SsrError, SsrPattern, SsrRule}; 9use crate::{SsrError, SsrPattern, SsrRule};
10use rustc_hash::{FxHashMap, FxHashSet}; 10use rustc_hash::{FxHashMap, FxHashSet};
11use std::str::FromStr; 11use std::{fmt::Display, str::FromStr};
12use syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T}; 12use syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
13use test_utils::mark; 13use test_utils::mark;
14 14
@@ -34,12 +34,16 @@ pub(crate) enum PatternElement {
34#[derive(Clone, Debug, PartialEq, Eq)] 34#[derive(Clone, Debug, PartialEq, Eq)]
35pub(crate) struct Placeholder { 35pub(crate) struct Placeholder {
36 /// The name of this placeholder. e.g. for "$a", this would be "a" 36 /// The name of this placeholder. e.g. for "$a", this would be "a"
37 pub(crate) ident: SmolStr, 37 pub(crate) ident: Var,
38 /// A unique name used in place of this placeholder when we parse the pattern as Rust code. 38 /// A unique name used in place of this placeholder when we parse the pattern as Rust code.
39 stand_in_name: String, 39 stand_in_name: String,
40 pub(crate) constraints: Vec<Constraint>, 40 pub(crate) constraints: Vec<Constraint>,
41} 41}
42 42
43/// Represents a `$var` in an SSR query.
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45pub(crate) struct Var(pub String);
46
43#[derive(Clone, Debug, PartialEq, Eq)] 47#[derive(Clone, Debug, PartialEq, Eq)]
44pub(crate) enum Constraint { 48pub(crate) enum Constraint {
45 Kind(NodeKind), 49 Kind(NodeKind),
@@ -205,7 +209,7 @@ fn parse_pattern(pattern_str: &str) -> Result<Vec<PatternElement>, SsrError> {
205 if token.kind == T![$] { 209 if token.kind == T![$] {
206 let placeholder = parse_placeholder(&mut tokens)?; 210 let placeholder = parse_placeholder(&mut tokens)?;
207 if !placeholder_names.insert(placeholder.ident.clone()) { 211 if !placeholder_names.insert(placeholder.ident.clone()) {
208 bail!("Name `{}` repeats more than once", placeholder.ident); 212 bail!("Placeholder `{}` repeats more than once", placeholder.ident);
209 } 213 }
210 res.push(PatternElement::Placeholder(placeholder)); 214 res.push(PatternElement::Placeholder(placeholder));
211 } else { 215 } else {
@@ -228,7 +232,7 @@ fn validate_rule(rule: &SsrRule) -> Result<(), SsrError> {
228 for p in &rule.template.tokens { 232 for p in &rule.template.tokens {
229 if let PatternElement::Placeholder(placeholder) = p { 233 if let PatternElement::Placeholder(placeholder) = p {
230 if !defined_placeholders.contains(&placeholder.ident) { 234 if !defined_placeholders.contains(&placeholder.ident) {
231 undefined.push(format!("${}", placeholder.ident)); 235 undefined.push(placeholder.ident.to_string());
232 } 236 }
233 if !placeholder.constraints.is_empty() { 237 if !placeholder.constraints.is_empty() {
234 bail!("Replacement placeholders cannot have constraints"); 238 bail!("Replacement placeholders cannot have constraints");
@@ -344,7 +348,17 @@ impl NodeKind {
344 348
345impl Placeholder { 349impl Placeholder {
346 fn new(name: SmolStr, constraints: Vec<Constraint>) -> Self { 350 fn new(name: SmolStr, constraints: Vec<Constraint>) -> Self {
347 Self { stand_in_name: format!("__placeholder_{}", name), constraints, ident: name } 351 Self {
352 stand_in_name: format!("__placeholder_{}", name),
353 constraints,
354 ident: Var(name.to_string()),
355 }
356 }
357}
358
359impl Display for Var {
360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 write!(f, "${}", self.0)
348 } 362 }
349} 363}
350 364
diff --git a/crates/ssr/src/replacing.rs b/crates/ssr/src/replacing.rs
index 8f8fe6149..29284e3f1 100644
--- a/crates/ssr/src/replacing.rs
+++ b/crates/ssr/src/replacing.rs
@@ -1,10 +1,11 @@
1//! Code for applying replacement templates for matches that have previously been found. 1//! Code for applying replacement templates for matches that have previously been found.
2 2
3use crate::matching::Var;
4use crate::{resolving::ResolvedRule, Match, SsrMatches}; 3use crate::{resolving::ResolvedRule, Match, SsrMatches};
4use itertools::Itertools;
5use rustc_hash::{FxHashMap, FxHashSet}; 5use rustc_hash::{FxHashMap, FxHashSet};
6use syntax::ast::{self, AstToken}; 6use syntax::ast::{self, AstToken};
7use syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize}; 7use syntax::{SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize};
8use test_utils::mark;
8use text_edit::TextEdit; 9use text_edit::TextEdit;
9 10
10/// Returns a text edit that will replace each match in `matches` with its corresponding replacement 11/// Returns a text edit that will replace each match in `matches` with its corresponding replacement
@@ -114,11 +115,33 @@ impl ReplacementRenderer<'_> {
114 fn render_token(&mut self, token: &SyntaxToken) { 115 fn render_token(&mut self, token: &SyntaxToken) {
115 if let Some(placeholder) = self.rule.get_placeholder(&token) { 116 if let Some(placeholder) = self.rule.get_placeholder(&token) {
116 if let Some(placeholder_value) = 117 if let Some(placeholder_value) =
117 self.match_info.placeholder_values.get(&Var(placeholder.ident.to_string())) 118 self.match_info.placeholder_values.get(&placeholder.ident)
118 { 119 {
119 let range = &placeholder_value.range.range; 120 let range = &placeholder_value.range.range;
120 let mut matched_text = 121 let mut matched_text =
121 self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned(); 122 self.file_src[usize::from(range.start())..usize::from(range.end())].to_owned();
123 // If a method call is performed directly on the placeholder, then autoderef and
124 // autoref will apply, so we can just substitute whatever the placeholder matched to
125 // directly. If we're not applying a method call, then we need to add explicitly
126 // deref and ref in order to match whatever was being done implicitly at the match
127 // site.
128 if !token_is_method_call_receiver(token)
129 && (placeholder_value.autoderef_count > 0
130 || placeholder_value.autoref_kind != ast::SelfParamKind::Owned)
131 {
132 mark::hit!(replace_autoref_autoderef_capture);
133 let ref_kind = match placeholder_value.autoref_kind {
134 ast::SelfParamKind::Owned => "",
135 ast::SelfParamKind::Ref => "&",
136 ast::SelfParamKind::MutRef => "&mut ",
137 };
138 matched_text = format!(
139 "{}{}{}",
140 ref_kind,
141 "*".repeat(placeholder_value.autoderef_count),
142 matched_text
143 );
144 }
122 let edit = matches_to_edit_at_offset( 145 let edit = matches_to_edit_at_offset(
123 &placeholder_value.inner_matches, 146 &placeholder_value.inner_matches,
124 self.file_src, 147 self.file_src,
@@ -179,6 +202,29 @@ impl ReplacementRenderer<'_> {
179 } 202 }
180} 203}
181 204
205/// Returns whether token is the receiver of a method call. Note, being within the receiver of a
206/// method call doesn't count. e.g. if the token is `$a`, then `$a.foo()` will return true, while
207/// `($a + $b).foo()` or `x.foo($a)` will return false.
208fn token_is_method_call_receiver(token: &SyntaxToken) -> bool {
209 use syntax::ast::AstNode;
210 // Find the first method call among the ancestors of `token`, then check if the only token
211 // within the receiver is `token`.
212 if let Some(receiver) =
213 token.ancestors().find_map(ast::MethodCallExpr::cast).and_then(|call| call.expr())
214 {
215 let tokens = receiver.syntax().descendants_with_tokens().filter_map(|node_or_token| {
216 match node_or_token {
217 SyntaxElement::Token(t) => Some(t),
218 _ => None,
219 }
220 });
221 if let Some((only_token,)) = tokens.collect_tuple() {
222 return only_token == *token;
223 }
224 }
225 false
226}
227
182fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> { 228fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> {
183 use syntax::ast::AstNode; 229 use syntax::ast::AstNode;
184 if ast::Expr::can_cast(kind) { 230 if ast::Expr::can_cast(kind) {
diff --git a/crates/ssr/src/tests.rs b/crates/ssr/src/tests.rs
index 0d0a00090..e45c88864 100644
--- a/crates/ssr/src/tests.rs
+++ b/crates/ssr/src/tests.rs
@@ -31,7 +31,7 @@ fn parser_two_delimiters() {
31fn parser_repeated_name() { 31fn parser_repeated_name() {
32 assert_eq!( 32 assert_eq!(
33 parse_error_text("foo($a, $a) ==>>"), 33 parse_error_text("foo($a, $a) ==>>"),
34 "Parse error: Name `a` repeats more than once" 34 "Parse error: Placeholder `$a` repeats more than once"
35 ); 35 );
36} 36}
37 37
@@ -1172,3 +1172,110 @@ fn match_trait_method_call() {
1172 assert_matches("Bar::foo($a, $b)", code, &["v1.foo(1)", "Bar::foo(&v1, 3)", "v1_ref.foo(5)"]); 1172 assert_matches("Bar::foo($a, $b)", code, &["v1.foo(1)", "Bar::foo(&v1, 3)", "v1_ref.foo(5)"]);
1173 assert_matches("Bar2::foo($a, $b)", code, &["v2.foo(2)", "Bar2::foo(&v2, 4)", "v2_ref.foo(6)"]); 1173 assert_matches("Bar2::foo($a, $b)", code, &["v2.foo(2)", "Bar2::foo(&v2, 4)", "v2_ref.foo(6)"]);
1174} 1174}
1175
1176#[test]
1177fn replace_autoref_autoderef_capture() {
1178 // Here we have several calls to `$a.foo()`. In the first case autoref is applied, in the
1179 // second, we already have a reference, so it isn't. When $a is used in a context where autoref
1180 // doesn't apply, we need to prefix it with `&`. Finally, we have some cases where autoderef
1181 // needs to be applied.
1182 mark::check!(replace_autoref_autoderef_capture);
1183 let code = r#"
1184 struct Foo {}
1185 impl Foo {
1186 fn foo(&self) {}
1187 fn foo2(&self) {}
1188 }
1189 fn bar(_: &Foo) {}
1190 fn main() {
1191 let f = Foo {};
1192 let fr = &f;
1193 let fr2 = &fr;
1194 let fr3 = &fr2;
1195 f.foo();
1196 fr.foo();
1197 fr2.foo();
1198 fr3.foo();
1199 }
1200 "#;
1201 assert_ssr_transform(
1202 "Foo::foo($a) ==>> bar($a)",
1203 code,
1204 expect![[r#"
1205 struct Foo {}
1206 impl Foo {
1207 fn foo(&self) {}
1208 fn foo2(&self) {}
1209 }
1210 fn bar(_: &Foo) {}
1211 fn main() {
1212 let f = Foo {};
1213 let fr = &f;
1214 let fr2 = &fr;
1215 let fr3 = &fr2;
1216 bar(&f);
1217 bar(&*fr);
1218 bar(&**fr2);
1219 bar(&***fr3);
1220 }
1221 "#]],
1222 );
1223 // If the placeholder is used as the receiver of another method call, then we don't need to
1224 // explicitly autoderef or autoref.
1225 assert_ssr_transform(
1226 "Foo::foo($a) ==>> $a.foo2()",
1227 code,
1228 expect![[r#"
1229 struct Foo {}
1230 impl Foo {
1231 fn foo(&self) {}
1232 fn foo2(&self) {}
1233 }
1234 fn bar(_: &Foo) {}
1235 fn main() {
1236 let f = Foo {};
1237 let fr = &f;
1238 let fr2 = &fr;
1239 let fr3 = &fr2;
1240 f.foo2();
1241 fr.foo2();
1242 fr2.foo2();
1243 fr3.foo2();
1244 }
1245 "#]],
1246 );
1247}
1248
1249#[test]
1250fn replace_autoref_mut() {
1251 let code = r#"
1252 struct Foo {}
1253 impl Foo {
1254 fn foo(&mut self) {}
1255 }
1256 fn bar(_: &mut Foo) {}
1257 fn main() {
1258 let mut f = Foo {};
1259 f.foo();
1260 let fr = &mut f;
1261 fr.foo();
1262 }
1263 "#;
1264 assert_ssr_transform(
1265 "Foo::foo($a) ==>> bar($a)",
1266 code,
1267 expect![[r#"
1268 struct Foo {}
1269 impl Foo {
1270 fn foo(&mut self) {}
1271 }
1272 fn bar(_: &mut Foo) {}
1273 fn main() {
1274 let mut f = Foo {};
1275 bar(&mut f);
1276 let fr = &mut f;
1277 bar(&mut *fr);
1278 }
1279 "#]],
1280 );
1281}
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 3c5027fe5..265d19288 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -17,7 +17,7 @@ pub fn timeit(label: &'static str) -> impl Drop {
17 17
18 impl Drop for Guard { 18 impl Drop for Guard {
19 fn drop(&mut self) { 19 fn drop(&mut self) {
20 eprintln!("{}: {:?}", self.label, self.start.elapsed()) 20 eprintln!("{}: {:.2?}", self.label, self.start.elapsed())
21 } 21 }
22 } 22 }
23 23
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index 47e351f9d..ec3132da8 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -13,7 +13,7 @@ doctest = false
13[dependencies] 13[dependencies]
14itertools = "0.9.0" 14itertools = "0.9.0"
15rowan = "0.10.0" 15rowan = "0.10.0"
16rustc_lexer = { version = "671.0.0", package = "rustc-ap-rustc_lexer" } 16rustc_lexer = { version = "673.0.0", package = "rustc-ap-rustc_lexer" }
17rustc-hash = "1.1.0" 17rustc-hash = "1.1.0"
18arrayvec = "0.5.1" 18arrayvec = "0.5.1"
19once_cell = "1.3.1" 19once_cell = "1.3.1"
diff --git a/crates/syntax/src/ast/edit.rs b/crates/syntax/src/ast/edit.rs
index 190746e09..060b20966 100644
--- a/crates/syntax/src/ast/edit.rs
+++ b/crates/syntax/src/ast/edit.rs
@@ -91,29 +91,52 @@ impl ast::AssocItemList {
91 res = make_multiline(res); 91 res = make_multiline(res);
92 } 92 }
93 items.into_iter().for_each(|it| res = res.append_item(it)); 93 items.into_iter().for_each(|it| res = res.append_item(it));
94 res 94 res.fixup_trailing_whitespace().unwrap_or(res)
95 } 95 }
96 96
97 #[must_use] 97 #[must_use]
98 pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList { 98 pub fn append_item(&self, item: ast::AssocItem) -> ast::AssocItemList {
99 let (indent, position) = match self.assoc_items().last() { 99 let (indent, position, whitespace) = match self.assoc_items().last() {
100 Some(it) => ( 100 Some(it) => (
101 leading_indent(it.syntax()).unwrap_or_default().to_string(), 101 leading_indent(it.syntax()).unwrap_or_default().to_string(),
102 InsertPosition::After(it.syntax().clone().into()), 102 InsertPosition::After(it.syntax().clone().into()),
103 "\n\n",
103 ), 104 ),
104 None => match self.l_curly_token() { 105 None => match self.l_curly_token() {
105 Some(it) => ( 106 Some(it) => (
106 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(), 107 " ".to_string() + &leading_indent(self.syntax()).unwrap_or_default(),
107 InsertPosition::After(it.into()), 108 InsertPosition::After(it.into()),
109 "\n",
108 ), 110 ),
109 None => return self.clone(), 111 None => return self.clone(),
110 }, 112 },
111 }; 113 };
112 let ws = tokens::WsBuilder::new(&format!("\n{}", indent)); 114 let ws = tokens::WsBuilder::new(&format!("{}{}", whitespace, indent));
113 let to_insert: ArrayVec<[SyntaxElement; 2]> = 115 let to_insert: ArrayVec<[SyntaxElement; 2]> =
114 [ws.ws().into(), item.syntax().clone().into()].into(); 116 [ws.ws().into(), item.syntax().clone().into()].into();
115 self.insert_children(position, to_insert) 117 self.insert_children(position, to_insert)
116 } 118 }
119
120 /// Remove extra whitespace between last item and closing curly brace.
121 fn fixup_trailing_whitespace(&self) -> Option<ast::AssocItemList> {
122 let first_token_after_items =
123 self.assoc_items().last()?.syntax().next_sibling_or_token()?;
124 let last_token_before_curly = self.r_curly_token()?.prev_sibling_or_token()?;
125 if last_token_before_curly != first_token_after_items {
126 // there is something more between last item and
127 // right curly than just whitespace - bail out
128 return None;
129 }
130 let whitespace =
131 last_token_before_curly.clone().into_token().and_then(ast::Whitespace::cast)?;
132 let text = whitespace.syntax().text();
133 let newline = text.rfind("\n")?;
134 let keep = tokens::WsBuilder::new(&text[newline..]);
135 Some(self.replace_children(
136 first_token_after_items..=last_token_before_curly,
137 std::iter::once(keep.ws().into()),
138 ))
139 }
117} 140}
118 141
119impl ast::RecordExprFieldList { 142impl ast::RecordExprFieldList {
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index 1be01fd88..2e3133449 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -412,7 +412,13 @@ Reloads project information (that is, re-executes `cargo metadata`).
412 412
413**Method:** `rust-analyzer/status` 413**Method:** `rust-analyzer/status`
414 414
415**Notification:** `"loading" | "ready" | "invalid" | "needsReload"` 415**Notification:**
416
417```typescript
418interface StatusParams {
419 status: "loading" | "ready" | "invalid" | "needsReload",
420}
421```
416 422
417This notification is sent from server to client. 423This notification is sent from server to client.
418The client can use it to display persistent status to the user (in modline). 424The client can use it to display persistent status to the user (in modline).
diff --git a/docs/dev/style.md b/docs/dev/style.md
index 963a6d73d..8effddcda 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -176,6 +176,35 @@ fn frobnicate(walrus: Option<Walrus>) {
176} 176}
177``` 177```
178 178
179# Getters & Setters
180
181If a field can have any value without breaking invariants, make the field public.
182Conversely, if there is an invariant, document it, enforce it in the "constructor" function, make the field private, and provide a getter.
183Never provide setters.
184
185Getters should return borrowed data:
186
187```
188struct Person {
189 // Invariant: never empty
190 first_name: String,
191 middle_name: Option<String>
192}
193
194// Good
195impl Person {
196 fn first_name(&self) -> &str { self.first_name.as_str() }
197 fn middle_name(&self) -> Option<&str> { self.middle_name.as_ref() }
198}
199
200// Not as good
201impl Person {
202 fn first_name(&self) -> String { self.first_name.clone() }
203 fn middle_name(&self) -> &Option<String> { &self.middle_name }
204}
205```
206
207
179# Premature Pessimization 208# Premature Pessimization
180 209
181Avoid writing code which is slower than it needs to be. 210Avoid writing code which is slower than it needs to be.
diff --git a/editors/code/.eslintignore b/editors/code/.eslintignore
new file mode 100644
index 000000000..3df5c860b
--- /dev/null
+++ b/editors/code/.eslintignore
@@ -0,0 +1,3 @@
1node_modules
2.eslintrc.js
3rollup.config.js \ No newline at end of file
diff --git a/editors/code/package.json b/editors/code/package.json
index ee5f96bf3..429ff5def 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -609,6 +609,15 @@
609 }, 609 },
610 "description": "List of warnings that should be displayed with hint severity.\nThe warnings will be indicated by faded text or three dots in code and will not show up in the problems panel.", 610 "description": "List of warnings that should be displayed with hint severity.\nThe warnings will be indicated by faded text or three dots in code and will not show up in the problems panel.",
611 "default": [] 611 "default": []
612 },
613 "rust-analyzer.analysis.disabledDiagnostics": {
614 "type": "array",
615 "uniqueItems": true,
616 "items": {
617 "type": "string"
618 },
619 "description": "List of rust-analyzer diagnostics to disable",
620 "default": []
612 } 621 }
613 } 622 }
614 }, 623 },
diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts
index 6e767babf..543f7e02e 100644
--- a/editors/code/src/ctx.ts
+++ b/editors/code/src/ctx.ts
@@ -36,7 +36,7 @@ export class Ctx {
36 36
37 res.pushCleanup(client.start()); 37 res.pushCleanup(client.start());
38 await client.onReady(); 38 await client.onReady();
39 client.onNotification(ra.status, (status) => res.setStatus(status)); 39 client.onNotification(ra.status, (params) => res.setStatus(params.status));
40 return res; 40 return res;
41 } 41 }
42 42
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index 494d51c83..8663737a6 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -8,7 +8,10 @@ export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analy
8export const memoryUsage = new lc.RequestType<null, string, void>("rust-analyzer/memoryUsage"); 8export const memoryUsage = new lc.RequestType<null, string, void>("rust-analyzer/memoryUsage");
9 9
10export type Status = "loading" | "ready" | "invalid" | "needsReload"; 10export type Status = "loading" | "ready" | "invalid" | "needsReload";
11export const status = new lc.NotificationType<Status>("rust-analyzer/status"); 11export interface StatusParams {
12 status: Status;
13}
14export const status = new lc.NotificationType<StatusParams>("rust-analyzer/status");
12 15
13export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace"); 16export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace");
14 17
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs
index 4b2b614fa..c468468de 100644
--- a/xtask/src/codegen.rs
+++ b/xtask/src/codegen.rs
@@ -16,7 +16,11 @@ use std::{
16 path::{Path, PathBuf}, 16 path::{Path, PathBuf},
17}; 17};
18 18
19use crate::{not_bash::fs2, project_root, Result}; 19use crate::{
20 ensure_rustfmt,
21 not_bash::{fs2, pushenv, run},
22 project_root, Result,
23};
20 24
21pub use self::{ 25pub use self::{
22 gen_assists_docs::{generate_assists_docs, generate_assists_tests}, 26 gen_assists_docs::{generate_assists_docs, generate_assists_tests},
@@ -71,6 +75,18 @@ fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> {
71 } 75 }
72} 76}
73 77
78const PREAMBLE: &str = "Generated file, do not edit by hand, see `xtask/src/codegen`";
79
80fn reformat(text: impl std::fmt::Display) -> Result<String> {
81 let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
82 ensure_rustfmt()?;
83 let stdout = run!(
84 "rustfmt --config-path {} --config fn_single_line=true", project_root().join("rustfmt.toml").display();
85 <text.to_string().as_bytes()
86 )?;
87 Ok(format!("//! {}\n\n{}\n", PREAMBLE, stdout))
88}
89
74fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> { 90fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> {
75 do_extract_comment_blocks(text, false).into_iter().map(|(_line, block)| block).collect() 91 do_extract_comment_blocks(text, false).into_iter().map(|(_line, block)| block).collect()
76} 92}
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs
index 526941f73..4f4968594 100644
--- a/xtask/src/codegen/gen_assists_docs.rs
+++ b/xtask/src/codegen/gen_assists_docs.rs
@@ -3,7 +3,7 @@
3use std::{fmt, fs, path::Path}; 3use std::{fmt, fs, path::Path};
4 4
5use crate::{ 5use crate::{
6 codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode}, 6 codegen::{self, extract_comment_blocks_with_empty_lines, reformat, Location, Mode, PREAMBLE},
7 project_root, rust_files, Result, 7 project_root, rust_files, Result,
8}; 8};
9 9
@@ -15,7 +15,7 @@ pub fn generate_assists_tests(mode: Mode) -> Result<()> {
15pub fn generate_assists_docs(mode: Mode) -> Result<()> { 15pub fn generate_assists_docs(mode: Mode) -> Result<()> {
16 let assists = Assist::collect()?; 16 let assists = Assist::collect()?;
17 let contents = assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); 17 let contents = assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
18 let contents = contents.trim().to_string() + "\n"; 18 let contents = format!("//{}\n{}\n", PREAMBLE, contents.trim());
19 let dst = project_root().join("docs/user/generated_assists.adoc"); 19 let dst = project_root().join("docs/user/generated_assists.adoc");
20 codegen::update(&dst, &contents, mode) 20 codegen::update(&dst, &contents, mode)
21} 21}
@@ -134,7 +134,7 @@ r#####"
134 134
135 buf.push_str(&test) 135 buf.push_str(&test)
136 } 136 }
137 let buf = crate::reformat(buf)?; 137 let buf = reformat(buf)?;
138 codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode) 138 codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode)
139} 139}
140 140
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs
index 31bc3839d..3f0013e82 100644
--- a/xtask/src/codegen/gen_feature_docs.rs
+++ b/xtask/src/codegen/gen_feature_docs.rs
@@ -3,14 +3,14 @@
3use std::{fmt, fs, path::PathBuf}; 3use std::{fmt, fs, path::PathBuf};
4 4
5use crate::{ 5use crate::{
6 codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode}, 6 codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode, PREAMBLE},
7 project_root, rust_files, Result, 7 project_root, rust_files, Result,
8}; 8};
9 9
10pub fn generate_feature_docs(mode: Mode) -> Result<()> { 10pub fn generate_feature_docs(mode: Mode) -> Result<()> {
11 let features = Feature::collect()?; 11 let features = Feature::collect()?;
12 let contents = features.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); 12 let contents = features.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n");
13 let contents = contents.trim().to_string() + "\n"; 13 let contents = format!("//{}\n{}\n", PREAMBLE, contents.trim());
14 let dst = project_root().join("docs/user/generated_features.adoc"); 14 let dst = project_root().join("docs/user/generated_features.adoc");
15 codegen::update(&dst, &contents, mode)?; 15 codegen::update(&dst, &contents, mode)?;
16 Ok(()) 16 Ok(())
diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs
index dd1f4d6a2..df3ec22c8 100644
--- a/xtask/src/codegen/gen_syntax.rs
+++ b/xtask/src/codegen/gen_syntax.rs
@@ -14,7 +14,7 @@ use ungrammar::{rust_grammar, Grammar, Rule};
14 14
15use crate::{ 15use crate::{
16 ast_src::{AstEnumSrc, AstNodeSrc, AstSrc, Cardinality, Field, KindsSrc, KINDS_SRC}, 16 ast_src::{AstEnumSrc, AstNodeSrc, AstSrc, Cardinality, Field, KindsSrc, KINDS_SRC},
17 codegen::{self, update, Mode}, 17 codegen::{self, reformat, update, Mode},
18 project_root, Result, 18 project_root, Result,
19}; 19};
20 20
@@ -61,7 +61,7 @@ fn generate_tokens(grammar: &AstSrc) -> Result<String> {
61 } 61 }
62 }); 62 });
63 63
64 let pretty = crate::reformat(quote! { 64 let pretty = reformat(quote! {
65 use crate::{SyntaxKind::{self, *}, SyntaxToken, ast::AstToken}; 65 use crate::{SyntaxKind::{self, *}, SyntaxToken, ast::AstToken};
66 #(#tokens)* 66 #(#tokens)*
67 })? 67 })?
@@ -261,7 +261,7 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: &AstSrc) -> Result<String> {
261 } 261 }
262 } 262 }
263 263
264 let pretty = crate::reformat(res)?; 264 let pretty = reformat(res)?;
265 Ok(pretty) 265 Ok(pretty)
266} 266}
267 267
@@ -383,7 +383,7 @@ fn generate_syntax_kinds(grammar: KindsSrc<'_>) -> Result<String> {
383 } 383 }
384 }; 384 };
385 385
386 crate::reformat(ast) 386 reformat(ast)
387} 387}
388 388
389fn to_upper_snake_case(s: &str) -> String { 389fn to_upper_snake_case(s: &str) -> String {
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs
index 807ef587c..e790d995f 100644
--- a/xtask/src/lib.rs
+++ b/xtask/src/lib.rs
@@ -3,14 +3,15 @@
3//! See https://github.com/matklad/cargo-xtask/ 3//! See https://github.com/matklad/cargo-xtask/
4 4
5pub mod not_bash; 5pub mod not_bash;
6pub mod codegen;
7mod ast_src;
8
6pub mod install; 9pub mod install;
7pub mod release; 10pub mod release;
8pub mod dist; 11pub mod dist;
9pub mod pre_commit; 12pub mod pre_commit;
10pub mod metrics; 13pub mod metrics;
11 14pub mod pre_cache;
12pub mod codegen;
13mod ast_src;
14 15
15use std::{ 16use std::{
16 env, 17 env,
@@ -21,7 +22,7 @@ use walkdir::{DirEntry, WalkDir};
21 22
22use crate::{ 23use crate::{
23 codegen::Mode, 24 codegen::Mode,
24 not_bash::{fs2, pushd, pushenv, rm_rf}, 25 not_bash::{pushd, pushenv},
25}; 26};
26 27
27pub use anyhow::{bail, Context as _, Result}; 28pub use anyhow::{bail, Context as _, Result};
@@ -62,17 +63,6 @@ pub fn run_rustfmt(mode: Mode) -> Result<()> {
62 Ok(()) 63 Ok(())
63} 64}
64 65
65fn reformat(text: impl std::fmt::Display) -> Result<String> {
66 let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
67 ensure_rustfmt()?;
68 let stdout = run!(
69 "rustfmt --config-path {} --config fn_single_line=true", project_root().join("rustfmt.toml").display();
70 <text.to_string().as_bytes()
71 )?;
72 let preamble = "Generated file, do not edit by hand, see `xtask/src/codegen`";
73 Ok(format!("//! {}\n\n{}\n", preamble, stdout))
74}
75
76fn ensure_rustfmt() -> Result<()> { 66fn ensure_rustfmt() -> Result<()> {
77 let out = run!("rustfmt --version")?; 67 let out = run!("rustfmt --version")?;
78 if !out.contains("stable") { 68 if !out.contains("stable") {
@@ -119,42 +109,6 @@ pub fn run_fuzzer() -> Result<()> {
119 Ok(()) 109 Ok(())
120} 110}
121 111
122/// Cleans the `./target` dir after the build such that only
123/// dependencies are cached on CI.
124pub fn run_pre_cache() -> Result<()> {
125 let slow_tests_cookie = Path::new("./target/.slow_tests_cookie");
126 if !slow_tests_cookie.exists() {
127 panic!("slow tests were skipped on CI!")
128 }
129 rm_rf(slow_tests_cookie)?;
130
131 for entry in Path::new("./target/debug").read_dir()? {
132 let entry = entry?;
133 if entry.file_type().map(|it| it.is_file()).ok() == Some(true) {
134 // Can't delete yourself on windows :-(
135 if !entry.path().ends_with("xtask.exe") {
136 rm_rf(&entry.path())?
137 }
138 }
139 }
140
141 fs2::remove_file("./target/.rustc_info.json")?;
142 let to_delete = ["hir", "heavy_test", "xtask", "ide", "rust-analyzer"];
143 for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
144 for entry in Path::new(dir).read_dir()? {
145 let entry = entry?;
146 if to_delete.iter().any(|&it| entry.path().display().to_string().contains(it)) {
147 // Can't delete yourself on windows :-(
148 if !entry.path().ends_with("xtask.exe") {
149 rm_rf(&entry.path())?
150 }
151 }
152 }
153 }
154
155 Ok(())
156}
157
158fn is_release_tag(tag: &str) -> bool { 112fn is_release_tag(tag: &str) -> bool {
159 tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit()) 113 tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit())
160} 114}
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index 71caff248..c4a15f4bd 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -17,9 +17,10 @@ use xtask::{
17 install::{ClientOpt, InstallCmd, Malloc, ServerOpt}, 17 install::{ClientOpt, InstallCmd, Malloc, ServerOpt},
18 metrics::MetricsCmd, 18 metrics::MetricsCmd,
19 not_bash::pushd, 19 not_bash::pushd,
20 pre_cache::PreCacheCmd,
20 pre_commit, project_root, 21 pre_commit, project_root,
21 release::{PromoteCmd, ReleaseCmd}, 22 release::{PromoteCmd, ReleaseCmd},
22 run_clippy, run_fuzzer, run_pre_cache, run_rustfmt, Result, 23 run_clippy, run_fuzzer, run_rustfmt, Result,
23}; 24};
24 25
25fn main() -> Result<()> { 26fn main() -> Result<()> {
@@ -101,7 +102,7 @@ FLAGS:
101 } 102 }
102 "pre-cache" => { 103 "pre-cache" => {
103 args.finish()?; 104 args.finish()?;
104 run_pre_cache() 105 PreCacheCmd.run()
105 } 106 }
106 "release" => { 107 "release" => {
107 let dry_run = args.contains("--dry-run"); 108 let dry_run = args.contains("--dry-run");
diff --git a/xtask/src/pre_cache.rs b/xtask/src/pre_cache.rs
new file mode 100644
index 000000000..47ba6ba24
--- /dev/null
+++ b/xtask/src/pre_cache.rs
@@ -0,0 +1,80 @@
1use std::{
2 fs::FileType,
3 path::{Path, PathBuf},
4};
5
6use anyhow::Result;
7
8use crate::not_bash::{fs2, rm_rf};
9
10pub struct PreCacheCmd;
11
12impl PreCacheCmd {
13 /// Cleans the `./target` dir after the build such that only
14 /// dependencies are cached on CI.
15 pub fn run(self) -> Result<()> {
16 let slow_tests_cookie = Path::new("./target/.slow_tests_cookie");
17 if !slow_tests_cookie.exists() {
18 panic!("slow tests were skipped on CI!")
19 }
20 rm_rf(slow_tests_cookie)?;
21
22 for path in read_dir("./target/debug", FileType::is_file)? {
23 // Can't delete yourself on windows :-(
24 if !path.ends_with("xtask.exe") {
25 rm_rf(&path)?
26 }
27 }
28
29 fs2::remove_file("./target/.rustc_info.json")?;
30
31 let to_delete = read_dir("./crates", FileType::is_dir)?
32 .into_iter()
33 .map(|path| path.file_name().unwrap().to_string_lossy().replace('-', "_"))
34 .collect::<Vec<_>>();
35
36 for &dir in ["./target/debug/deps", "target/debug/.fingerprint"].iter() {
37 for path in read_dir(dir, |_file_type| true)? {
38 if path.ends_with("xtask.exe") {
39 continue;
40 }
41 let file_name = path.file_name().unwrap().to_string_lossy();
42 let (stem, _) = match rsplit_once(&file_name, '-') {
43 Some(it) => it,
44 None => {
45 rm_rf(path)?;
46 continue;
47 }
48 };
49 let stem = stem.replace('-', "_");
50 if to_delete.contains(&stem) {
51 rm_rf(path)?;
52 }
53 }
54 }
55
56 Ok(())
57 }
58}
59fn read_dir(path: impl AsRef<Path>, cond: impl Fn(&FileType) -> bool) -> Result<Vec<PathBuf>> {
60 read_dir_impl(path.as_ref(), &cond)
61}
62
63fn read_dir_impl(path: &Path, cond: &dyn Fn(&FileType) -> bool) -> Result<Vec<PathBuf>> {
64 let mut res = Vec::new();
65 for entry in path.read_dir()? {
66 let entry = entry?;
67 let file_type = entry.file_type()?;
68 if cond(&file_type) {
69 res.push(entry.path())
70 }
71 }
72 Ok(res)
73}
74
75fn rsplit_once(haystack: &str, delim: char) -> Option<(&str, &str)> {
76 let mut split = haystack.rsplitn(2, delim);
77 let suffix = split.next()?;
78 let prefix = split.next()?;
79 Some((prefix, suffix))
80}
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs
index ca9749ed4..bec3c630b 100644
--- a/xtask/tests/tidy.rs
+++ b/xtask/tests/tidy.rs
@@ -82,7 +82,7 @@ MIT/Apache-2.0
82MIT/Apache-2.0 AND BSD-2-Clause 82MIT/Apache-2.0 AND BSD-2-Clause
83Unlicense OR MIT 83Unlicense OR MIT
84Unlicense/MIT 84Unlicense/MIT
85Zlib 85Zlib OR Apache-2.0 OR MIT
86" 86"
87 .lines() 87 .lines()
88 .filter(|it| !it.is_empty()) 88 .filter(|it| !it.is_empty())