aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock88
-rw-r--r--README.md1
-rw-r--r--crates/paths/Cargo.toml8
-rw-r--r--crates/paths/src/lib.rs123
-rw-r--r--crates/ra_assists/src/assist_context.rs2
-rw-r--r--crates/ra_assists/src/ast_transform.rs2
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs50
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs280
-rw-r--r--crates/ra_assists/src/utils.rs2
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs24
-rw-r--r--crates/ra_hir_def/src/diagnostics.rs3
-rw-r--r--crates/ra_hir_def/src/nameres.rs3
-rw-r--r--crates/ra_hir_def/src/nameres/mod_resolution.rs8
-rw-r--r--crates/ra_hir_def/src/path.rs2
-rw-r--r--crates/ra_hir_ty/src/_match.rs1045
-rw-r--r--crates/ra_ide/src/completion/completion_context.rs1
-rw-r--r--crates/ra_ide/src/diagnostics.rs20
-rw-r--r--crates/ra_ide/src/lib.rs8
-rw-r--r--crates/ra_ide/src/prime_caches.rs2
-rw-r--r--crates/ra_ide/src/references/rename.rs91
-rw-r--r--crates/ra_ide/src/snapshots/highlight_doctest.html28
-rw-r--r--crates/ra_ide/src/snapshots/highlight_injection.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlight_strings.html8
-rw-r--r--crates/ra_ide/src/snapshots/highlight_unsafe.html2
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html10
-rw-r--r--crates/ra_ide/src/snapshots/rainbow_highlighting.html2
-rw-r--r--crates/ra_ide/src/ssr.rs74
-rw-r--r--crates/ra_ide/src/status.rs15
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs122
-rw-r--r--crates/ra_ide/src/syntax_highlighting/html.rs4
-rw-r--r--crates/ra_ide/src/syntax_highlighting/injection.rs16
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs2
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs32
-rw-r--r--crates/ra_ide_db/src/change.rs79
-rw-r--r--crates/ra_ide_db/src/source_change.rs6
-rw-r--r--crates/ra_ide_db/src/symbol_index.rs90
-rw-r--r--crates/ra_parser/src/grammar/expressions.rs6
-rw-r--r--crates/ra_project_model/src/lib.rs32
-rw-r--r--crates/ra_syntax/src/algo.rs8
-rw-r--r--crates/ra_syntax/src/lib.rs3
-rw-r--r--crates/ra_syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast1
-rw-r--r--crates/rust-analyzer/src/bin/main.rs8
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs32
-rw-r--r--crates/rust-analyzer/src/config.rs11
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs6
-rw-r--r--crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_hint.snap86
-rw-r--r--crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_info.snap86
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs213
-rw-r--r--crates/rust-analyzer/src/global_state.rs84
-rw-r--r--crates/rust-analyzer/src/main_loop.rs85
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs9
-rw-r--r--crates/test_utils/src/lib.rs12
-rw-r--r--crates/vfs/Cargo.toml14
-rw-r--r--crates/vfs/src/file_set.rs99
-rw-r--r--crates/vfs/src/lib.rs138
-rw-r--r--crates/vfs/src/loader.rs69
-rw-r--r--crates/vfs/src/path_interner.rs31
-rw-r--r--crates/vfs/src/vfs_path.rs49
-rw-r--r--crates/vfs/src/walkdir_loader.rs108
-rw-r--r--docs/dev/README.md51
-rw-r--r--editors/code/package.json18
62 files changed, 2422 insertions, 1093 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 308e36836..f6d5b900f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10,6 +10,12 @@ dependencies = [
10] 10]
11 11
12[[package]] 12[[package]]
13name = "adler32"
14version = "1.1.0"
15source = "registry+https://github.com/rust-lang/crates.io-index"
16checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
17
18[[package]]
13name = "aho-corasick" 19name = "aho-corasick"
14version = "0.7.10" 20version = "0.7.10"
15source = "registry+https://github.com/rust-lang/crates.io-index" 21source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -55,22 +61,23 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
55 61
56[[package]] 62[[package]]
57name = "backtrace" 63name = "backtrace"
58version = "0.3.48" 64version = "0.3.49"
59source = "registry+https://github.com/rust-lang/crates.io-index" 65source = "registry+https://github.com/rust-lang/crates.io-index"
60checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" 66checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c"
61dependencies = [ 67dependencies = [
62 "addr2line", 68 "addr2line",
63 "cfg-if", 69 "cfg-if",
64 "libc", 70 "libc",
71 "miniz_oxide",
65 "object", 72 "object",
66 "rustc-demangle", 73 "rustc-demangle",
67] 74]
68 75
69[[package]] 76[[package]]
70name = "base64" 77name = "base64"
71version = "0.12.1" 78version = "0.12.2"
72source = "registry+https://github.com/rust-lang/crates.io-index" 79source = "registry+https://github.com/rust-lang/crates.io-index"
73checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" 80checksum = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67"
74 81
75[[package]] 82[[package]]
76name = "bitflags" 83name = "bitflags"
@@ -438,9 +445,9 @@ dependencies = [
438 445
439[[package]] 446[[package]]
440name = "hermit-abi" 447name = "hermit-abi"
441version = "0.1.13" 448version = "0.1.14"
442source = "registry+https://github.com/rust-lang/crates.io-index" 449source = "registry+https://github.com/rust-lang/crates.io-index"
443checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" 450checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
444dependencies = [ 451dependencies = [
445 "libc", 452 "libc",
446] 453]
@@ -695,6 +702,15 @@ dependencies = [
695] 702]
696 703
697[[package]] 704[[package]]
705name = "miniz_oxide"
706version = "0.3.7"
707source = "registry+https://github.com/rust-lang/crates.io-index"
708checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
709dependencies = [
710 "adler32",
711]
712
713[[package]]
698name = "mio" 714name = "mio"
699version = "0.6.22" 715version = "0.6.22"
700source = "registry+https://github.com/rust-lang/crates.io-index" 716source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -778,9 +794,9 @@ dependencies = [
778 794
779[[package]] 795[[package]]
780name = "object" 796name = "object"
781version = "0.19.0" 797version = "0.20.0"
782source = "registry+https://github.com/rust-lang/crates.io-index" 798source = "registry+https://github.com/rust-lang/crates.io-index"
783checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" 799checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
784 800
785[[package]] 801[[package]]
786name = "once_cell" 802name = "once_cell"
@@ -814,9 +830,9 @@ dependencies = [
814 830
815[[package]] 831[[package]]
816name = "paste" 832name = "paste"
817version = "0.1.16" 833version = "0.1.17"
818source = "registry+https://github.com/rust-lang/crates.io-index" 834source = "registry+https://github.com/rust-lang/crates.io-index"
819checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec" 835checksum = "026c63fe245362be0322bfec5a9656d458d13f9cfb1785d1b38458b9968e8080"
820dependencies = [ 836dependencies = [
821 "paste-impl", 837 "paste-impl",
822 "proc-macro-hack", 838 "proc-macro-hack",
@@ -824,17 +840,18 @@ dependencies = [
824 840
825[[package]] 841[[package]]
826name = "paste-impl" 842name = "paste-impl"
827version = "0.1.16" 843version = "0.1.17"
828source = "registry+https://github.com/rust-lang/crates.io-index" 844source = "registry+https://github.com/rust-lang/crates.io-index"
829checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32" 845checksum = "7b9281a268ec213237dcd2aa3c3d0f46681b04ced37c1616fd36567a9e6954b0"
830dependencies = [ 846dependencies = [
831 "proc-macro-hack", 847 "proc-macro-hack",
832 "proc-macro2",
833 "quote",
834 "syn",
835] 848]
836 849
837[[package]] 850[[package]]
851name = "paths"
852version = "0.1.0"
853
854[[package]]
838name = "percent-encoding" 855name = "percent-encoding"
839version = "2.1.0" 856version = "2.1.0"
840source = "registry+https://github.com/rust-lang/crates.io-index" 857source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -852,9 +869,9 @@ dependencies = [
852 869
853[[package]] 870[[package]]
854name = "pico-args" 871name = "pico-args"
855version = "0.3.1" 872version = "0.3.2"
856source = "registry+https://github.com/rust-lang/crates.io-index" 873source = "registry+https://github.com/rust-lang/crates.io-index"
857checksum = "3ad1f1b834a05d42dae330066e9699a173b28185b3bdc3dbf14ca239585de8cc" 874checksum = "6a71836ceac43f0349e3bd964f5bb902f7b003916f32a4ad00354dafc447fa8f"
858 875
859[[package]] 876[[package]]
860name = "plain" 877name = "plain"
@@ -1271,10 +1288,11 @@ dependencies = [
1271 1288
1272[[package]] 1289[[package]]
1273name = "rayon" 1290name = "rayon"
1274version = "1.3.0" 1291version = "1.3.1"
1275source = "registry+https://github.com/rust-lang/crates.io-index" 1292source = "registry+https://github.com/rust-lang/crates.io-index"
1276checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" 1293checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080"
1277dependencies = [ 1294dependencies = [
1295 "autocfg",
1278 "crossbeam-deque", 1296 "crossbeam-deque",
1279 "either", 1297 "either",
1280 "rayon-core", 1298 "rayon-core",
@@ -1282,9 +1300,9 @@ dependencies = [
1282 1300
1283[[package]] 1301[[package]]
1284name = "rayon-core" 1302name = "rayon-core"
1285version = "1.7.0" 1303version = "1.7.1"
1286source = "registry+https://github.com/rust-lang/crates.io-index" 1304source = "registry+https://github.com/rust-lang/crates.io-index"
1287checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" 1305checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280"
1288dependencies = [ 1306dependencies = [
1289 "crossbeam-deque", 1307 "crossbeam-deque",
1290 "crossbeam-queue", 1308 "crossbeam-queue",
@@ -1319,15 +1337,15 @@ checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
1319 1337
1320[[package]] 1338[[package]]
1321name = "relative-path" 1339name = "relative-path"
1322version = "1.0.0" 1340version = "1.2.1"
1323source = "registry+https://github.com/rust-lang/crates.io-index" 1341source = "registry+https://github.com/rust-lang/crates.io-index"
1324checksum = "bedde000f40f2921ce439ea165c9c53fd629bfa115140c72e22aceacb4a21954" 1342checksum = "c602122c47b382cd045b10866a084b184035d45d8c2609cdd3762852ddfae2a1"
1325 1343
1326[[package]] 1344[[package]]
1327name = "remove_dir_all" 1345name = "remove_dir_all"
1328version = "0.5.2" 1346version = "0.5.3"
1329source = "registry+https://github.com/rust-lang/crates.io-index" 1347source = "registry+https://github.com/rust-lang/crates.io-index"
1330checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 1348checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1331dependencies = [ 1349dependencies = [
1332 "winapi 0.3.8", 1350 "winapi 0.3.8",
1333] 1351]
@@ -1501,18 +1519,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
1501 1519
1502[[package]] 1520[[package]]
1503name = "serde" 1521name = "serde"
1504version = "1.0.111" 1522version = "1.0.112"
1505source = "registry+https://github.com/rust-lang/crates.io-index" 1523source = "registry+https://github.com/rust-lang/crates.io-index"
1506checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" 1524checksum = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243"
1507dependencies = [ 1525dependencies = [
1508 "serde_derive", 1526 "serde_derive",
1509] 1527]
1510 1528
1511[[package]] 1529[[package]]
1512name = "serde_derive" 1530name = "serde_derive"
1513version = "1.0.111" 1531version = "1.0.112"
1514source = "registry+https://github.com/rust-lang/crates.io-index" 1532source = "registry+https://github.com/rust-lang/crates.io-index"
1515checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" 1533checksum = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57"
1516dependencies = [ 1534dependencies = [
1517 "proc-macro2", 1535 "proc-macro2",
1518 "quote", 1536 "quote",
@@ -1726,6 +1744,18 @@ dependencies = [
1726] 1744]
1727 1745
1728[[package]] 1746[[package]]
1747name = "vfs"
1748version = "0.1.0"
1749dependencies = [
1750 "crossbeam-channel",
1751 "globset",
1752 "jod-thread",
1753 "paths",
1754 "rustc-hash",
1755 "walkdir",
1756]
1757
1758[[package]]
1729name = "walkdir" 1759name = "walkdir"
1730version = "2.3.1" 1760version = "2.3.1"
1731source = "registry+https://github.com/rust-lang/crates.io-index" 1761source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/README.md b/README.md
index 1e7c3e9b4..7ba705e73 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,7 @@ Work on rust-analyzer is sponsored by
12 12
13[<img src="https://user-images.githubusercontent.com/1711539/58105231-cf306900-7bee-11e9-83d8-9f1102e59d29.png" alt="Ferrous Systems" width="300">](https://ferrous-systems.com/) 13[<img src="https://user-images.githubusercontent.com/1711539/58105231-cf306900-7bee-11e9-83d8-9f1102e59d29.png" alt="Ferrous Systems" width="300">](https://ferrous-systems.com/)
14- [Mozilla](https://www.mozilla.org/en-US/) 14- [Mozilla](https://www.mozilla.org/en-US/)
15- [Embark Studios](https://embark-studios.com/)
15- [freiheit.com](https://www.freiheit.com) 16- [freiheit.com](https://www.freiheit.com)
16 17
17## Quick Start 18## Quick Start
diff --git a/crates/paths/Cargo.toml b/crates/paths/Cargo.toml
new file mode 100644
index 000000000..646ee7fd5
--- /dev/null
+++ b/crates/paths/Cargo.toml
@@ -0,0 +1,8 @@
1[package]
2name = "paths"
3version = "0.1.0"
4authors = ["rust-analyzer developers"]
5edition = "2018"
6
7[lib]
8doctest = false
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs
new file mode 100644
index 000000000..c7ce0c42f
--- /dev/null
+++ b/crates/paths/src/lib.rs
@@ -0,0 +1,123 @@
1//! Thin wrappers around `std::path`, distinguishing between absolute and
2//! relative paths.
3use std::{
4 convert::{TryFrom, TryInto},
5 ops,
6 path::{Component, Path, PathBuf},
7};
8
9#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
10pub struct AbsPathBuf(PathBuf);
11
12impl From<AbsPathBuf> for PathBuf {
13 fn from(AbsPathBuf(path_buf): AbsPathBuf) -> PathBuf {
14 path_buf
15 }
16}
17
18impl ops::Deref for AbsPathBuf {
19 type Target = AbsPath;
20 fn deref(&self) -> &AbsPath {
21 self.as_path()
22 }
23}
24
25impl AsRef<Path> for AbsPathBuf {
26 fn as_ref(&self) -> &Path {
27 self.0.as_path()
28 }
29}
30
31impl TryFrom<PathBuf> for AbsPathBuf {
32 type Error = PathBuf;
33 fn try_from(path_buf: PathBuf) -> Result<AbsPathBuf, PathBuf> {
34 if !path_buf.is_absolute() {
35 return Err(path_buf);
36 }
37 Ok(AbsPathBuf(path_buf))
38 }
39}
40
41impl TryFrom<&str> for AbsPathBuf {
42 type Error = PathBuf;
43 fn try_from(path: &str) -> Result<AbsPathBuf, PathBuf> {
44 AbsPathBuf::try_from(PathBuf::from(path))
45 }
46}
47
48impl AbsPathBuf {
49 pub fn as_path(&self) -> &AbsPath {
50 AbsPath::new_unchecked(self.0.as_path())
51 }
52 pub fn pop(&mut self) -> bool {
53 self.0.pop()
54 }
55}
56
57#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
58#[repr(transparent)]
59pub struct AbsPath(Path);
60
61impl ops::Deref for AbsPath {
62 type Target = Path;
63 fn deref(&self) -> &Path {
64 &self.0
65 }
66}
67
68impl AsRef<Path> for AbsPath {
69 fn as_ref(&self) -> &Path {
70 &self.0
71 }
72}
73
74impl<'a> TryFrom<&'a Path> for &'a AbsPath {
75 type Error = &'a Path;
76 fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> {
77 if !path.is_absolute() {
78 return Err(path);
79 }
80 Ok(AbsPath::new_unchecked(path))
81 }
82}
83
84impl AbsPath {
85 fn new_unchecked(path: &Path) -> &AbsPath {
86 unsafe { &*(path as *const Path as *const AbsPath) }
87 }
88
89 pub fn join(&self, path: impl AsRef<Path>) -> AbsPathBuf {
90 self.as_ref().join(path).try_into().unwrap()
91 }
92 pub fn normalize(&self) -> AbsPathBuf {
93 AbsPathBuf(normalize_path(&self.0))
94 }
95}
96
97// https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85
98fn normalize_path(path: &Path) -> PathBuf {
99 let mut components = path.components().peekable();
100 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
101 components.next();
102 PathBuf::from(c.as_os_str())
103 } else {
104 PathBuf::new()
105 };
106
107 for component in components {
108 match component {
109 Component::Prefix(..) => unreachable!(),
110 Component::RootDir => {
111 ret.push(component.as_os_str());
112 }
113 Component::CurDir => {}
114 Component::ParentDir => {
115 ret.pop();
116 }
117 Component::Normal(c) => {
118 ret.push(c);
119 }
120 }
121 }
122 ret
123}
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index edd8255f4..ee614de72 100644
--- a/crates/ra_assists/src/assist_context.rs
+++ b/crates/ra_assists/src/assist_context.rs
@@ -252,7 +252,7 @@ impl AssistBuilder {
252 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) { 252 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
253 let node = rewriter.rewrite_root().unwrap(); 253 let node = rewriter.rewrite_root().unwrap();
254 let new = rewriter.rewrite(&node); 254 let new = rewriter.rewrite(&node);
255 algo::diff(&node, &new).into_text_edit(&mut self.edit) 255 algo::diff(&node, &new).into_text_edit(&mut self.edit);
256 } 256 }
257 257
258 // FIXME: kill this API 258 // FIXME: kill this API
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
index 3079a02a2..00fa95b6c 100644
--- a/crates/ra_assists/src/ast_transform.rs
+++ b/crates/ra_assists/src/ast_transform.rs
@@ -106,6 +106,7 @@ impl<'a> SubstituteTypeParams<'a> {
106 _ => return None, 106 _ => return None,
107 }; 107 };
108 // FIXME: use `hir::Path::from_src` instead. 108 // FIXME: use `hir::Path::from_src` instead.
109 #[allow(deprecated)]
109 let path = hir::Path::from_ast(path)?; 110 let path = hir::Path::from_ast(path)?;
110 let resolution = self.source_scope.resolve_hir_path(&path)?; 111 let resolution = self.source_scope.resolve_hir_path(&path)?;
111 match resolution { 112 match resolution {
@@ -150,6 +151,7 @@ impl<'a> QualifyPaths<'a> {
150 return None; 151 return None;
151 } 152 }
152 // FIXME: use `hir::Path::from_src` instead. 153 // FIXME: use `hir::Path::from_src` instead.
154 #[allow(deprecated)]
153 let hir_path = hir::Path::from_ast(p.clone()); 155 let hir_path = hir::Path::from_ast(p.clone());
154 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?; 156 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
155 match resolution { 157 match resolution {
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
index cc303285b..569efb768 100644
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ra_assists/src/handlers/fill_match_arms.rs
@@ -136,8 +136,20 @@ fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
136} 136}
137 137
138fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { 138fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
139 let pat_head = pat.syntax().first_child().map(|node| node.text()); 139 let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text());
140 let var_head = var.syntax().first_child().map(|node| node.text()); 140
141 let pat_head = match pat {
142 Pat::BindPat(bind_pat) => {
143 if let Some(p) = bind_pat.pat() {
144 first_node_text(&p)
145 } else {
146 return false;
147 }
148 }
149 pat => first_node_text(pat),
150 };
151
152 let var_head = first_node_text(var);
141 153
142 pat_head == var_head 154 pat_head == var_head
143} 155}
@@ -351,6 +363,40 @@ mod tests {
351 } 363 }
352 364
353 #[test] 365 #[test]
366 fn partial_fill_bind_pat() {
367 check_assist(
368 fill_match_arms,
369 r#"
370 enum A {
371 As,
372 Bs,
373 Cs(Option<i32>),
374 }
375 fn main() {
376 match A::As<|> {
377 A::As(_) => {}
378 a @ A::Bs(_) => {}
379 }
380 }
381 "#,
382 r#"
383 enum A {
384 As,
385 Bs,
386 Cs(Option<i32>),
387 }
388 fn main() {
389 match A::As {
390 A::As(_) => {}
391 a @ A::Bs(_) => {}
392 $0A::Cs(_) => {}
393 }
394 }
395 "#,
396 );
397 }
398
399 #[test]
354 fn fill_match_arms_empty_body() { 400 fn fill_match_arms_empty_body() {
355 check_assist( 401 check_assist(
356 fill_match_arms, 402 fill_match_arms,
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
index 0197a8cf0..b4784c333 100644
--- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
+++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs
@@ -1,7 +1,10 @@
1use hir; 1use hir;
2use ra_syntax::{ast, AstNode, SmolStr, TextRange}; 2use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode};
3 3
4use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; 4use crate::{
5 utils::{find_insert_use_container, insert_use_statement},
6 AssistContext, AssistId, Assists,
7};
5 8
6// Assist: replace_qualified_name_with_use 9// Assist: replace_qualified_name_with_use
7// 10//
@@ -39,16 +42,18 @@ pub(crate) fn replace_qualified_name_with_use(
39 target, 42 target,
40 |builder| { 43 |builder| {
41 let path_to_import = hir_path.mod_path().clone(); 44 let path_to_import = hir_path.mod_path().clone();
45 let container = match find_insert_use_container(path.syntax(), ctx) {
46 Some(c) => c,
47 None => return,
48 };
42 insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); 49 insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder());
43 50
44 if let Some(last) = path.segment() { 51 // Now that we've brought the name into scope, re-qualify all paths that could be
45 // Here we are assuming the assist will provide a correct use statement 52 // affected (that is, all paths inside the node we added the `use` to).
46 // so we can delete the path qualifier 53 let mut rewriter = SyntaxRewriter::default();
47 builder.delete(TextRange::new( 54 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
48 path.syntax().text_range().start(), 55 shorten_paths(&mut rewriter, syntax, path);
49 last.syntax().text_range().start(), 56 builder.rewrite(rewriter);
50 ));
51 }
52 }, 57 },
53 ) 58 )
54} 59}
@@ -73,6 +78,69 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> {
73 Some(ps) 78 Some(ps)
74} 79}
75 80
81/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
82fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: ast::Path) {
83 for child in node.children() {
84 match_ast! {
85 match child {
86 // Don't modify `use` items, as this can break the `use` item when injecting a new
87 // import into the use tree.
88 ast::UseItem(_it) => continue,
89 // Don't descend into submodules, they don't have the same `use` items in scope.
90 ast::Module(_it) => continue,
91
92 ast::Path(p) => {
93 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
94 Some(()) => {},
95 None => shorten_paths(rewriter, p.syntax().clone(), path.clone()),
96 }
97 },
98 _ => shorten_paths(rewriter, child, path.clone()),
99 }
100 }
101 }
102}
103
104fn maybe_replace_path(
105 rewriter: &mut SyntaxRewriter<'static>,
106 path: ast::Path,
107 target: ast::Path,
108) -> Option<()> {
109 if !path_eq(path.clone(), target.clone()) {
110 return None;
111 }
112
113 // Shorten `path`, leaving only its last segment.
114 if let Some(parent) = path.qualifier() {
115 rewriter.delete(parent.syntax());
116 }
117 if let Some(double_colon) = path.coloncolon_token() {
118 rewriter.delete(&double_colon);
119 }
120
121 Some(())
122}
123
124fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
125 let mut lhs_curr = lhs;
126 let mut rhs_curr = rhs;
127 loop {
128 match (lhs_curr.segment(), rhs_curr.segment()) {
129 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
130 _ => return false,
131 }
132
133 match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
134 (Some(lhs), Some(rhs)) => {
135 lhs_curr = lhs;
136 rhs_curr = rhs;
137 }
138 (None, None) => return true,
139 _ => return false,
140 }
141 }
142}
143
76#[cfg(test)] 144#[cfg(test)]
77mod tests { 145mod tests {
78 use crate::tests::{check_assist, check_assist_not_applicable}; 146 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -83,10 +151,10 @@ mod tests {
83 fn test_replace_add_use_no_anchor() { 151 fn test_replace_add_use_no_anchor() {
84 check_assist( 152 check_assist(
85 replace_qualified_name_with_use, 153 replace_qualified_name_with_use,
86 " 154 r"
87std::fmt::Debug<|> 155std::fmt::Debug<|>
88 ", 156 ",
89 " 157 r"
90use std::fmt::Debug; 158use std::fmt::Debug;
91 159
92Debug 160Debug
@@ -97,13 +165,13 @@ Debug
97 fn test_replace_add_use_no_anchor_with_item_below() { 165 fn test_replace_add_use_no_anchor_with_item_below() {
98 check_assist( 166 check_assist(
99 replace_qualified_name_with_use, 167 replace_qualified_name_with_use,
100 " 168 r"
101std::fmt::Debug<|> 169std::fmt::Debug<|>
102 170
103fn main() { 171fn main() {
104} 172}
105 ", 173 ",
106 " 174 r"
107use std::fmt::Debug; 175use std::fmt::Debug;
108 176
109Debug 177Debug
@@ -118,13 +186,13 @@ fn main() {
118 fn test_replace_add_use_no_anchor_with_item_above() { 186 fn test_replace_add_use_no_anchor_with_item_above() {
119 check_assist( 187 check_assist(
120 replace_qualified_name_with_use, 188 replace_qualified_name_with_use,
121 " 189 r"
122fn main() { 190fn main() {
123} 191}
124 192
125std::fmt::Debug<|> 193std::fmt::Debug<|>
126 ", 194 ",
127 " 195 r"
128use std::fmt::Debug; 196use std::fmt::Debug;
129 197
130fn main() { 198fn main() {
@@ -139,10 +207,10 @@ Debug
139 fn test_replace_add_use_no_anchor_2seg() { 207 fn test_replace_add_use_no_anchor_2seg() {
140 check_assist( 208 check_assist(
141 replace_qualified_name_with_use, 209 replace_qualified_name_with_use,
142 " 210 r"
143std::fmt<|>::Debug 211std::fmt<|>::Debug
144 ", 212 ",
145 " 213 r"
146use std::fmt; 214use std::fmt;
147 215
148fmt::Debug 216fmt::Debug
@@ -154,13 +222,13 @@ fmt::Debug
154 fn test_replace_add_use() { 222 fn test_replace_add_use() {
155 check_assist( 223 check_assist(
156 replace_qualified_name_with_use, 224 replace_qualified_name_with_use,
157 " 225 r"
158use stdx; 226use stdx;
159 227
160impl std::fmt::Debug<|> for Foo { 228impl std::fmt::Debug<|> for Foo {
161} 229}
162 ", 230 ",
163 " 231 r"
164use stdx; 232use stdx;
165use std::fmt::Debug; 233use std::fmt::Debug;
166 234
@@ -174,11 +242,11 @@ impl Debug for Foo {
174 fn test_replace_file_use_other_anchor() { 242 fn test_replace_file_use_other_anchor() {
175 check_assist( 243 check_assist(
176 replace_qualified_name_with_use, 244 replace_qualified_name_with_use,
177 " 245 r"
178impl std::fmt::Debug<|> for Foo { 246impl std::fmt::Debug<|> for Foo {
179} 247}
180 ", 248 ",
181 " 249 r"
182use std::fmt::Debug; 250use std::fmt::Debug;
183 251
184impl Debug for Foo { 252impl Debug for Foo {
@@ -191,11 +259,11 @@ impl Debug for Foo {
191 fn test_replace_add_use_other_anchor_indent() { 259 fn test_replace_add_use_other_anchor_indent() {
192 check_assist( 260 check_assist(
193 replace_qualified_name_with_use, 261 replace_qualified_name_with_use,
194 " 262 r"
195 impl std::fmt::Debug<|> for Foo { 263 impl std::fmt::Debug<|> for Foo {
196 } 264 }
197 ", 265 ",
198 " 266 r"
199 use std::fmt::Debug; 267 use std::fmt::Debug;
200 268
201 impl Debug for Foo { 269 impl Debug for Foo {
@@ -208,13 +276,13 @@ impl Debug for Foo {
208 fn test_replace_split_different() { 276 fn test_replace_split_different() {
209 check_assist( 277 check_assist(
210 replace_qualified_name_with_use, 278 replace_qualified_name_with_use,
211 " 279 r"
212use std::fmt; 280use std::fmt;
213 281
214impl std::io<|> for Foo { 282impl std::io<|> for Foo {
215} 283}
216 ", 284 ",
217 " 285 r"
218use std::{io, fmt}; 286use std::{io, fmt};
219 287
220impl io for Foo { 288impl io for Foo {
@@ -227,13 +295,13 @@ impl io for Foo {
227 fn test_replace_split_self_for_use() { 295 fn test_replace_split_self_for_use() {
228 check_assist( 296 check_assist(
229 replace_qualified_name_with_use, 297 replace_qualified_name_with_use,
230 " 298 r"
231use std::fmt; 299use std::fmt;
232 300
233impl std::fmt::Debug<|> for Foo { 301impl std::fmt::Debug<|> for Foo {
234} 302}
235 ", 303 ",
236 " 304 r"
237use std::fmt::{self, Debug, }; 305use std::fmt::{self, Debug, };
238 306
239impl Debug for Foo { 307impl Debug for Foo {
@@ -246,13 +314,13 @@ impl Debug for Foo {
246 fn test_replace_split_self_for_target() { 314 fn test_replace_split_self_for_target() {
247 check_assist( 315 check_assist(
248 replace_qualified_name_with_use, 316 replace_qualified_name_with_use,
249 " 317 r"
250use std::fmt::Debug; 318use std::fmt::Debug;
251 319
252impl std::fmt<|> for Foo { 320impl std::fmt<|> for Foo {
253} 321}
254 ", 322 ",
255 " 323 r"
256use std::fmt::{self, Debug}; 324use std::fmt::{self, Debug};
257 325
258impl fmt for Foo { 326impl fmt for Foo {
@@ -265,13 +333,13 @@ impl fmt for Foo {
265 fn test_replace_add_to_nested_self_nested() { 333 fn test_replace_add_to_nested_self_nested() {
266 check_assist( 334 check_assist(
267 replace_qualified_name_with_use, 335 replace_qualified_name_with_use,
268 " 336 r"
269use std::fmt::{Debug, nested::{Display}}; 337use std::fmt::{Debug, nested::{Display}};
270 338
271impl std::fmt::nested<|> for Foo { 339impl std::fmt::nested<|> for Foo {
272} 340}
273", 341",
274 " 342 r"
275use std::fmt::{Debug, nested::{Display, self}}; 343use std::fmt::{Debug, nested::{Display, self}};
276 344
277impl nested for Foo { 345impl nested for Foo {
@@ -284,13 +352,13 @@ impl nested for Foo {
284 fn test_replace_add_to_nested_self_already_included() { 352 fn test_replace_add_to_nested_self_already_included() {
285 check_assist( 353 check_assist(
286 replace_qualified_name_with_use, 354 replace_qualified_name_with_use,
287 " 355 r"
288use std::fmt::{Debug, nested::{self, Display}}; 356use std::fmt::{Debug, nested::{self, Display}};
289 357
290impl std::fmt::nested<|> for Foo { 358impl std::fmt::nested<|> for Foo {
291} 359}
292", 360",
293 " 361 r"
294use std::fmt::{Debug, nested::{self, Display}}; 362use std::fmt::{Debug, nested::{self, Display}};
295 363
296impl nested for Foo { 364impl nested for Foo {
@@ -303,13 +371,13 @@ impl nested for Foo {
303 fn test_replace_add_to_nested_nested() { 371 fn test_replace_add_to_nested_nested() {
304 check_assist( 372 check_assist(
305 replace_qualified_name_with_use, 373 replace_qualified_name_with_use,
306 " 374 r"
307use std::fmt::{Debug, nested::{Display}}; 375use std::fmt::{Debug, nested::{Display}};
308 376
309impl std::fmt::nested::Debug<|> for Foo { 377impl std::fmt::nested::Debug<|> for Foo {
310} 378}
311", 379",
312 " 380 r"
313use std::fmt::{Debug, nested::{Display, Debug}}; 381use std::fmt::{Debug, nested::{Display, Debug}};
314 382
315impl Debug for Foo { 383impl Debug for Foo {
@@ -322,13 +390,13 @@ impl Debug for Foo {
322 fn test_replace_split_common_target_longer() { 390 fn test_replace_split_common_target_longer() {
323 check_assist( 391 check_assist(
324 replace_qualified_name_with_use, 392 replace_qualified_name_with_use,
325 " 393 r"
326use std::fmt::Debug; 394use std::fmt::Debug;
327 395
328impl std::fmt::nested::Display<|> for Foo { 396impl std::fmt::nested::Display<|> for Foo {
329} 397}
330", 398",
331 " 399 r"
332use std::fmt::{nested::Display, Debug}; 400use std::fmt::{nested::Display, Debug};
333 401
334impl Display for Foo { 402impl Display for Foo {
@@ -341,13 +409,13 @@ impl Display for Foo {
341 fn test_replace_split_common_use_longer() { 409 fn test_replace_split_common_use_longer() {
342 check_assist( 410 check_assist(
343 replace_qualified_name_with_use, 411 replace_qualified_name_with_use,
344 " 412 r"
345use std::fmt::nested::Debug; 413use std::fmt::nested::Debug;
346 414
347impl std::fmt::Display<|> for Foo { 415impl std::fmt::Display<|> for Foo {
348} 416}
349", 417",
350 " 418 r"
351use std::fmt::{Display, nested::Debug}; 419use std::fmt::{Display, nested::Debug};
352 420
353impl Display for Foo { 421impl Display for Foo {
@@ -360,7 +428,7 @@ impl Display for Foo {
360 fn test_replace_use_nested_import() { 428 fn test_replace_use_nested_import() {
361 check_assist( 429 check_assist(
362 replace_qualified_name_with_use, 430 replace_qualified_name_with_use,
363 " 431 r"
364use crate::{ 432use crate::{
365 ty::{Substs, Ty}, 433 ty::{Substs, Ty},
366 AssocItem, 434 AssocItem,
@@ -368,7 +436,7 @@ use crate::{
368 436
369fn foo() { crate::ty::lower<|>::trait_env() } 437fn foo() { crate::ty::lower<|>::trait_env() }
370", 438",
371 " 439 r"
372use crate::{ 440use crate::{
373 ty::{Substs, Ty, lower}, 441 ty::{Substs, Ty, lower},
374 AssocItem, 442 AssocItem,
@@ -383,13 +451,13 @@ fn foo() { lower::trait_env() }
383 fn test_replace_alias() { 451 fn test_replace_alias() {
384 check_assist( 452 check_assist(
385 replace_qualified_name_with_use, 453 replace_qualified_name_with_use,
386 " 454 r"
387use std::fmt as foo; 455use std::fmt as foo;
388 456
389impl foo::Debug<|> for Foo { 457impl foo::Debug<|> for Foo {
390} 458}
391", 459",
392 " 460 r"
393use std::fmt as foo; 461use std::fmt as foo;
394 462
395impl Debug for Foo { 463impl Debug for Foo {
@@ -402,7 +470,7 @@ impl Debug for Foo {
402 fn test_replace_not_applicable_one_segment() { 470 fn test_replace_not_applicable_one_segment() {
403 check_assist_not_applicable( 471 check_assist_not_applicable(
404 replace_qualified_name_with_use, 472 replace_qualified_name_with_use,
405 " 473 r"
406impl foo<|> for Foo { 474impl foo<|> for Foo {
407} 475}
408", 476",
@@ -413,7 +481,7 @@ impl foo<|> for Foo {
413 fn test_replace_not_applicable_in_use() { 481 fn test_replace_not_applicable_in_use() {
414 check_assist_not_applicable( 482 check_assist_not_applicable(
415 replace_qualified_name_with_use, 483 replace_qualified_name_with_use,
416 " 484 r"
417use std::fmt<|>; 485use std::fmt<|>;
418", 486",
419 ); 487 );
@@ -423,14 +491,14 @@ use std::fmt<|>;
423 fn test_replace_add_use_no_anchor_in_mod_mod() { 491 fn test_replace_add_use_no_anchor_in_mod_mod() {
424 check_assist( 492 check_assist(
425 replace_qualified_name_with_use, 493 replace_qualified_name_with_use,
426 " 494 r"
427mod foo { 495mod foo {
428 mod bar { 496 mod bar {
429 std::fmt::Debug<|> 497 std::fmt::Debug<|>
430 } 498 }
431} 499}
432 ", 500 ",
433 " 501 r"
434mod foo { 502mod foo {
435 mod bar { 503 mod bar {
436 use std::fmt::Debug; 504 use std::fmt::Debug;
@@ -446,14 +514,14 @@ mod foo {
446 fn inserts_imports_after_inner_attributes() { 514 fn inserts_imports_after_inner_attributes() {
447 check_assist( 515 check_assist(
448 replace_qualified_name_with_use, 516 replace_qualified_name_with_use,
449 " 517 r"
450#![allow(dead_code)] 518#![allow(dead_code)]
451 519
452fn main() { 520fn main() {
453 std::fmt::Debug<|> 521 std::fmt::Debug<|>
454} 522}
455 ", 523 ",
456 " 524 r"
457#![allow(dead_code)] 525#![allow(dead_code)]
458use std::fmt::Debug; 526use std::fmt::Debug;
459 527
@@ -463,4 +531,116 @@ fn main() {
463 ", 531 ",
464 ); 532 );
465 } 533 }
534
535 #[test]
536 fn replaces_all_affected_paths() {
537 check_assist(
538 replace_qualified_name_with_use,
539 r"
540fn main() {
541 std::fmt::Debug<|>;
542 let x: std::fmt::Debug = std::fmt::Debug;
543}
544 ",
545 r"
546use std::fmt::Debug;
547
548fn main() {
549 Debug;
550 let x: Debug = Debug;
551}
552 ",
553 );
554 }
555
556 #[test]
557 fn replaces_all_affected_paths_mod() {
558 check_assist(
559 replace_qualified_name_with_use,
560 r"
561mod m {
562 fn f() {
563 std::fmt::Debug<|>;
564 let x: std::fmt::Debug = std::fmt::Debug;
565 }
566 fn g() {
567 std::fmt::Debug;
568 }
569}
570
571fn f() {
572 std::fmt::Debug;
573}
574 ",
575 r"
576mod m {
577 use std::fmt::Debug;
578
579 fn f() {
580 Debug;
581 let x: Debug = Debug;
582 }
583 fn g() {
584 Debug;
585 }
586}
587
588fn f() {
589 std::fmt::Debug;
590}
591 ",
592 );
593 }
594
595 #[test]
596 fn does_not_replace_in_submodules() {
597 check_assist(
598 replace_qualified_name_with_use,
599 r"
600fn main() {
601 std::fmt::Debug<|>;
602}
603
604mod sub {
605 fn f() {
606 std::fmt::Debug;
607 }
608}
609 ",
610 r"
611use std::fmt::Debug;
612
613fn main() {
614 Debug;
615}
616
617mod sub {
618 fn f() {
619 std::fmt::Debug;
620 }
621}
622 ",
623 );
624 }
625
626 #[test]
627 fn does_not_replace_in_use() {
628 check_assist(
629 replace_qualified_name_with_use,
630 r"
631use std::fmt::Display;
632
633fn main() {
634 std::fmt<|>;
635}
636 ",
637 r"
638use std::fmt::{self, Display};
639
640fn main() {
641 fmt;
642}
643 ",
644 );
645 }
466} 646}
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs
index 0038a9764..c1ff0de7b 100644
--- a/crates/ra_assists/src/utils.rs
+++ b/crates/ra_assists/src/utils.rs
@@ -13,7 +13,7 @@ use rustc_hash::FxHashSet;
13 13
14use crate::assist_config::SnippetCap; 14use crate::assist_config::SnippetCap;
15 15
16pub(crate) use insert_use::insert_use_statement; 16pub(crate) use insert_use::{find_insert_use_container, insert_use_statement};
17 17
18#[derive(Clone, Copy, Debug)] 18#[derive(Clone, Copy, Debug)]
19pub(crate) enum Cursor<'a> { 19pub(crate) enum Cursor<'a> {
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs
index 0ee43482f..8c4f33e59 100644
--- a/crates/ra_assists/src/utils/insert_use.rs
+++ b/crates/ra_assists/src/utils/insert_use.rs
@@ -12,6 +12,20 @@ use ra_syntax::{
12use ra_text_edit::TextEditBuilder; 12use ra_text_edit::TextEditBuilder;
13 13
14use crate::assist_context::AssistContext; 14use crate::assist_context::AssistContext;
15use either::Either;
16
17/// Determines the containing syntax node in which to insert a `use` statement affecting `position`.
18pub(crate) fn find_insert_use_container(
19 position: &SyntaxNode,
20 ctx: &AssistContext,
21) -> Option<Either<ast::ItemList, ast::SourceFile>> {
22 ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| {
23 if let Some(module) = ast::Module::cast(n.clone()) {
24 return module.item_list().map(|it| Either::Left(it));
25 }
26 Some(Either::Right(ast::SourceFile::cast(n)?))
27 })
28}
15 29
16/// Creates and inserts a use statement for the given path to import. 30/// Creates and inserts a use statement for the given path to import.
17/// The use statement is inserted in the scope most appropriate to the 31/// The use statement is inserted in the scope most appropriate to the
@@ -24,15 +38,11 @@ pub(crate) fn insert_use_statement(
24 builder: &mut TextEditBuilder, 38 builder: &mut TextEditBuilder,
25) { 39) {
26 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>(); 40 let target = path_to_import.to_string().split("::").map(SmolStr::new).collect::<Vec<_>>();
27 let container = ctx.sema.ancestors_with_macros(position.clone()).find_map(|n| { 41 let container = find_insert_use_container(position, ctx);
28 if let Some(module) = ast::Module::cast(n.clone()) {
29 return module.item_list().map(|it| it.syntax().clone());
30 }
31 ast::SourceFile::cast(n).map(|it| it.syntax().clone())
32 });
33 42
34 if let Some(container) = container { 43 if let Some(container) = container {
35 let action = best_action_for_target(container, position.clone(), &target); 44 let syntax = container.either(|l| l.syntax().clone(), |r| r.syntax().clone());
45 let action = best_action_for_target(syntax, position.clone(), &target);
36 make_assist(&action, &target, builder); 46 make_assist(&action, &target, builder);
37 } 47 }
38} 48}
diff --git a/crates/ra_hir_def/src/diagnostics.rs b/crates/ra_hir_def/src/diagnostics.rs
index 510c5e064..30db48f86 100644
--- a/crates/ra_hir_def/src/diagnostics.rs
+++ b/crates/ra_hir_def/src/diagnostics.rs
@@ -3,7 +3,6 @@
3use std::any::Any; 3use std::any::Any;
4 4
5use hir_expand::diagnostics::Diagnostic; 5use hir_expand::diagnostics::Diagnostic;
6use ra_db::RelativePathBuf;
7use ra_syntax::{ast, AstPtr, SyntaxNodePtr}; 6use ra_syntax::{ast, AstPtr, SyntaxNodePtr};
8 7
9use hir_expand::{HirFileId, InFile}; 8use hir_expand::{HirFileId, InFile};
@@ -12,7 +11,7 @@ use hir_expand::{HirFileId, InFile};
12pub struct UnresolvedModule { 11pub struct UnresolvedModule {
13 pub file: HirFileId, 12 pub file: HirFileId,
14 pub decl: AstPtr<ast::Module>, 13 pub decl: AstPtr<ast::Module>,
15 pub candidate: RelativePathBuf, 14 pub candidate: String,
16} 15}
17 16
18impl Diagnostic for UnresolvedModule { 17impl Diagnostic for UnresolvedModule {
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs
index f279c2ad4..b3e5f491a 100644
--- a/crates/ra_hir_def/src/nameres.rs
+++ b/crates/ra_hir_def/src/nameres.rs
@@ -296,7 +296,6 @@ pub enum ModuleSource {
296 296
297mod diagnostics { 297mod diagnostics {
298 use hir_expand::diagnostics::DiagnosticSink; 298 use hir_expand::diagnostics::DiagnosticSink;
299 use ra_db::RelativePathBuf;
300 use ra_syntax::{ast, AstPtr}; 299 use ra_syntax::{ast, AstPtr};
301 300
302 use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId}; 301 use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId};
@@ -306,7 +305,7 @@ mod diagnostics {
306 UnresolvedModule { 305 UnresolvedModule {
307 module: LocalModuleId, 306 module: LocalModuleId,
308 declaration: AstId<ast::Module>, 307 declaration: AstId<ast::Module>,
309 candidate: RelativePathBuf, 308 candidate: String,
310 }, 309 },
311 } 310 }
312 311
diff --git a/crates/ra_hir_def/src/nameres/mod_resolution.rs b/crates/ra_hir_def/src/nameres/mod_resolution.rs
index cede4a6fc..19fe0615a 100644
--- a/crates/ra_hir_def/src/nameres/mod_resolution.rs
+++ b/crates/ra_hir_def/src/nameres/mod_resolution.rs
@@ -44,7 +44,7 @@ impl ModDir {
44 file_id: HirFileId, 44 file_id: HirFileId,
45 name: &Name, 45 name: &Name,
46 attr_path: Option<&SmolStr>, 46 attr_path: Option<&SmolStr>,
47 ) -> Result<(FileId, ModDir), RelativePathBuf> { 47 ) -> Result<(FileId, ModDir), String> {
48 let file_id = file_id.original_file(db.upcast()); 48 let file_id = file_id.original_file(db.upcast());
49 49
50 let mut candidate_files = Vec::new(); 50 let mut candidate_files = Vec::new();
@@ -52,11 +52,11 @@ impl ModDir {
52 Some(attr_path) => { 52 Some(attr_path) => {
53 let base = 53 let base =
54 if self.root_non_dir_owner { self.path.parent().unwrap() } else { &self.path }; 54 if self.root_non_dir_owner { self.path.parent().unwrap() } else { &self.path };
55 candidate_files.push(base.join(attr_path)) 55 candidate_files.push(base.join(attr_path).to_string())
56 } 56 }
57 None => { 57 None => {
58 candidate_files.push(self.path.join(&format!("{}.rs", name))); 58 candidate_files.push(self.path.join(&format!("{}.rs", name)).to_string());
59 candidate_files.push(self.path.join(&format!("{}/mod.rs", name))); 59 candidate_files.push(self.path.join(&format!("{}/mod.rs", name)).to_string());
60 } 60 }
61 }; 61 };
62 62
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs
index ba16442bd..190d6d98d 100644
--- a/crates/ra_hir_def/src/path.rs
+++ b/crates/ra_hir_def/src/path.rs
@@ -154,7 +154,7 @@ 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: It does not handle `$crate` from macro call. 157 #[deprecated = "Doesn't handle hygiene, don't add new calls, remove old ones"]
158 pub fn from_ast(path: ast::Path) -> Option<Path> { 158 pub fn from_ast(path: ast::Path) -> Option<Path> {
159 lower::lower_path(path, &Hygiene::new_unhygienic()) 159 lower::lower_path(path, &Hygiene::new_unhygienic())
160 } 160 }
diff --git a/crates/ra_hir_ty/src/_match.rs b/crates/ra_hir_ty/src/_match.rs
index 3e6e1e333..02a7a61f1 100644
--- a/crates/ra_hir_ty/src/_match.rs
+++ b/crates/ra_hir_ty/src/_match.rs
@@ -8,11 +8,11 @@
8//! This file includes the logic for exhaustiveness and usefulness checking for 8//! This file includes the logic for exhaustiveness and usefulness checking for
9//! pattern-matching. Specifically, given a list of patterns for a type, we can 9//! pattern-matching. Specifically, given a list of patterns for a type, we can
10//! tell whether: 10//! tell whether:
11//! (a) the patterns cover every possible constructor for the type [exhaustiveness] 11//! - (a) the patterns cover every possible constructor for the type (exhaustiveness).
12//! (b) each pattern is necessary [usefulness] 12//! - (b) each pattern is necessary (usefulness).
13//! 13//!
14//! The algorithm implemented here is a modified version of the one described in: 14//! The algorithm implemented here is a modified version of the one described in
15//! http://moscova.inria.fr/~maranget/papers/warn/index.html 15//! <http://moscova.inria.fr/~maranget/papers/warn/index.html>.
16//! However, to save future implementors from reading the original paper, we 16//! However, to save future implementors from reading the original paper, we
17//! summarise the algorithm here to hopefully save time and be a little clearer 17//! summarise the algorithm here to hopefully save time and be a little clearer
18//! (without being so rigorous). 18//! (without being so rigorous).
@@ -37,20 +37,26 @@
37//! new pattern `p`. 37//! new pattern `p`.
38//! 38//!
39//! For example, say we have the following: 39//! For example, say we have the following:
40//!
41//! ```ignore
42//! // x: (Option<bool>, Result<()>)
43//! match x {
44//! (Some(true), _) => {}
45//! (None, Err(())) => {}
46//! (None, Err(_)) => {}
47//! }
40//! ``` 48//! ```
41//! // x: (Option<bool>, Result<()>) 49//!
42//! match x {
43//! (Some(true), _) => {}
44//! (None, Err(())) => {}
45//! (None, Err(_)) => {}
46//! }
47//! ```
48//! Here, the matrix `P` starts as: 50//! Here, the matrix `P` starts as:
51//!
52//! ```text
49//! [ 53//! [
50//! [(Some(true), _)], 54//! [(Some(true), _)],
51//! [(None, Err(()))], 55//! [(None, Err(()))],
52//! [(None, Err(_))], 56//! [(None, Err(_))],
53//! ] 57//! ]
58//! ```
59//!
54//! We can tell it's not exhaustive, because `U(P, _)` is true (we're not covering 60//! We can tell it's not exhaustive, because `U(P, _)` is true (we're not covering
55//! `[(Some(false), _)]`, for instance). In addition, row 3 is not useful, because 61//! `[(Some(false), _)]`, for instance). In addition, row 3 is not useful, because
56//! all the values it covers are already covered by row 2. 62//! all the values it covers are already covered by row 2.
@@ -60,53 +66,61 @@
60//! To match the paper, the top of the stack is at the beginning / on the left. 66//! To match the paper, the top of the stack is at the beginning / on the left.
61//! 67//!
62//! There are two important operations on pattern-stacks necessary to understand the algorithm: 68//! There are two important operations on pattern-stacks necessary to understand the algorithm:
63//! 1. We can pop a given constructor off the top of a stack. This operation is called
64//! `specialize`, and is denoted `S(c, p)` where `c` is a constructor (like `Some` or
65//! `None`) and `p` a pattern-stack.
66//! If the pattern on top of the stack can cover `c`, this removes the constructor and
67//! pushes its arguments onto the stack. It also expands OR-patterns into distinct patterns.
68//! Otherwise the pattern-stack is discarded.
69//! This essentially filters those pattern-stacks whose top covers the constructor `c` and
70//! discards the others.
71//! 69//!
72//! For example, the first pattern above initially gives a stack `[(Some(true), _)]`. If we 70//! 1. We can pop a given constructor off the top of a stack. This operation is called
73//! pop the tuple constructor, we are left with `[Some(true), _]`, and if we then pop the 71//! `specialize`, and is denoted `S(c, p)` where `c` is a constructor (like `Some` or
74//! `Some` constructor we get `[true, _]`. If we had popped `None` instead, we would get 72//! `None`) and `p` a pattern-stack.
75//! nothing back. 73//! If the pattern on top of the stack can cover `c`, this removes the constructor and
74//! pushes its arguments onto the stack. It also expands OR-patterns into distinct patterns.
75//! Otherwise the pattern-stack is discarded.
76//! This essentially filters those pattern-stacks whose top covers the constructor `c` and
77//! discards the others.
78//!
79//! For example, the first pattern above initially gives a stack `[(Some(true), _)]`. If we
80//! pop the tuple constructor, we are left with `[Some(true), _]`, and if we then pop the
81//! `Some` constructor we get `[true, _]`. If we had popped `None` instead, we would get
82//! nothing back.
83//!
84//! This returns zero or more new pattern-stacks, as follows. We look at the pattern `p_1`
85//! on top of the stack, and we have four cases:
86//!
87//! * 1.1. `p_1 = c(r_1, .., r_a)`, i.e. the top of the stack has constructor `c`. We push onto
88//! the stack the arguments of this constructor, and return the result:
76//! 89//!
77//! This returns zero or more new pattern-stacks, as follows. We look at the pattern `p_1` 90//! r_1, .., r_a, p_2, .., p_n
78//! on top of the stack, and we have four cases:
79//! 1.1. `p_1 = c(r_1, .., r_a)`, i.e. the top of the stack has constructor `c`. We
80//! push onto the stack the arguments of this constructor, and return the result:
81//! r_1, .., r_a, p_2, .., p_n
82//! 1.2. `p_1 = c'(r_1, .., r_a')` where `c ≠ c'`. We discard the current stack and
83//! return nothing.
84//! 1.3. `p_1 = _`. We push onto the stack as many wildcards as the constructor `c` has
85//! arguments (its arity), and return the resulting stack:
86//! _, .., _, p_2, .., p_n
87//! 1.4. `p_1 = r_1 | r_2`. We expand the OR-pattern and then recurse on each resulting
88//! stack:
89//! S(c, (r_1, p_2, .., p_n))
90//! S(c, (r_2, p_2, .., p_n))
91//! 91//!
92//! 2. We can pop a wildcard off the top of the stack. This is called `D(p)`, where `p` is 92//! * 1.2. `p_1 = c'(r_1, .., r_a')` where `c ≠ c'`. We discard the current stack and return
93//! a pattern-stack. 93//! nothing.
94//! This is used when we know there are missing constructor cases, but there might be 94//! * 1.3. `p_1 = _`. We push onto the stack as many wildcards as the constructor `c` has
95//! existing wildcard patterns, so to check the usefulness of the matrix, we have to check 95//! arguments (its arity), and return the resulting stack:
96//! all its *other* components.
97//! 96//!
98//! It is computed as follows. We look at the pattern `p_1` on top of the stack, 97//! _, .., _, p_2, .., p_n
99//! and we have three cases:
100//! 1.1. `p_1 = c(r_1, .., r_a)`. We discard the current stack and return nothing.
101//! 1.2. `p_1 = _`. We return the rest of the stack:
102//! p_2, .., p_n
103//! 1.3. `p_1 = r_1 | r_2`. We expand the OR-pattern and then recurse on each resulting
104//! stack.
105//! D((r_1, p_2, .., p_n))
106//! D((r_2, p_2, .., p_n))
107//! 98//!
108//! Note that the OR-patterns are not always used directly in Rust, but are used to derive the 99//! * 1.4. `p_1 = r_1 | r_2`. We expand the OR-pattern and then recurse on each resulting stack:
109//! exhaustive integer matching rules, so they're written here for posterity. 100//!
101//! S(c, (r_1, p_2, .., p_n))
102//! S(c, (r_2, p_2, .., p_n))
103//!
104//! 2. We can pop a wildcard off the top of the stack. This is called `D(p)`, where `p` is
105//! a pattern-stack.
106//! This is used when we know there are missing constructor cases, but there might be
107//! existing wildcard patterns, so to check the usefulness of the matrix, we have to check
108//! all its *other* components.
109//!
110//! It is computed as follows. We look at the pattern `p_1` on top of the stack,
111//! and we have three cases:
112//! * 1.1. `p_1 = c(r_1, .., r_a)`. We discard the current stack and return nothing.
113//! * 1.2. `p_1 = _`. We return the rest of the stack:
114//!
115//! p_2, .., p_n
116//!
117//! * 1.3. `p_1 = r_1 | r_2`. We expand the OR-pattern and then recurse on each resulting stack:
118//!
119//! D((r_1, p_2, .., p_n))
120//! D((r_2, p_2, .., p_n))
121//!
122//! Note that the OR-patterns are not always used directly in Rust, but are used to derive the
123//! exhaustive integer matching rules, so they're written here for posterity.
110//! 124//!
111//! Both those operations extend straightforwardly to a list or pattern-stacks, i.e. a matrix, by 125//! Both those operations extend straightforwardly to a list or pattern-stacks, i.e. a matrix, by
112//! working row-by-row. Popping a constructor ends up keeping only the matrix rows that start with 126//! working row-by-row. Popping a constructor ends up keeping only the matrix rows that start with
@@ -120,73 +134,88 @@
120//! operates principally on the first component of the matrix and new pattern-stack `p`. 134//! operates principally on the first component of the matrix and new pattern-stack `p`.
121//! This algorithm is realised in the `is_useful` function. 135//! This algorithm is realised in the `is_useful` function.
122//! 136//!
123//! Base case. (`n = 0`, i.e., an empty tuple pattern) 137//! Base case (`n = 0`, i.e., an empty tuple pattern):
124//! - If `P` already contains an empty pattern (i.e., if the number of patterns `m > 0`), 138//! - If `P` already contains an empty pattern (i.e., if the number of patterns `m > 0`), then
125//! then `U(P, p)` is false. 139//! `U(P, p)` is false.
126//! - Otherwise, `P` must be empty, so `U(P, p)` is true. 140//! - Otherwise, `P` must be empty, so `U(P, p)` is true.
141//!
142//! Inductive step (`n > 0`, i.e., whether there's at least one column [which may then be expanded
143//! into further columns later]). We're going to match on the top of the new pattern-stack, `p_1`:
144//!
145//! - If `p_1 == c(r_1, .., r_a)`, i.e. we have a constructor pattern.
146//! Then, the usefulness of `p_1` can be reduced to whether it is useful when
147//! we ignore all the patterns in the first column of `P` that involve other constructors.
148//! This is where `S(c, P)` comes in:
127//! 149//!
128//! Inductive step. (`n > 0`, i.e., whether there's at least one column 150//! ```text
129//! [which may then be expanded into further columns later]) 151//! U(P, p) := U(S(c, P), S(c, p))
130//! We're going to match on the top of the new pattern-stack, `p_1`. 152//! ```
131//! - If `p_1 == c(r_1, .., r_a)`, i.e. we have a constructor pattern.
132//! Then, the usefulness of `p_1` can be reduced to whether it is useful when
133//! we ignore all the patterns in the first column of `P` that involve other constructors.
134//! This is where `S(c, P)` comes in:
135//! `U(P, p) := U(S(c, P), S(c, p))`
136//! This special case is handled in `is_useful_specialized`.
137//! 153//!
138//! For example, if `P` is: 154//! This special case is handled in `is_useful_specialized`.
139//! [
140//! [Some(true), _],
141//! [None, 0],
142//! ]
143//! and `p` is [Some(false), 0], then we don't care about row 2 since we know `p` only
144//! matches values that row 2 doesn't. For row 1 however, we need to dig into the
145//! arguments of `Some` to know whether some new value is covered. So we compute
146//! `U([[true, _]], [false, 0])`.
147//! 155//!
148//! - If `p_1 == _`, then we look at the list of constructors that appear in the first 156//! For example, if `P` is:
149//! component of the rows of `P`:
150//! + If there are some constructors that aren't present, then we might think that the
151//! wildcard `_` is useful, since it covers those constructors that weren't covered
152//! before.
153//! That's almost correct, but only works if there were no wildcards in those first
154//! components. So we need to check that `p` is useful with respect to the rows that
155//! start with a wildcard, if there are any. This is where `D` comes in:
156//! `U(P, p) := U(D(P), D(p))`
157//! 157//!
158//! For example, if `P` is: 158//! ```text
159//! [ 159//! [
160//! [_, true, _], 160//! [Some(true), _],
161//! [None, false, 1], 161//! [None, 0],
162//! ] 162//! ]
163//! and `p` is [_, false, _], the `Some` constructor doesn't appear in `P`. So if we 163//! ```
164//! only had row 2, we'd know that `p` is useful. However row 1 starts with a
165//! wildcard, so we need to check whether `U([[true, _]], [false, 1])`.
166//! 164//!
167//! + Otherwise, all possible constructors (for the relevant type) are present. In this 165//! and `p` is `[Some(false), 0]`, then we don't care about row 2 since we know `p` only
168//! case we must check whether the wildcard pattern covers any unmatched value. For 166//! matches values that row 2 doesn't. For row 1 however, we need to dig into the
169//! that, we can think of the `_` pattern as a big OR-pattern that covers all 167//! arguments of `Some` to know whether some new value is covered. So we compute
170//! possible constructors. For `Option`, that would mean `_ = None | Some(_)` for 168//! `U([[true, _]], [false, 0])`.
171//! example. The wildcard pattern is useful in this case if it is useful when
172//! specialized to one of the possible constructors. So we compute:
173//! `U(P, p) := ∃(k ϵ constructors) U(S(k, P), S(k, p))`
174//! 169//!
175//! For example, if `P` is: 170//! - If `p_1 == _`, then we look at the list of constructors that appear in the first component of
176//! [ 171//! the rows of `P`:
177//! [Some(true), _], 172//! - If there are some constructors that aren't present, then we might think that the
178//! [None, false], 173//! wildcard `_` is useful, since it covers those constructors that weren't covered
179//! ] 174//! before.
180//! and `p` is [_, false], both `None` and `Some` constructors appear in the first 175//! That's almost correct, but only works if there were no wildcards in those first
181//! components of `P`. We will therefore try popping both constructors in turn: we 176//! components. So we need to check that `p` is useful with respect to the rows that
182//! compute U([[true, _]], [_, false]) for the `Some` constructor, and U([[false]], 177//! start with a wildcard, if there are any. This is where `D` comes in:
183//! [false]) for the `None` constructor. The first case returns true, so we know that 178//! `U(P, p) := U(D(P), D(p))`
184//! `p` is useful for `P`. Indeed, it matches `[Some(false), _]` that wasn't matched
185//! before.
186//! 179//!
187//! - If `p_1 == r_1 | r_2`, then the usefulness depends on each `r_i` separately: 180//! For example, if `P` is:
188//! `U(P, p) := U(P, (r_1, p_2, .., p_n)) 181//! ```text
189//! || U(P, (r_2, p_2, .., p_n))` 182//! [
183//! [_, true, _],
184//! [None, false, 1],
185//! ]
186//! ```
187//! and `p` is `[_, false, _]`, the `Some` constructor doesn't appear in `P`. So if we
188//! only had row 2, we'd know that `p` is useful. However row 1 starts with a
189//! wildcard, so we need to check whether `U([[true, _]], [false, 1])`.
190//!
191//! - Otherwise, all possible constructors (for the relevant type) are present. In this
192//! case we must check whether the wildcard pattern covers any unmatched value. For
193//! that, we can think of the `_` pattern as a big OR-pattern that covers all
194//! possible constructors. For `Option`, that would mean `_ = None | Some(_)` for
195//! example. The wildcard pattern is useful in this case if it is useful when
196//! specialized to one of the possible constructors. So we compute:
197//! `U(P, p) := ∃(k ϵ constructors) U(S(k, P), S(k, p))`
198//!
199//! For example, if `P` is:
200//! ```text
201//! [
202//! [Some(true), _],
203//! [None, false],
204//! ]
205//! ```
206//! and `p` is `[_, false]`, both `None` and `Some` constructors appear in the first
207//! components of `P`. We will therefore try popping both constructors in turn: we
208//! compute `U([[true, _]], [_, false])` for the `Some` constructor, and `U([[false]],
209//! [false])` for the `None` constructor. The first case returns true, so we know that
210//! `p` is useful for `P`. Indeed, it matches `[Some(false), _]` that wasn't matched
211//! before.
212//!
213//! - If `p_1 == r_1 | r_2`, then the usefulness depends on each `r_i` separately:
214//!
215//! ```text
216//! U(P, p) := U(P, (r_1, p_2, .., p_n))
217//! || U(P, (r_2, p_2, .., p_n))
218//! ```
190use std::sync::Arc; 219use std::sync::Arc;
191 220
192use smallvec::{smallvec, SmallVec}; 221use smallvec::{smallvec, SmallVec};
@@ -333,7 +362,12 @@ impl PatStack {
333 cx: &MatchCheckCtx, 362 cx: &MatchCheckCtx,
334 constructor: &Constructor, 363 constructor: &Constructor,
335 ) -> MatchCheckResult<Option<PatStack>> { 364 ) -> MatchCheckResult<Option<PatStack>> {
336 let result = match (self.head().as_pat(cx), constructor) { 365 if self.is_empty() {
366 return Ok(None);
367 }
368
369 let head_pat = self.head().as_pat(cx);
370 let result = match (head_pat, constructor) {
337 (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => { 371 (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => {
338 if ellipsis.is_some() { 372 if ellipsis.is_some() {
339 // If there are ellipsis here, we should add the correct number of 373 // If there are ellipsis here, we should add the correct number of
@@ -502,7 +536,7 @@ impl Matrix {
502 } 536 }
503 537
504 fn heads(&self) -> Vec<PatIdOrWild> { 538 fn heads(&self) -> Vec<PatIdOrWild> {
505 self.0.iter().map(|p| p.head()).collect() 539 self.0.iter().flat_map(|p| p.get_head()).collect()
506 } 540 }
507 541
508 /// Computes `D(self)` for each contained PatStack. 542 /// Computes `D(self)` for each contained PatStack.
@@ -808,194 +842,193 @@ mod tests {
808 842
809 pub(super) use crate::{diagnostics::MissingMatchArms, test_db::TestDB}; 843 pub(super) use crate::{diagnostics::MissingMatchArms, test_db::TestDB};
810 844
811 pub(super) fn check_diagnostic_message(content: &str) -> String { 845 pub(super) fn check_diagnostic_message(ra_fixture: &str) -> String {
812 TestDB::with_single_file(content).0.diagnostic::<MissingMatchArms>().0 846 TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>().0
813 } 847 }
814 848
815 pub(super) fn check_diagnostic(content: &str) { 849 pub(super) fn check_diagnostic(ra_fixture: &str) {
816 let diagnostic_count = 850 let diagnostic_count =
817 TestDB::with_single_file(content).0.diagnostic::<MissingMatchArms>().1; 851 TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>().1;
818 852
819 assert_eq!(1, diagnostic_count, "no diagnostic reported"); 853 assert_eq!(1, diagnostic_count, "no diagnostic reported");
820 } 854 }
821 855
822 pub(super) fn check_no_diagnostic(content: &str) { 856 pub(super) fn check_no_diagnostic(ra_fixture: &str) {
823 let diagnostic_count = 857 let diagnostic_count =
824 TestDB::with_single_file(content).0.diagnostic::<MissingMatchArms>().1; 858 TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>().1;
825 859
826 assert_eq!(0, diagnostic_count, "expected no diagnostic, found one"); 860 assert_eq!(0, diagnostic_count, "expected no diagnostic, found one");
827 } 861 }
828 862
829 #[test] 863 #[test]
830 fn empty_tuple_no_arms_diagnostic_message() { 864 fn empty_tuple_no_arms_diagnostic_message() {
831 let content = r"
832 fn test_fn() {
833 match () {
834 }
835 }
836 ";
837
838 assert_snapshot!( 865 assert_snapshot!(
839 check_diagnostic_message(content), 866 check_diagnostic_message(r"
867 fn test_fn() {
868 match () {
869 }
870 }
871 "),
840 @"\"()\": Missing match arm\n" 872 @"\"()\": Missing match arm\n"
841 ); 873 );
842 } 874 }
843 875
844 #[test] 876 #[test]
845 fn empty_tuple_no_arms() { 877 fn empty_tuple_no_arms() {
846 let content = r" 878 check_diagnostic(
879 r"
847 fn test_fn() { 880 fn test_fn() {
848 match () { 881 match () {
849 } 882 }
850 } 883 }
851 "; 884 ",
852 885 );
853 check_diagnostic(content);
854 } 886 }
855 887
856 #[test] 888 #[test]
857 fn empty_tuple_wild() { 889 fn empty_tuple_wild() {
858 let content = r" 890 check_no_diagnostic(
891 r"
859 fn test_fn() { 892 fn test_fn() {
860 match () { 893 match () {
861 _ => {} 894 _ => {}
862 } 895 }
863 } 896 }
864 "; 897 ",
865 898 );
866 check_no_diagnostic(content);
867 } 899 }
868 900
869 #[test] 901 #[test]
870 fn empty_tuple_no_diagnostic() { 902 fn empty_tuple_no_diagnostic() {
871 let content = r" 903 check_no_diagnostic(
904 r"
872 fn test_fn() { 905 fn test_fn() {
873 match () { 906 match () {
874 () => {} 907 () => {}
875 } 908 }
876 } 909 }
877 "; 910 ",
878 911 );
879 check_no_diagnostic(content);
880 } 912 }
881 913
882 #[test] 914 #[test]
883 fn tuple_of_empty_tuple_no_arms() { 915 fn tuple_of_empty_tuple_no_arms() {
884 let content = r" 916 check_diagnostic(
917 r"
885 fn test_fn() { 918 fn test_fn() {
886 match (()) { 919 match (()) {
887 } 920 }
888 } 921 }
889 "; 922 ",
890 923 );
891 check_diagnostic(content);
892 } 924 }
893 925
894 #[test] 926 #[test]
895 fn tuple_of_empty_tuple_no_diagnostic() { 927 fn tuple_of_empty_tuple_no_diagnostic() {
896 let content = r" 928 check_no_diagnostic(
929 r"
897 fn test_fn() { 930 fn test_fn() {
898 match (()) { 931 match (()) {
899 (()) => {} 932 (()) => {}
900 } 933 }
901 } 934 }
902 "; 935 ",
903 936 );
904 check_no_diagnostic(content);
905 } 937 }
906 938
907 #[test] 939 #[test]
908 fn tuple_of_two_empty_tuple_no_arms() { 940 fn tuple_of_two_empty_tuple_no_arms() {
909 let content = r" 941 check_diagnostic(
942 r"
910 fn test_fn() { 943 fn test_fn() {
911 match ((), ()) { 944 match ((), ()) {
912 } 945 }
913 } 946 }
914 "; 947 ",
915 948 );
916 check_diagnostic(content);
917 } 949 }
918 950
919 #[test] 951 #[test]
920 fn tuple_of_two_empty_tuple_no_diagnostic() { 952 fn tuple_of_two_empty_tuple_no_diagnostic() {
921 let content = r" 953 check_no_diagnostic(
954 r"
922 fn test_fn() { 955 fn test_fn() {
923 match ((), ()) { 956 match ((), ()) {
924 ((), ()) => {} 957 ((), ()) => {}
925 } 958 }
926 } 959 }
927 "; 960 ",
928 961 );
929 check_no_diagnostic(content);
930 } 962 }
931 963
932 #[test] 964 #[test]
933 fn bool_no_arms() { 965 fn bool_no_arms() {
934 let content = r" 966 check_diagnostic(
967 r"
935 fn test_fn() { 968 fn test_fn() {
936 match false { 969 match false {
937 } 970 }
938 } 971 }
939 "; 972 ",
940 973 );
941 check_diagnostic(content);
942 } 974 }
943 975
944 #[test] 976 #[test]
945 fn bool_missing_arm() { 977 fn bool_missing_arm() {
946 let content = r" 978 check_diagnostic(
979 r"
947 fn test_fn() { 980 fn test_fn() {
948 match false { 981 match false {
949 true => {} 982 true => {}
950 } 983 }
951 } 984 }
952 "; 985 ",
953 986 );
954 check_diagnostic(content);
955 } 987 }
956 988
957 #[test] 989 #[test]
958 fn bool_no_diagnostic() { 990 fn bool_no_diagnostic() {
959 let content = r" 991 check_no_diagnostic(
992 r"
960 fn test_fn() { 993 fn test_fn() {
961 match false { 994 match false {
962 true => {} 995 true => {}
963 false => {} 996 false => {}
964 } 997 }
965 } 998 }
966 "; 999 ",
967 1000 );
968 check_no_diagnostic(content);
969 } 1001 }
970 1002
971 #[test] 1003 #[test]
972 fn tuple_of_bools_no_arms() { 1004 fn tuple_of_bools_no_arms() {
973 let content = r" 1005 check_diagnostic(
1006 r"
974 fn test_fn() { 1007 fn test_fn() {
975 match (false, true) { 1008 match (false, true) {
976 } 1009 }
977 } 1010 }
978 "; 1011 ",
979 1012 );
980 check_diagnostic(content);
981 } 1013 }
982 1014
983 #[test] 1015 #[test]
984 fn tuple_of_bools_missing_arms() { 1016 fn tuple_of_bools_missing_arms() {
985 let content = r" 1017 check_diagnostic(
1018 r"
986 fn test_fn() { 1019 fn test_fn() {
987 match (false, true) { 1020 match (false, true) {
988 (true, true) => {}, 1021 (true, true) => {},
989 } 1022 }
990 } 1023 }
991 "; 1024 ",
992 1025 );
993 check_diagnostic(content);
994 } 1026 }
995 1027
996 #[test] 1028 #[test]
997 fn tuple_of_bools_missing_arm() { 1029 fn tuple_of_bools_missing_arm() {
998 let content = r" 1030 check_diagnostic(
1031 r"
999 fn test_fn() { 1032 fn test_fn() {
1000 match (false, true) { 1033 match (false, true) {
1001 (false, true) => {}, 1034 (false, true) => {},
@@ -1003,14 +1036,14 @@ mod tests {
1003 (true, false) => {}, 1036 (true, false) => {},
1004 } 1037 }
1005 } 1038 }
1006 "; 1039 ",
1007 1040 );
1008 check_diagnostic(content);
1009 } 1041 }
1010 1042
1011 #[test] 1043 #[test]
1012 fn tuple_of_bools_with_wilds() { 1044 fn tuple_of_bools_with_wilds() {
1013 let content = r" 1045 check_no_diagnostic(
1046 r"
1014 fn test_fn() { 1047 fn test_fn() {
1015 match (false, true) { 1048 match (false, true) {
1016 (false, _) => {}, 1049 (false, _) => {},
@@ -1018,14 +1051,14 @@ mod tests {
1018 (_, true) => {}, 1051 (_, true) => {},
1019 } 1052 }
1020 } 1053 }
1021 "; 1054 ",
1022 1055 );
1023 check_no_diagnostic(content);
1024 } 1056 }
1025 1057
1026 #[test] 1058 #[test]
1027 fn tuple_of_bools_no_diagnostic() { 1059 fn tuple_of_bools_no_diagnostic() {
1028 let content = r" 1060 check_no_diagnostic(
1061 r"
1029 fn test_fn() { 1062 fn test_fn() {
1030 match (false, true) { 1063 match (false, true) {
1031 (true, true) => {}, 1064 (true, true) => {},
@@ -1034,27 +1067,27 @@ mod tests {
1034 (false, false) => {}, 1067 (false, false) => {},
1035 } 1068 }
1036 } 1069 }
1037 "; 1070 ",
1038 1071 );
1039 check_no_diagnostic(content);
1040 } 1072 }
1041 1073
1042 #[test] 1074 #[test]
1043 fn tuple_of_bools_binding_missing_arms() { 1075 fn tuple_of_bools_binding_missing_arms() {
1044 let content = r" 1076 check_diagnostic(
1077 r"
1045 fn test_fn() { 1078 fn test_fn() {
1046 match (false, true) { 1079 match (false, true) {
1047 (true, _x) => {}, 1080 (true, _x) => {},
1048 } 1081 }
1049 } 1082 }
1050 "; 1083 ",
1051 1084 );
1052 check_diagnostic(content);
1053 } 1085 }
1054 1086
1055 #[test] 1087 #[test]
1056 fn tuple_of_bools_binding_no_diagnostic() { 1088 fn tuple_of_bools_binding_no_diagnostic() {
1057 let content = r" 1089 check_no_diagnostic(
1090 r"
1058 fn test_fn() { 1091 fn test_fn() {
1059 match (false, true) { 1092 match (false, true) {
1060 (true, _x) => {}, 1093 (true, _x) => {},
@@ -1062,80 +1095,80 @@ mod tests {
1062 (false, false) => {}, 1095 (false, false) => {},
1063 } 1096 }
1064 } 1097 }
1065 "; 1098 ",
1066 1099 );
1067 check_no_diagnostic(content);
1068 } 1100 }
1069 1101
1070 #[test] 1102 #[test]
1071 fn tuple_of_bools_with_ellipsis_at_end_no_diagnostic() { 1103 fn tuple_of_bools_with_ellipsis_at_end_no_diagnostic() {
1072 let content = r" 1104 check_no_diagnostic(
1105 r"
1073 fn test_fn() { 1106 fn test_fn() {
1074 match (false, true, false) { 1107 match (false, true, false) {
1075 (false, ..) => {}, 1108 (false, ..) => {},
1076 (true, ..) => {}, 1109 (true, ..) => {},
1077 } 1110 }
1078 } 1111 }
1079 "; 1112 ",
1080 1113 );
1081 check_no_diagnostic(content);
1082 } 1114 }
1083 1115
1084 #[test] 1116 #[test]
1085 fn tuple_of_bools_with_ellipsis_at_beginning_no_diagnostic() { 1117 fn tuple_of_bools_with_ellipsis_at_beginning_no_diagnostic() {
1086 let content = r" 1118 check_no_diagnostic(
1119 r"
1087 fn test_fn() { 1120 fn test_fn() {
1088 match (false, true, false) { 1121 match (false, true, false) {
1089 (.., false) => {}, 1122 (.., false) => {},
1090 (.., true) => {}, 1123 (.., true) => {},
1091 } 1124 }
1092 } 1125 }
1093 "; 1126 ",
1094 1127 );
1095 check_no_diagnostic(content);
1096 } 1128 }
1097 1129
1098 #[test] 1130 #[test]
1099 fn tuple_of_bools_with_ellipsis_no_diagnostic() { 1131 fn tuple_of_bools_with_ellipsis_no_diagnostic() {
1100 let content = r" 1132 check_no_diagnostic(
1133 r"
1101 fn test_fn() { 1134 fn test_fn() {
1102 match (false, true, false) { 1135 match (false, true, false) {
1103 (..) => {}, 1136 (..) => {},
1104 } 1137 }
1105 } 1138 }
1106 "; 1139 ",
1107 1140 );
1108 check_no_diagnostic(content);
1109 } 1141 }
1110 1142
1111 #[test] 1143 #[test]
1112 fn tuple_of_tuple_and_bools_no_arms() { 1144 fn tuple_of_tuple_and_bools_no_arms() {
1113 let content = r" 1145 check_diagnostic(
1146 r"
1114 fn test_fn() { 1147 fn test_fn() {
1115 match (false, ((), false)) { 1148 match (false, ((), false)) {
1116 } 1149 }
1117 } 1150 }
1118 "; 1151 ",
1119 1152 );
1120 check_diagnostic(content);
1121 } 1153 }
1122 1154
1123 #[test] 1155 #[test]
1124 fn tuple_of_tuple_and_bools_missing_arms() { 1156 fn tuple_of_tuple_and_bools_missing_arms() {
1125 let content = r" 1157 check_diagnostic(
1158 r"
1126 fn test_fn() { 1159 fn test_fn() {
1127 match (false, ((), false)) { 1160 match (false, ((), false)) {
1128 (true, ((), true)) => {}, 1161 (true, ((), true)) => {},
1129 } 1162 }
1130 } 1163 }
1131 "; 1164 ",
1132 1165 );
1133 check_diagnostic(content);
1134 } 1166 }
1135 1167
1136 #[test] 1168 #[test]
1137 fn tuple_of_tuple_and_bools_no_diagnostic() { 1169 fn tuple_of_tuple_and_bools_no_diagnostic() {
1138 let content = r" 1170 check_no_diagnostic(
1171 r"
1139 fn test_fn() { 1172 fn test_fn() {
1140 match (false, ((), false)) { 1173 match (false, ((), false)) {
1141 (true, ((), true)) => {}, 1174 (true, ((), true)) => {},
@@ -1144,27 +1177,27 @@ mod tests {
1144 (false, ((), false)) => {}, 1177 (false, ((), false)) => {},
1145 } 1178 }
1146 } 1179 }
1147 "; 1180 ",
1148 1181 );
1149 check_no_diagnostic(content);
1150 } 1182 }
1151 1183
1152 #[test] 1184 #[test]
1153 fn tuple_of_tuple_and_bools_wildcard_missing_arms() { 1185 fn tuple_of_tuple_and_bools_wildcard_missing_arms() {
1154 let content = r" 1186 check_diagnostic(
1187 r"
1155 fn test_fn() { 1188 fn test_fn() {
1156 match (false, ((), false)) { 1189 match (false, ((), false)) {
1157 (true, _) => {}, 1190 (true, _) => {},
1158 } 1191 }
1159 } 1192 }
1160 "; 1193 ",
1161 1194 );
1162 check_diagnostic(content);
1163 } 1195 }
1164 1196
1165 #[test] 1197 #[test]
1166 fn tuple_of_tuple_and_bools_wildcard_no_diagnostic() { 1198 fn tuple_of_tuple_and_bools_wildcard_no_diagnostic() {
1167 let content = r" 1199 check_no_diagnostic(
1200 r"
1168 fn test_fn() { 1201 fn test_fn() {
1169 match (false, ((), false)) { 1202 match (false, ((), false)) {
1170 (true, ((), true)) => {}, 1203 (true, ((), true)) => {},
@@ -1172,14 +1205,14 @@ mod tests {
1172 (false, _) => {}, 1205 (false, _) => {},
1173 } 1206 }
1174 } 1207 }
1175 "; 1208 ",
1176 1209 );
1177 check_no_diagnostic(content);
1178 } 1210 }
1179 1211
1180 #[test] 1212 #[test]
1181 fn enum_no_arms() { 1213 fn enum_no_arms() {
1182 let content = r" 1214 check_diagnostic(
1215 r"
1183 enum Either { 1216 enum Either {
1184 A, 1217 A,
1185 B, 1218 B,
@@ -1188,14 +1221,14 @@ mod tests {
1188 match Either::A { 1221 match Either::A {
1189 } 1222 }
1190 } 1223 }
1191 "; 1224 ",
1192 1225 );
1193 check_diagnostic(content);
1194 } 1226 }
1195 1227
1196 #[test] 1228 #[test]
1197 fn enum_missing_arms() { 1229 fn enum_missing_arms() {
1198 let content = r" 1230 check_diagnostic(
1231 r"
1199 enum Either { 1232 enum Either {
1200 A, 1233 A,
1201 B, 1234 B,
@@ -1205,14 +1238,14 @@ mod tests {
1205 Either::A => {}, 1238 Either::A => {},
1206 } 1239 }
1207 } 1240 }
1208 "; 1241 ",
1209 1242 );
1210 check_diagnostic(content);
1211 } 1243 }
1212 1244
1213 #[test] 1245 #[test]
1214 fn enum_no_diagnostic() { 1246 fn enum_no_diagnostic() {
1215 let content = r" 1247 check_no_diagnostic(
1248 r"
1216 enum Either { 1249 enum Either {
1217 A, 1250 A,
1218 B, 1251 B,
@@ -1223,14 +1256,14 @@ mod tests {
1223 Either::B => {}, 1256 Either::B => {},
1224 } 1257 }
1225 } 1258 }
1226 "; 1259 ",
1227 1260 );
1228 check_no_diagnostic(content);
1229 } 1261 }
1230 1262
1231 #[test] 1263 #[test]
1232 fn enum_ref_missing_arms() { 1264 fn enum_ref_missing_arms() {
1233 let content = r" 1265 check_diagnostic(
1266 r"
1234 enum Either { 1267 enum Either {
1235 A, 1268 A,
1236 B, 1269 B,
@@ -1240,14 +1273,14 @@ mod tests {
1240 Either::A => {}, 1273 Either::A => {},
1241 } 1274 }
1242 } 1275 }
1243 "; 1276 ",
1244 1277 );
1245 check_diagnostic(content);
1246 } 1278 }
1247 1279
1248 #[test] 1280 #[test]
1249 fn enum_ref_no_diagnostic() { 1281 fn enum_ref_no_diagnostic() {
1250 let content = r" 1282 check_no_diagnostic(
1283 r"
1251 enum Either { 1284 enum Either {
1252 A, 1285 A,
1253 B, 1286 B,
@@ -1258,14 +1291,14 @@ mod tests {
1258 Either::B => {}, 1291 Either::B => {},
1259 } 1292 }
1260 } 1293 }
1261 "; 1294 ",
1262 1295 );
1263 check_no_diagnostic(content);
1264 } 1296 }
1265 1297
1266 #[test] 1298 #[test]
1267 fn enum_containing_bool_no_arms() { 1299 fn enum_containing_bool_no_arms() {
1268 let content = r" 1300 check_diagnostic(
1301 r"
1269 enum Either { 1302 enum Either {
1270 A(bool), 1303 A(bool),
1271 B, 1304 B,
@@ -1274,14 +1307,14 @@ mod tests {
1274 match Either::B { 1307 match Either::B {
1275 } 1308 }
1276 } 1309 }
1277 "; 1310 ",
1278 1311 );
1279 check_diagnostic(content);
1280 } 1312 }
1281 1313
1282 #[test] 1314 #[test]
1283 fn enum_containing_bool_missing_arms() { 1315 fn enum_containing_bool_missing_arms() {
1284 let content = r" 1316 check_diagnostic(
1317 r"
1285 enum Either { 1318 enum Either {
1286 A(bool), 1319 A(bool),
1287 B, 1320 B,
@@ -1292,14 +1325,14 @@ mod tests {
1292 Either::B => (), 1325 Either::B => (),
1293 } 1326 }
1294 } 1327 }
1295 "; 1328 ",
1296 1329 );
1297 check_diagnostic(content);
1298 } 1330 }
1299 1331
1300 #[test] 1332 #[test]
1301 fn enum_containing_bool_no_diagnostic() { 1333 fn enum_containing_bool_no_diagnostic() {
1302 let content = r" 1334 check_no_diagnostic(
1335 r"
1303 enum Either { 1336 enum Either {
1304 A(bool), 1337 A(bool),
1305 B, 1338 B,
@@ -1311,14 +1344,14 @@ mod tests {
1311 Either::B => (), 1344 Either::B => (),
1312 } 1345 }
1313 } 1346 }
1314 "; 1347 ",
1315 1348 );
1316 check_no_diagnostic(content);
1317 } 1349 }
1318 1350
1319 #[test] 1351 #[test]
1320 fn enum_containing_bool_with_wild_no_diagnostic() { 1352 fn enum_containing_bool_with_wild_no_diagnostic() {
1321 let content = r" 1353 check_no_diagnostic(
1354 r"
1322 enum Either { 1355 enum Either {
1323 A(bool), 1356 A(bool),
1324 B, 1357 B,
@@ -1329,14 +1362,14 @@ mod tests {
1329 _ => (), 1362 _ => (),
1330 } 1363 }
1331 } 1364 }
1332 "; 1365 ",
1333 1366 );
1334 check_no_diagnostic(content);
1335 } 1367 }
1336 1368
1337 #[test] 1369 #[test]
1338 fn enum_containing_bool_with_wild_2_no_diagnostic() { 1370 fn enum_containing_bool_with_wild_2_no_diagnostic() {
1339 let content = r" 1371 check_no_diagnostic(
1372 r"
1340 enum Either { 1373 enum Either {
1341 A(bool), 1374 A(bool),
1342 B, 1375 B,
@@ -1347,14 +1380,14 @@ mod tests {
1347 Either::B => (), 1380 Either::B => (),
1348 } 1381 }
1349 } 1382 }
1350 "; 1383 ",
1351 1384 );
1352 check_no_diagnostic(content);
1353 } 1385 }
1354 1386
1355 #[test] 1387 #[test]
1356 fn enum_different_sizes_missing_arms() { 1388 fn enum_different_sizes_missing_arms() {
1357 let content = r" 1389 check_diagnostic(
1390 r"
1358 enum Either { 1391 enum Either {
1359 A(bool), 1392 A(bool),
1360 B(bool, bool), 1393 B(bool, bool),
@@ -1365,14 +1398,14 @@ mod tests {
1365 Either::B(false, _) => (), 1398 Either::B(false, _) => (),
1366 } 1399 }
1367 } 1400 }
1368 "; 1401 ",
1369 1402 );
1370 check_diagnostic(content);
1371 } 1403 }
1372 1404
1373 #[test] 1405 #[test]
1374 fn enum_different_sizes_no_diagnostic() { 1406 fn enum_different_sizes_no_diagnostic() {
1375 let content = r" 1407 check_no_diagnostic(
1408 r"
1376 enum Either { 1409 enum Either {
1377 A(bool), 1410 A(bool),
1378 B(bool, bool), 1411 B(bool, bool),
@@ -1384,14 +1417,14 @@ mod tests {
1384 Either::B(false, _) => (), 1417 Either::B(false, _) => (),
1385 } 1418 }
1386 } 1419 }
1387 "; 1420 ",
1388 1421 );
1389 check_no_diagnostic(content);
1390 } 1422 }
1391 1423
1392 #[test] 1424 #[test]
1393 fn or_no_diagnostic() { 1425 fn or_no_diagnostic() {
1394 let content = r" 1426 check_no_diagnostic(
1427 r"
1395 enum Either { 1428 enum Either {
1396 A(bool), 1429 A(bool),
1397 B(bool, bool), 1430 B(bool, bool),
@@ -1403,14 +1436,14 @@ mod tests {
1403 Either::B(false, _) => (), 1436 Either::B(false, _) => (),
1404 } 1437 }
1405 } 1438 }
1406 "; 1439 ",
1407 1440 );
1408 check_no_diagnostic(content);
1409 } 1441 }
1410 1442
1411 #[test] 1443 #[test]
1412 fn tuple_of_enum_no_diagnostic() { 1444 fn tuple_of_enum_no_diagnostic() {
1413 let content = r" 1445 check_no_diagnostic(
1446 r"
1414 enum Either { 1447 enum Either {
1415 A(bool), 1448 A(bool),
1416 B(bool, bool), 1449 B(bool, bool),
@@ -1427,14 +1460,16 @@ mod tests {
1427 (Either::B(_, _), Either2::D) => (), 1460 (Either::B(_, _), Either2::D) => (),
1428 } 1461 }
1429 } 1462 }
1430 "; 1463 ",
1431 1464 );
1432 check_no_diagnostic(content);
1433 } 1465 }
1434 1466
1435 #[test] 1467 #[test]
1436 fn mismatched_types() { 1468 fn mismatched_types() {
1437 let content = r" 1469 // Match statements with arms that don't match the
1470 // expression pattern do not fire this diagnostic.
1471 check_no_diagnostic(
1472 r"
1438 enum Either { 1473 enum Either {
1439 A, 1474 A,
1440 B, 1475 B,
@@ -1449,47 +1484,47 @@ mod tests {
1449 Either2::D => (), 1484 Either2::D => (),
1450 } 1485 }
1451 } 1486 }
1452 "; 1487 ",
1453 1488 );
1454 // Match statements with arms that don't match the
1455 // expression pattern do not fire this diagnostic.
1456 check_no_diagnostic(content);
1457 } 1489 }
1458 1490
1459 #[test] 1491 #[test]
1460 fn mismatched_types_with_different_arity() { 1492 fn mismatched_types_with_different_arity() {
1461 let content = r" 1493 // Match statements with arms that don't match the
1494 // expression pattern do not fire this diagnostic.
1495 check_no_diagnostic(
1496 r"
1462 fn test_fn() { 1497 fn test_fn() {
1463 match (true, false) { 1498 match (true, false) {
1464 (true, false, true) => (), 1499 (true, false, true) => (),
1465 (true) => (), 1500 (true) => (),
1466 } 1501 }
1467 } 1502 }
1468 "; 1503 ",
1469 1504 );
1470 // Match statements with arms that don't match the
1471 // expression pattern do not fire this diagnostic.
1472 check_no_diagnostic(content);
1473 } 1505 }
1474 1506
1475 #[test] 1507 #[test]
1476 fn malformed_match_arm_tuple_missing_pattern() { 1508 fn malformed_match_arm_tuple_missing_pattern() {
1477 let content = r" 1509 // Match statements with arms that don't match the
1510 // expression pattern do not fire this diagnostic.
1511 check_no_diagnostic(
1512 r"
1478 fn test_fn() { 1513 fn test_fn() {
1479 match (0) { 1514 match (0) {
1480 () => (), 1515 () => (),
1481 } 1516 }
1482 } 1517 }
1483 "; 1518 ",
1484 1519 );
1485 // Match statements with arms that don't match the
1486 // expression pattern do not fire this diagnostic.
1487 check_no_diagnostic(content);
1488 } 1520 }
1489 1521
1490 #[test] 1522 #[test]
1491 fn malformed_match_arm_tuple_enum_missing_pattern() { 1523 fn malformed_match_arm_tuple_enum_missing_pattern() {
1492 let content = r" 1524 // We are testing to be sure we don't panic here when the match
1525 // arm `Either::B` is missing its pattern.
1526 check_no_diagnostic(
1527 r"
1493 enum Either { 1528 enum Either {
1494 A, 1529 A,
1495 B(u32), 1530 B(u32),
@@ -1500,32 +1535,30 @@ mod tests {
1500 Either::B() => (), 1535 Either::B() => (),
1501 } 1536 }
1502 } 1537 }
1503 "; 1538 ",
1504 1539 );
1505 // We are testing to be sure we don't panic here when the match
1506 // arm `Either::B` is missing its pattern.
1507 check_no_diagnostic(content);
1508 } 1540 }
1509 1541
1510 #[test] 1542 #[test]
1511 fn enum_not_in_scope() { 1543 fn enum_not_in_scope() {
1512 let content = r" 1544 // The enum is not in scope so we don't perform exhaustiveness
1545 // checking, but we want to be sure we don't panic here (and
1546 // we don't create a diagnostic).
1547 check_no_diagnostic(
1548 r"
1513 fn test_fn() { 1549 fn test_fn() {
1514 match Foo::Bar { 1550 match Foo::Bar {
1515 Foo::Baz => (), 1551 Foo::Baz => (),
1516 } 1552 }
1517 } 1553 }
1518 "; 1554 ",
1519 1555 );
1520 // The enum is not in scope so we don't perform exhaustiveness
1521 // checking, but we want to be sure we don't panic here (and
1522 // we don't create a diagnostic).
1523 check_no_diagnostic(content);
1524 } 1556 }
1525 1557
1526 #[test] 1558 #[test]
1527 fn expr_diverges() { 1559 fn expr_diverges() {
1528 let content = r" 1560 check_no_diagnostic(
1561 r"
1529 enum Either { 1562 enum Either {
1530 A, 1563 A,
1531 B, 1564 B,
@@ -1536,14 +1569,14 @@ mod tests {
1536 Either::B => (), 1569 Either::B => (),
1537 } 1570 }
1538 } 1571 }
1539 "; 1572 ",
1540 1573 );
1541 check_no_diagnostic(content);
1542 } 1574 }
1543 1575
1544 #[test] 1576 #[test]
1545 fn expr_loop_with_break() { 1577 fn expr_loop_with_break() {
1546 let content = r" 1578 check_no_diagnostic(
1579 r"
1547 enum Either { 1580 enum Either {
1548 A, 1581 A,
1549 B, 1582 B,
@@ -1554,14 +1587,14 @@ mod tests {
1554 Either::B => (), 1587 Either::B => (),
1555 } 1588 }
1556 } 1589 }
1557 "; 1590 ",
1558 1591 );
1559 check_no_diagnostic(content);
1560 } 1592 }
1561 1593
1562 #[test] 1594 #[test]
1563 fn expr_partially_diverges() { 1595 fn expr_partially_diverges() {
1564 let content = r" 1596 check_no_diagnostic(
1597 r"
1565 enum Either<T> { 1598 enum Either<T> {
1566 A(T), 1599 A(T),
1567 B, 1600 B,
@@ -1575,14 +1608,14 @@ mod tests {
1575 Either::B => 0, 1608 Either::B => 0,
1576 } 1609 }
1577 } 1610 }
1578 "; 1611 ",
1579 1612 );
1580 check_no_diagnostic(content);
1581 } 1613 }
1582 1614
1583 #[test] 1615 #[test]
1584 fn enum_record_no_arms() { 1616 fn enum_record_no_arms() {
1585 let content = r" 1617 check_diagnostic(
1618 r"
1586 enum Either { 1619 enum Either {
1587 A { foo: bool }, 1620 A { foo: bool },
1588 B, 1621 B,
@@ -1592,14 +1625,14 @@ mod tests {
1592 match a { 1625 match a {
1593 } 1626 }
1594 } 1627 }
1595 "; 1628 ",
1596 1629 );
1597 check_diagnostic(content);
1598 } 1630 }
1599 1631
1600 #[test] 1632 #[test]
1601 fn enum_record_missing_arms() { 1633 fn enum_record_missing_arms() {
1602 let content = r" 1634 check_diagnostic(
1635 r"
1603 enum Either { 1636 enum Either {
1604 A { foo: bool }, 1637 A { foo: bool },
1605 B, 1638 B,
@@ -1610,14 +1643,14 @@ mod tests {
1610 Either::A { foo: true } => (), 1643 Either::A { foo: true } => (),
1611 } 1644 }
1612 } 1645 }
1613 "; 1646 ",
1614 1647 );
1615 check_diagnostic(content);
1616 } 1648 }
1617 1649
1618 #[test] 1650 #[test]
1619 fn enum_record_no_diagnostic() { 1651 fn enum_record_no_diagnostic() {
1620 let content = r" 1652 check_no_diagnostic(
1653 r"
1621 enum Either { 1654 enum Either {
1622 A { foo: bool }, 1655 A { foo: bool },
1623 B, 1656 B,
@@ -1630,14 +1663,17 @@ mod tests {
1630 Either::B => (), 1663 Either::B => (),
1631 } 1664 }
1632 } 1665 }
1633 "; 1666 ",
1634 1667 );
1635 check_no_diagnostic(content);
1636 } 1668 }
1637 1669
1638 #[test] 1670 #[test]
1639 fn enum_record_missing_field_no_diagnostic() { 1671 fn enum_record_missing_field_no_diagnostic() {
1640 let content = r" 1672 // When `Either::A` is missing a struct member, we don't want
1673 // to fire the missing match arm diagnostic. This should fire
1674 // some other diagnostic.
1675 check_no_diagnostic(
1676 r"
1641 enum Either { 1677 enum Either {
1642 A { foo: bool }, 1678 A { foo: bool },
1643 B, 1679 B,
@@ -1649,17 +1685,16 @@ mod tests {
1649 Either::B => (), 1685 Either::B => (),
1650 } 1686 }
1651 } 1687 }
1652 "; 1688 ",
1653 1689 );
1654 // When `Either::A` is missing a struct member, we don't want
1655 // to fire the missing match arm diagnostic. This should fire
1656 // some other diagnostic.
1657 check_no_diagnostic(content);
1658 } 1690 }
1659 1691
1660 #[test] 1692 #[test]
1661 fn enum_record_missing_field_missing_match_arm() { 1693 fn enum_record_missing_field_missing_match_arm() {
1662 let content = r" 1694 // Even though `Either::A` is missing fields, we still want to fire
1695 // the missing arm diagnostic here, since we know `Either::B` is missing.
1696 check_diagnostic(
1697 r"
1663 enum Either { 1698 enum Either {
1664 A { foo: bool }, 1699 A { foo: bool },
1665 B, 1700 B,
@@ -1670,16 +1705,14 @@ mod tests {
1670 Either::A { } => (), 1705 Either::A { } => (),
1671 } 1706 }
1672 } 1707 }
1673 "; 1708 ",
1674 1709 );
1675 // Even though `Either::A` is missing fields, we still want to fire
1676 // the missing arm diagnostic here, since we know `Either::B` is missing.
1677 check_diagnostic(content);
1678 } 1710 }
1679 1711
1680 #[test] 1712 #[test]
1681 fn enum_record_no_diagnostic_wild() { 1713 fn enum_record_no_diagnostic_wild() {
1682 let content = r" 1714 check_no_diagnostic(
1715 r"
1683 enum Either { 1716 enum Either {
1684 A { foo: bool }, 1717 A { foo: bool },
1685 B, 1718 B,
@@ -1691,14 +1724,14 @@ mod tests {
1691 Either::B => (), 1724 Either::B => (),
1692 } 1725 }
1693 } 1726 }
1694 "; 1727 ",
1695 1728 );
1696 check_no_diagnostic(content);
1697 } 1729 }
1698 1730
1699 #[test] 1731 #[test]
1700 fn enum_record_fields_out_of_order_missing_arm() { 1732 fn enum_record_fields_out_of_order_missing_arm() {
1701 let content = r" 1733 check_diagnostic(
1734 r"
1702 enum Either { 1735 enum Either {
1703 A { foo: bool, bar: () }, 1736 A { foo: bool, bar: () },
1704 B, 1737 B,
@@ -1710,14 +1743,14 @@ mod tests {
1710 Either::A { foo: true, bar: () } => (), 1743 Either::A { foo: true, bar: () } => (),
1711 } 1744 }
1712 } 1745 }
1713 "; 1746 ",
1714 1747 );
1715 check_diagnostic(content);
1716 } 1748 }
1717 1749
1718 #[test] 1750 #[test]
1719 fn enum_record_fields_out_of_order_no_diagnostic() { 1751 fn enum_record_fields_out_of_order_no_diagnostic() {
1720 let content = r" 1752 check_no_diagnostic(
1753 r"
1721 enum Either { 1754 enum Either {
1722 A { foo: bool, bar: () }, 1755 A { foo: bool, bar: () },
1723 B, 1756 B,
@@ -1730,89 +1763,89 @@ mod tests {
1730 Either::B => (), 1763 Either::B => (),
1731 } 1764 }
1732 } 1765 }
1733 "; 1766 ",
1734 1767 );
1735 check_no_diagnostic(content);
1736 } 1768 }
1737 1769
1738 #[test] 1770 #[test]
1739 fn enum_record_ellipsis_missing_arm() { 1771 fn enum_record_ellipsis_missing_arm() {
1740 let content = r" 1772 check_diagnostic(
1741 enum Either { 1773 r"
1742 A { foo: bool, bar: bool }, 1774 enum Either {
1743 B, 1775 A { foo: bool, bar: bool },
1744 } 1776 B,
1745 fn test_fn() { 1777 }
1746 match Either::B { 1778 fn test_fn() {
1747 Either::A { foo: true, .. } => (), 1779 match Either::B {
1748 Either::B => (), 1780 Either::A { foo: true, .. } => (),
1749 } 1781 Either::B => (),
1750 } 1782 }
1751 "; 1783 }
1752 1784 ",
1753 check_diagnostic(content); 1785 );
1754 } 1786 }
1755 1787
1756 #[test] 1788 #[test]
1757 fn enum_record_ellipsis_no_diagnostic() { 1789 fn enum_record_ellipsis_no_diagnostic() {
1758 let content = r" 1790 check_no_diagnostic(
1759 enum Either { 1791 r"
1760 A { foo: bool, bar: bool }, 1792 enum Either {
1761 B, 1793 A { foo: bool, bar: bool },
1762 } 1794 B,
1763 fn test_fn() { 1795 }
1764 let a = Either::A { foo: true }; 1796 fn test_fn() {
1765 match a { 1797 let a = Either::A { foo: true };
1766 Either::A { foo: true, .. } => (), 1798 match a {
1767 Either::A { foo: false, .. } => (), 1799 Either::A { foo: true, .. } => (),
1768 Either::B => (), 1800 Either::A { foo: false, .. } => (),
1769 } 1801 Either::B => (),
1770 } 1802 }
1771 "; 1803 }
1772 1804 ",
1773 check_no_diagnostic(content); 1805 );
1774 } 1806 }
1775 1807
1776 #[test] 1808 #[test]
1777 fn enum_record_ellipsis_all_fields_missing_arm() { 1809 fn enum_record_ellipsis_all_fields_missing_arm() {
1778 let content = r" 1810 check_diagnostic(
1779 enum Either { 1811 r"
1780 A { foo: bool, bar: bool }, 1812 enum Either {
1781 B, 1813 A { foo: bool, bar: bool },
1782 } 1814 B,
1783 fn test_fn() { 1815 }
1784 let a = Either::B; 1816 fn test_fn() {
1785 match a { 1817 let a = Either::B;
1786 Either::A { .. } => (), 1818 match a {
1787 } 1819 Either::A { .. } => (),
1788 } 1820 }
1789 "; 1821 }
1790 1822 ",
1791 check_diagnostic(content); 1823 );
1792 } 1824 }
1793 1825
1794 #[test] 1826 #[test]
1795 fn enum_record_ellipsis_all_fields_no_diagnostic() { 1827 fn enum_record_ellipsis_all_fields_no_diagnostic() {
1796 let content = r" 1828 check_no_diagnostic(
1797 enum Either { 1829 r"
1798 A { foo: bool, bar: bool }, 1830 enum Either {
1799 B, 1831 A { foo: bool, bar: bool },
1800 } 1832 B,
1801 fn test_fn() { 1833 }
1802 let a = Either::B; 1834 fn test_fn() {
1803 match a { 1835 let a = Either::B;
1804 Either::A { .. } => (), 1836 match a {
1805 Either::B => (), 1837 Either::A { .. } => (),
1806 } 1838 Either::B => (),
1807 } 1839 }
1808 "; 1840 }
1809 1841 ",
1810 check_no_diagnostic(content); 1842 );
1811 } 1843 }
1812 1844
1813 #[test] 1845 #[test]
1814 fn enum_tuple_partial_ellipsis_no_diagnostic() { 1846 fn enum_tuple_partial_ellipsis_no_diagnostic() {
1815 let content = r" 1847 check_no_diagnostic(
1848 r"
1816 enum Either { 1849 enum Either {
1817 A(bool, bool, bool, bool), 1850 A(bool, bool, bool, bool),
1818 B, 1851 B,
@@ -1826,14 +1859,14 @@ mod tests {
1826 Either::B => {}, 1859 Either::B => {},
1827 } 1860 }
1828 } 1861 }
1829 "; 1862 ",
1830 1863 );
1831 check_no_diagnostic(content);
1832 } 1864 }
1833 1865
1834 #[test] 1866 #[test]
1835 fn enum_tuple_partial_ellipsis_2_no_diagnostic() { 1867 fn enum_tuple_partial_ellipsis_2_no_diagnostic() {
1836 let content = r" 1868 check_no_diagnostic(
1869 r"
1837 enum Either { 1870 enum Either {
1838 A(bool, bool, bool, bool), 1871 A(bool, bool, bool, bool),
1839 B, 1872 B,
@@ -1847,14 +1880,14 @@ mod tests {
1847 Either::B => {}, 1880 Either::B => {},
1848 } 1881 }
1849 } 1882 }
1850 "; 1883 ",
1851 1884 );
1852 check_no_diagnostic(content);
1853 } 1885 }
1854 1886
1855 #[test] 1887 #[test]
1856 fn enum_tuple_partial_ellipsis_missing_arm() { 1888 fn enum_tuple_partial_ellipsis_missing_arm() {
1857 let content = r" 1889 check_diagnostic(
1890 r"
1858 enum Either { 1891 enum Either {
1859 A(bool, bool, bool, bool), 1892 A(bool, bool, bool, bool),
1860 B, 1893 B,
@@ -1867,14 +1900,14 @@ mod tests {
1867 Either::B => {}, 1900 Either::B => {},
1868 } 1901 }
1869 } 1902 }
1870 "; 1903 ",
1871 1904 );
1872 check_diagnostic(content);
1873 } 1905 }
1874 1906
1875 #[test] 1907 #[test]
1876 fn enum_tuple_partial_ellipsis_2_missing_arm() { 1908 fn enum_tuple_partial_ellipsis_2_missing_arm() {
1877 let content = r" 1909 check_diagnostic(
1910 r"
1878 enum Either { 1911 enum Either {
1879 A(bool, bool, bool, bool), 1912 A(bool, bool, bool, bool),
1880 B, 1913 B,
@@ -1887,14 +1920,14 @@ mod tests {
1887 Either::B => {}, 1920 Either::B => {},
1888 } 1921 }
1889 } 1922 }
1890 "; 1923 ",
1891 1924 );
1892 check_diagnostic(content);
1893 } 1925 }
1894 1926
1895 #[test] 1927 #[test]
1896 fn enum_tuple_ellipsis_no_diagnostic() { 1928 fn enum_tuple_ellipsis_no_diagnostic() {
1897 let content = r" 1929 check_no_diagnostic(
1930 r"
1898 enum Either { 1931 enum Either {
1899 A(bool, bool, bool, bool), 1932 A(bool, bool, bool, bool),
1900 B, 1933 B,
@@ -1905,51 +1938,51 @@ mod tests {
1905 Either::B => {}, 1938 Either::B => {},
1906 } 1939 }
1907 } 1940 }
1908 "; 1941 ",
1909 1942 );
1910 check_no_diagnostic(content);
1911 } 1943 }
1912 1944
1913 #[test] 1945 #[test]
1914 fn enum_never() { 1946 fn enum_never() {
1915 let content = r" 1947 check_no_diagnostic(
1948 r"
1916 enum Never {} 1949 enum Never {}
1917 1950
1918 fn test_fn(never: Never) { 1951 fn test_fn(never: Never) {
1919 match never {} 1952 match never {}
1920 } 1953 }
1921 "; 1954 ",
1922 1955 );
1923 check_no_diagnostic(content);
1924 } 1956 }
1925 1957
1926 #[test] 1958 #[test]
1927 fn type_never() { 1959 fn type_never() {
1928 let content = r" 1960 check_no_diagnostic(
1961 r"
1929 fn test_fn(never: !) { 1962 fn test_fn(never: !) {
1930 match never {} 1963 match never {}
1931 } 1964 }
1932 "; 1965 ",
1933 1966 );
1934 check_no_diagnostic(content);
1935 } 1967 }
1936 1968
1937 #[test] 1969 #[test]
1938 fn enum_never_ref() { 1970 fn enum_never_ref() {
1939 let content = r" 1971 check_no_diagnostic(
1972 r"
1940 enum Never {} 1973 enum Never {}
1941 1974
1942 fn test_fn(never: &Never) { 1975 fn test_fn(never: &Never) {
1943 match never {} 1976 match never {}
1944 } 1977 }
1945 "; 1978 ",
1946 1979 );
1947 check_no_diagnostic(content);
1948 } 1980 }
1949 1981
1950 #[test] 1982 #[test]
1951 fn expr_diverges_missing_arm() { 1983 fn expr_diverges_missing_arm() {
1952 let content = r" 1984 check_no_diagnostic(
1985 r"
1953 enum Either { 1986 enum Either {
1954 A, 1987 A,
1955 B, 1988 B,
@@ -1959,9 +1992,27 @@ mod tests {
1959 Either::A => (), 1992 Either::A => (),
1960 } 1993 }
1961 } 1994 }
1962 "; 1995 ",
1996 );
1997 }
1963 1998
1964 check_no_diagnostic(content); 1999 #[test]
2000 fn or_pattern_panic() {
2001 check_no_diagnostic(
2002 r"
2003 pub enum Category {
2004 Infinity,
2005 Zero,
2006 }
2007
2008 fn panic(a: Category, b: Category) {
2009 match (a, b) {
2010 (Category::Zero | Category::Infinity, _) => {}
2011 (_, Category::Zero | Category::Infinity) => {}
2012 }
2013 }
2014 ",
2015 );
1965 } 2016 }
1966} 2017}
1967 2018
@@ -1981,23 +2032,26 @@ mod false_negatives {
1981 2032
1982 #[test] 2033 #[test]
1983 fn integers() { 2034 fn integers() {
1984 let content = r" 2035 // This is a false negative.
2036 // We don't currently check integer exhaustiveness.
2037 check_no_diagnostic(
2038 r"
1985 fn test_fn() { 2039 fn test_fn() {
1986 match 5 { 2040 match 5 {
1987 10 => (), 2041 10 => (),
1988 11..20 => (), 2042 11..20 => (),
1989 } 2043 }
1990 } 2044 }
1991 "; 2045 ",
1992 2046 );
1993 // This is a false negative.
1994 // We don't currently check integer exhaustiveness.
1995 check_no_diagnostic(content);
1996 } 2047 }
1997 2048
1998 #[test] 2049 #[test]
1999 fn internal_or() { 2050 fn internal_or() {
2000 let content = r" 2051 // This is a false negative.
2052 // We do not currently handle patterns with internal `or`s.
2053 check_no_diagnostic(
2054 r"
2001 fn test_fn() { 2055 fn test_fn() {
2002 enum Either { 2056 enum Either {
2003 A(bool), 2057 A(bool),
@@ -2007,16 +2061,18 @@ mod false_negatives {
2007 Either::A(true | false) => (), 2061 Either::A(true | false) => (),
2008 } 2062 }
2009 } 2063 }
2010 "; 2064 ",
2011 2065 );
2012 // This is a false negative.
2013 // We do not currently handle patterns with internal `or`s.
2014 check_no_diagnostic(content);
2015 } 2066 }
2016 2067
2017 #[test] 2068 #[test]
2018 fn expr_loop_missing_arm() { 2069 fn expr_loop_missing_arm() {
2019 let content = r" 2070 // This is a false negative.
2071 // We currently infer the type of `loop { break Foo::A }` to `!`, which
2072 // causes us to skip the diagnostic since `Either::A` doesn't type check
2073 // with `!`.
2074 check_diagnostic(
2075 r"
2020 enum Either { 2076 enum Either {
2021 A, 2077 A,
2022 B, 2078 B,
@@ -2026,48 +2082,46 @@ mod false_negatives {
2026 Either::A => (), 2082 Either::A => (),
2027 } 2083 }
2028 } 2084 }
2029 "; 2085 ",
2030 2086 );
2031 // This is a false negative.
2032 // We currently infer the type of `loop { break Foo::A }` to `!`, which
2033 // causes us to skip the diagnostic since `Either::A` doesn't type check
2034 // with `!`.
2035 check_diagnostic(content);
2036 } 2087 }
2037 2088
2038 #[test] 2089 #[test]
2039 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() { 2090 fn tuple_of_bools_with_ellipsis_at_end_missing_arm() {
2040 let content = r" 2091 // This is a false negative.
2092 // We don't currently handle tuple patterns with ellipsis.
2093 check_no_diagnostic(
2094 r"
2041 fn test_fn() { 2095 fn test_fn() {
2042 match (false, true, false) { 2096 match (false, true, false) {
2043 (false, ..) => {}, 2097 (false, ..) => {},
2044 } 2098 }
2045 } 2099 }
2046 "; 2100 ",
2047 2101 );
2048 // This is a false negative.
2049 // We don't currently handle tuple patterns with ellipsis.
2050 check_no_diagnostic(content);
2051 } 2102 }
2052 2103
2053 #[test] 2104 #[test]
2054 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() { 2105 fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() {
2055 let content = r" 2106 // This is a false negative.
2107 // We don't currently handle tuple patterns with ellipsis.
2108 check_no_diagnostic(
2109 r"
2056 fn test_fn() { 2110 fn test_fn() {
2057 match (false, true, false) { 2111 match (false, true, false) {
2058 (.., false) => {}, 2112 (.., false) => {},
2059 } 2113 }
2060 } 2114 }
2061 "; 2115 ",
2062 2116 );
2063 // This is a false negative.
2064 // We don't currently handle tuple patterns with ellipsis.
2065 check_no_diagnostic(content);
2066 } 2117 }
2067 2118
2068 #[test] 2119 #[test]
2069 fn struct_missing_arm() { 2120 fn struct_missing_arm() {
2070 let content = r" 2121 // This is a false negative.
2122 // We don't currently handle structs.
2123 check_no_diagnostic(
2124 r"
2071 struct Foo { 2125 struct Foo {
2072 a: bool, 2126 a: bool,
2073 } 2127 }
@@ -2076,10 +2130,7 @@ mod false_negatives {
2076 Foo { a: true } => {}, 2130 Foo { a: true } => {},
2077 } 2131 }
2078 } 2132 }
2079 "; 2133 ",
2080 2134 );
2081 // This is a false negative.
2082 // We don't currently handle structs.
2083 check_no_diagnostic(content);
2084 } 2135 }
2085} 2136}
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs
index 9f4c582d0..560fb19e6 100644
--- a/crates/ra_ide/src/completion/completion_context.rs
+++ b/crates/ra_ide/src/completion/completion_context.rs
@@ -381,6 +381,7 @@ impl<'a> CompletionContext<'a> {
381 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); 381 self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some();
382 self.has_type_args = segment.type_arg_list().is_some(); 382 self.has_type_args = segment.type_arg_list().is_some();
383 383
384 #[allow(deprecated)]
384 if let Some(path) = hir::Path::from_ast(path.clone()) { 385 if let Some(path) = hir::Path::from_ast(path.clone()) {
385 if let Some(path_prefix) = path.qualifier() { 386 if let Some(path_prefix) = path.qualifier() {
386 self.path_prefix = Some(path_prefix); 387 self.path_prefix = Some(path_prefix);
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index e1bfd72f9..fd9abb55b 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -11,7 +11,7 @@ use hir::{
11 Semantics, 11 Semantics,
12}; 12};
13use itertools::Itertools; 13use itertools::Itertools;
14use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; 14use ra_db::SourceDatabase;
15use ra_ide_db::RootDatabase; 15use ra_ide_db::RootDatabase;
16use ra_prof::profile; 16use ra_prof::profile;
17use ra_syntax::{ 17use ra_syntax::{
@@ -57,14 +57,10 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
57 }) 57 })
58 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 58 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
59 let original_file = d.source().file_id.original_file(db); 59 let original_file = d.source().file_id.original_file(db);
60 let source_root = db.file_source_root(original_file); 60 let fix = Fix::new(
61 let path = db 61 "Create module",
62 .file_relative_path(original_file) 62 FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(),
63 .parent() 63 );
64 .unwrap_or_else(|| RelativePath::new(""))
65 .join(&d.candidate);
66 let fix =
67 Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into());
68 res.borrow_mut().push(Diagnostic { 64 res.borrow_mut().push(Diagnostic {
69 range: sema.diagnostics_range(d).range, 65 range: sema.diagnostics_range(d).range,
70 message: d.message(), 66 message: d.message(),
@@ -612,10 +608,10 @@ mod tests {
612 source_file_edits: [], 608 source_file_edits: [],
613 file_system_edits: [ 609 file_system_edits: [
614 CreateFile { 610 CreateFile {
615 source_root: SourceRootId( 611 anchor: FileId(
616 0, 612 1,
617 ), 613 ),
618 path: "foo.rs", 614 dst: "foo.rs",
619 }, 615 },
620 ], 616 ],
621 is_snippet: false, 617 is_snippet: false,
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 28f686767..51dc1f041 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -82,7 +82,7 @@ pub use ra_db::{
82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, 82 Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId,
83}; 83};
84pub use ra_ide_db::{ 84pub use ra_ide_db::{
85 change::{AnalysisChange, LibraryData}, 85 change::AnalysisChange,
86 line_index::{LineCol, LineIndex}, 86 line_index::{LineCol, LineIndex},
87 search::SearchScope, 87 search::SearchScope,
88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
@@ -440,12 +440,14 @@ impl Analysis {
440 440
441 /// Computes syntax highlighting for the given file 441 /// Computes syntax highlighting for the given file
442 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { 442 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
443 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None)) 443 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
444 } 444 }
445 445
446 /// Computes syntax highlighting for the given file range. 446 /// Computes syntax highlighting for the given file range.
447 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> { 447 pub fn highlight_range(&self, frange: FileRange) -> Cancelable<Vec<HighlightedRange>> {
448 self.with_db(|db| syntax_highlighting::highlight(db, frange.file_id, Some(frange.range))) 448 self.with_db(|db| {
449 syntax_highlighting::highlight(db, frange.file_id, Some(frange.range), false)
450 })
449 } 451 }
450 452
451 /// Computes syntax highlighting for the given file. 453 /// Computes syntax highlighting for the given file.
diff --git a/crates/ra_ide/src/prime_caches.rs b/crates/ra_ide/src/prime_caches.rs
index 90bf7d25f..c5ab5a1d8 100644
--- a/crates/ra_ide/src/prime_caches.rs
+++ b/crates/ra_ide/src/prime_caches.rs
@@ -7,6 +7,6 @@ use crate::{FileId, RootDatabase};
7 7
8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { 8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) {
9 for file in files { 9 for file in files {
10 let _ = crate::syntax_highlighting::highlight(db, file, None); 10 let _ = crate::syntax_highlighting::highlight(db, file, None, false);
11 } 11 }
12} 12}
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 915d4f4d3..c4f07f905 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -1,7 +1,7 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{ModuleSource, Semantics}; 3use hir::{ModuleSource, Semantics};
4use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; 4use ra_db::{RelativePathBuf, SourceDatabaseExt};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, 7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind,
@@ -92,23 +92,14 @@ fn rename_mod(
92 ModuleSource::SourceFile(..) => { 92 ModuleSource::SourceFile(..) => {
93 let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); 93 let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id);
94 // mod is defined in path/to/dir/mod.rs 94 // mod is defined in path/to/dir/mod.rs
95 let dst_path = if mod_path.file_stem() == Some("mod") { 95 let dst = if mod_path.file_stem() == Some("mod") {
96 mod_path 96 format!("../{}/mod.rs", new_name)
97 .parent()
98 .and_then(|p| p.parent())
99 .or_else(|| Some(RelativePath::new("")))
100 .map(|p| p.join(new_name).join("mod.rs"))
101 } else { 97 } else {
102 Some(mod_path.with_file_name(new_name).with_extension("rs")) 98 format!("{}.rs", new_name)
103 }; 99 };
104 if let Some(path) = dst_path { 100 let move_file =
105 let move_file = FileSystemEdit::MoveFile { 101 FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst };
106 src: file_id, 102 file_system_edits.push(move_file);
107 dst_source_root: sema.db.file_source_root(position.file_id),
108 dst_path: path,
109 };
110 file_system_edits.push(move_file);
111 }
112 } 103 }
113 ModuleSource::Module(..) => {} 104 ModuleSource::Module(..) => {}
114 } 105 }
@@ -623,16 +614,16 @@ mod tests {
623 #[test] 614 #[test]
624 fn test_rename_mod() { 615 fn test_rename_mod() {
625 let (analysis, position) = analysis_and_position( 616 let (analysis, position) = analysis_and_position(
626 " 617 r#"
627 //- /lib.rs 618//- /lib.rs
628 mod bar; 619mod bar;
629 620
630 //- /bar.rs 621//- /bar.rs
631 mod foo<|>; 622mod foo<|>;
632 623
633 //- /bar/foo.rs 624//- /bar/foo.rs
634 // emtpy 625// emtpy
635 ", 626 "#,
636 ); 627 );
637 let new_name = "foo2"; 628 let new_name = "foo2";
638 let source_change = analysis.rename(position, new_name).unwrap(); 629 let source_change = analysis.rename(position, new_name).unwrap();
@@ -662,10 +653,10 @@ mod tests {
662 src: FileId( 653 src: FileId(
663 3, 654 3,
664 ), 655 ),
665 dst_source_root: SourceRootId( 656 anchor: FileId(
666 0, 657 2,
667 ), 658 ),
668 dst_path: "bar/foo2.rs", 659 dst: "foo2.rs",
669 }, 660 },
670 ], 661 ],
671 is_snippet: false, 662 is_snippet: false,
@@ -678,12 +669,12 @@ mod tests {
678 #[test] 669 #[test]
679 fn test_rename_mod_in_dir() { 670 fn test_rename_mod_in_dir() {
680 let (analysis, position) = analysis_and_position( 671 let (analysis, position) = analysis_and_position(
681 " 672 r#"
682 //- /lib.rs 673//- /lib.rs
683 mod fo<|>o; 674mod fo<|>o;
684 //- /foo/mod.rs 675//- /foo/mod.rs
685 // emtpy 676// emtpy
686 ", 677 "#,
687 ); 678 );
688 let new_name = "foo2"; 679 let new_name = "foo2";
689 let source_change = analysis.rename(position, new_name).unwrap(); 680 let source_change = analysis.rename(position, new_name).unwrap();
@@ -713,10 +704,10 @@ mod tests {
713 src: FileId( 704 src: FileId(
714 2, 705 2,
715 ), 706 ),
716 dst_source_root: SourceRootId( 707 anchor: FileId(
717 0, 708 1,
718 ), 709 ),
719 dst_path: "foo2/mod.rs", 710 dst: "../foo2/mod.rs",
720 }, 711 },
721 ], 712 ],
722 is_snippet: false, 713 is_snippet: false,
@@ -753,19 +744,19 @@ mod tests {
753 #[test] 744 #[test]
754 fn test_rename_mod_filename_and_path() { 745 fn test_rename_mod_filename_and_path() {
755 let (analysis, position) = analysis_and_position( 746 let (analysis, position) = analysis_and_position(
756 " 747 r#"
757 //- /lib.rs 748//- /lib.rs
758 mod bar; 749mod bar;
759 fn f() { 750fn f() {
760 bar::foo::fun() 751 bar::foo::fun()
761 } 752}
762 753
763 //- /bar.rs 754//- /bar.rs
764 pub mod foo<|>; 755pub mod foo<|>;
765 756
766 //- /bar/foo.rs 757//- /bar/foo.rs
767 // pub fn fun() {} 758// pub fn fun() {}
768 ", 759 "#,
769 ); 760 );
770 let new_name = "foo2"; 761 let new_name = "foo2";
771 let source_change = analysis.rename(position, new_name).unwrap(); 762 let source_change = analysis.rename(position, new_name).unwrap();
@@ -808,10 +799,10 @@ mod tests {
808 src: FileId( 799 src: FileId(
809 3, 800 3,
810 ), 801 ),
811 dst_source_root: SourceRootId( 802 anchor: FileId(
812 0, 803 2,
813 ), 804 ),
814 dst_path: "bar/foo2.rs", 805 dst: "foo2.rs",
815 }, 806 },
816 ], 807 ],
817 is_snippet: false, 808 is_snippet: false,
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html
index 0ae8c7efc..f92a0aba5 100644
--- a/crates/ra_ide/src/snapshots/highlight_doctest.html
+++ b/crates/ra_ide/src/snapshots/highlight_doctest.html
@@ -25,22 +25,30 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
31.control { font-style: italic; } 33.control { font-style: italic; }
32</style> 34</style>
33<pre><code><span class="keyword">impl</span> <span class="unresolved_reference">Foo</span> { 35<pre><code><span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
36 <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>,
37}
38
39<span class="keyword">impl</span> <span class="struct">Foo</span> {
40 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration">bar</span>: <span class="builtin_type">bool</span> = <span class="bool_literal">true</span>;
41
34 <span class="comment">/// Constructs a new `Foo`.</span> 42 <span class="comment">/// Constructs a new `Foo`.</span>
35 <span class="comment">///</span> 43 <span class="comment">///</span>
36 <span class="comment">/// # Examples</span> 44 <span class="comment">/// # Examples</span>
37 <span class="comment">///</span> 45 <span class="comment">///</span>
38 <span class="comment">/// ```</span> 46 <span class="comment">/// ```</span>
39 <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> 47 <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span>
40 <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="unresolved_reference">Foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); 48 <span class="comment">/// </span><span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span>: <span class="struct">Foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>();
41 <span class="comment">/// ```</span> 49 <span class="comment">/// ```</span>
42 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="unresolved_reference">Foo</span> { 50 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -&gt; <span class="struct">Foo</span> {
43 <span class="unresolved_reference">Foo</span> { } 51 <span class="struct">Foo</span> { <span class="field">bar</span>: <span class="bool_literal">true</span> }
44 } 52 }
45 53
46 <span class="comment">/// `bar` method on `Foo`.</span> 54 <span class="comment">/// `bar` method on `Foo`.</span>
@@ -48,11 +56,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
48 <span class="comment">/// # Examples</span> 56 <span class="comment">/// # Examples</span>
49 <span class="comment">///</span> 57 <span class="comment">///</span>
50 <span class="comment">/// ```</span> 58 <span class="comment">/// ```</span>
51 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>(); 59 <span class="comment">/// </span><span class="keyword">use</span> <span class="module">x</span>::<span class="module">y</span>;
60 <span class="comment">///</span>
61 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>();
52 <span class="comment">///</span> 62 <span class="comment">///</span>
53 <span class="comment">/// </span><span class="comment">// calls bar on foo</span> 63 <span class="comment">/// </span><span class="comment">// calls bar on foo</span>
54 <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); 64 <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar());
55 <span class="comment">///</span> 65 <span class="comment">///</span>
66 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">bar</span> = <span class="variable">foo</span>.<span class="field">bar</span> || <span class="struct">Foo</span>::<span class="constant">bar</span>;
67 <span class="comment">///</span>
56 <span class="comment">/// </span><span class="comment">/* multi-line 68 <span class="comment">/// </span><span class="comment">/* multi-line
57 </span><span class="comment">/// </span><span class="comment"> comment */</span> 69 </span><span class="comment">/// </span><span class="comment"> comment */</span>
58 <span class="comment">///</span> 70 <span class="comment">///</span>
@@ -62,8 +74,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62 <span class="comment">///</span> 74 <span class="comment">///</span>
63 <span class="comment">/// ```</span> 75 <span class="comment">/// ```</span>
64 <span class="comment">///</span> 76 <span class="comment">///</span>
77 <span class="comment">/// ```rust,no_run</span>
78 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="struct">Foo</span>::<span class="function">new</span>().<span class="function">bar</span>();
65 <span class="comment">/// ```</span> 79 <span class="comment">/// ```</span>
66 <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">foobar</span> = <span class="unresolved_reference">Foo</span>::<span class="unresolved_reference">new</span>().<span class="unresolved_reference">bar</span>(); 80 <span class="comment">///</span>
81 <span class="comment">/// ```sh</span>
82 <span class="comment">/// echo 1</span>
67 <span class="comment">/// ```</span> 83 <span class="comment">/// ```</span>
68 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -&gt; <span class="builtin_type">bool</span> { 84 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -&gt; <span class="builtin_type">bool</span> {
69 <span class="bool_literal">true</span> 85 <span class="bool_literal">true</span>
diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html
index dec06eb51..47dbd7bc8 100644
--- a/crates/ra_ide/src/snapshots/highlight_injection.html
+++ b/crates/ra_ide/src/snapshots/highlight_injection.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html
index 849eb3b73..b46fa44c6 100644
--- a/crates/ra_ide/src/snapshots/highlight_strings.html
+++ b/crates/ra_ide/src/snapshots/highlight_strings.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
@@ -82,6 +84,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
82 84
83 <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>); 85 <span class="macro">println!</span>(<span class="string_literal">r"Hello, </span><span class="format_specifier">{</span><span class="format_specifier">}</span><span class="string_literal">!"</span>, <span class="string_literal">"world"</span>);
84 86
85 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>); 87 <span class="comment">// escape sequences</span>
88 <span class="macro">println!</span>(<span class="string_literal">"Hello</span><span class="escape_sequence">\n</span><span class="string_literal">World"</span>);
89 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="escape_sequence">\u{48}</span><span class="escape_sequence">\x65</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6C</span><span class="escape_sequence">\x6F</span><span class="string_literal"> World"</span>);
90
91 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="escape_sequence">\x41</span><span class="format_specifier">}</span><span class="string_literal">"</span>, A = <span class="numeric_literal">92</span>);
86 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>); 92 <span class="macro">println!</span>(<span class="string_literal">"</span><span class="format_specifier">{</span><span class="variable">ничоси</span><span class="format_specifier">}</span><span class="string_literal">"</span>, ничоси = <span class="numeric_literal">92</span>);
87}</code></pre> \ No newline at end of file 93}</code></pre> \ No newline at end of file
diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html
index bd24e6e38..73438fbb4 100644
--- a/crates/ra_ide/src/snapshots/highlight_unsafe.html
+++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 33548d43c..0c4f0a018 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
@@ -62,6 +64,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62 } 64 }
63} 65}
64 66
67<span class="macro">macro_rules!</span> <span class="macro declaration">noop</span> {
68 ($expr:expr) =&gt; {
69 $expr
70 }
71}
72
65<span class="comment">// comment</span> 73<span class="comment">// comment</span>
66<span class="keyword">fn</span> <span class="function declaration">main</span>() { 74<span class="keyword">fn</span> <span class="function declaration">main</span>() {
67 <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>); 75 <span class="macro">println!</span>(<span class="string_literal">"Hello, {}!"</span>, <span class="numeric_literal">92</span>);
@@ -80,6 +88,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
80 <span class="comment">// Do nothing</span> 88 <span class="comment">// Do nothing</span>
81 } 89 }
82 90
91 <span class="macro">noop!</span>(<span class="macro">noop</span><span class="macro">!</span>(<span class="numeric_literal">1</span>));
92
83 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>; 93 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">x</span> = <span class="numeric_literal">42</span>;
84 <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>; 94 <span class="keyword">let</span> <span class="variable declaration mutable">y</span> = &<span class="keyword">mut</span> <span class="variable mutable">x</span>;
85 <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>; 95 <span class="keyword">let</span> <span class="variable declaration">z</span> = &<span class="variable mutable">y</span>;
diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
index 1ab06182c..a74a70069 100644
--- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html
+++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html
@@ -25,6 +25,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
25.variable { color: #DCDCCC; } 25.variable { color: #DCDCCC; }
26.format_specifier { color: #CC696B; } 26.format_specifier { color: #CC696B; }
27.mutable { text-decoration: underline; } 27.mutable { text-decoration: underline; }
28.unresolved_reference { color: #FC5555; }
29.escape_sequence { color: #94BFF3; }
28 30
29.keyword { color: #F0DFAF; font-weight: bold; } 31.keyword { color: #F0DFAF; font-weight: bold; }
30.keyword.unsafe { color: #BC8383; font-weight: bold; } 32.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index 93e9aee1d..762aab962 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -27,11 +27,11 @@ impl std::error::Error for SsrError {}
27// 27//
28// Search and replace with named wildcards that will match any expression. 28// Search and replace with named wildcards that will match any expression.
29// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. 29// The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`.
30// A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. 30// A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement.
31// Available via the command `rust-analyzer.ssr`. 31// Available via the command `rust-analyzer.ssr`.
32// 32//
33// ```rust 33// ```rust
34// // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] 34// // Using structural search replace command [foo($a, $b) ==>> ($a).foo($b)]
35// 35//
36// // BEFORE 36// // BEFORE
37// String::from(foo(y + 5, z)) 37// String::from(foo(y + 5, z))
@@ -79,7 +79,7 @@ struct SsrPattern {
79 vars: Vec<Var>, 79 vars: Vec<Var>,
80} 80}
81 81
82/// represents an `$var` in an SSR query 82/// Represents a `$var` in an SSR query.
83#[derive(Debug, Clone, PartialEq, Eq, Hash)] 83#[derive(Debug, Clone, PartialEq, Eq, Hash)]
84struct Var(String); 84struct Var(String);
85 85
@@ -122,8 +122,7 @@ impl FromStr for SsrQuery {
122 let mut pattern = it.next().expect("something").to_string(); 122 let mut pattern = it.next().expect("something").to_string();
123 123
124 for part in it.map(split_by_var) { 124 for part in it.map(split_by_var) {
125 let (var, var_type, remainder) = part?; 125 let (var, remainder) = part?;
126 is_expr(var_type)?;
127 let new_var = create_name(var, &mut vars)?; 126 let new_var = create_name(var, &mut vars)?;
128 pattern.push_str(new_var); 127 pattern.push_str(new_var);
129 pattern.push_str(remainder); 128 pattern.push_str(remainder);
@@ -166,15 +165,11 @@ fn traverse(node: &SyntaxNode, go: &mut impl FnMut(&SyntaxNode) -> bool) {
166 } 165 }
167} 166}
168 167
169fn split_by_var(s: &str) -> Result<(&str, &str, &str), SsrError> { 168fn split_by_var(s: &str) -> Result<(&str, &str), SsrError> {
170 let end_of_name = s.find(':').ok_or_else(|| SsrError("Use $<name>:expr".into()))?; 169 let end_of_name = s.find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len());
171 let name = &s[0..end_of_name]; 170 let name = &s[..end_of_name];
172 is_name(name)?; 171 is_name(name)?;
173 let type_begin = end_of_name + 1; 172 Ok((name, &s[end_of_name..]))
174 let type_length =
175 s[type_begin..].find(|c| !char::is_ascii_alphanumeric(&c)).unwrap_or_else(|| s.len());
176 let type_name = &s[type_begin..type_begin + type_length];
177 Ok((name, type_name, &s[type_begin + type_length..]))
178} 173}
179 174
180fn is_name(s: &str) -> Result<(), SsrError> { 175fn is_name(s: &str) -> Result<(), SsrError> {
@@ -185,14 +180,6 @@ fn is_name(s: &str) -> Result<(), SsrError> {
185 } 180 }
186} 181}
187 182
188fn is_expr(s: &str) -> Result<(), SsrError> {
189 if s == "expr" {
190 Ok(())
191 } else {
192 Err(SsrError("Only $<name>:expr is supported".into()))
193 }
194}
195
196fn replace_in_template(template: String, var: &str, new_var: &str) -> String { 183fn replace_in_template(template: String, var: &str, new_var: &str) -> String {
197 let name = format!("${}", var); 184 let name = format!("${}", var);
198 template.replace(&name, new_var) 185 template.replace(&name, new_var)
@@ -450,7 +437,7 @@ mod tests {
450 437
451 #[test] 438 #[test]
452 fn parser_happy_case() { 439 fn parser_happy_case() {
453 let result: SsrQuery = "foo($a:expr, $b:expr) ==>> bar($b, $a)".parse().unwrap(); 440 let result: SsrQuery = "foo($a, $b) ==>> bar($b, $a)".parse().unwrap();
454 assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)"); 441 assert_eq!(&result.pattern.pattern.text(), "foo(__search_pattern_a, __search_pattern_b)");
455 assert_eq!(result.pattern.vars.len(), 2); 442 assert_eq!(result.pattern.vars.len(), 2);
456 assert_eq!(result.pattern.vars[0].0, "__search_pattern_a"); 443 assert_eq!(result.pattern.vars[0].0, "__search_pattern_a");
@@ -477,30 +464,9 @@ mod tests {
477 } 464 }
478 465
479 #[test] 466 #[test]
480 fn parser_no_pattern_type() {
481 assert_eq!(parse_error_text("foo($a) ==>>"), "Parse error: Use $<name>:expr");
482 }
483
484 #[test]
485 fn parser_invalid_name() {
486 assert_eq!(
487 parse_error_text("foo($a+:expr) ==>>"),
488 "Parse error: Name can contain only alphanumerics and _"
489 );
490 }
491
492 #[test]
493 fn parser_invalid_type() {
494 assert_eq!(
495 parse_error_text("foo($a:ident) ==>>"),
496 "Parse error: Only $<name>:expr is supported"
497 );
498 }
499
500 #[test]
501 fn parser_repeated_name() { 467 fn parser_repeated_name() {
502 assert_eq!( 468 assert_eq!(
503 parse_error_text("foo($a:expr, $a:expr) ==>>"), 469 parse_error_text("foo($a, $a) ==>>"),
504 "Parse error: Name `a` repeats more than once" 470 "Parse error: Name `a` repeats more than once"
505 ); 471 );
506 } 472 }
@@ -517,7 +483,7 @@ mod tests {
517 483
518 #[test] 484 #[test]
519 fn parse_match_replace() { 485 fn parse_match_replace() {
520 let query: SsrQuery = "foo($x:expr) ==>> bar($x)".parse().unwrap(); 486 let query: SsrQuery = "foo($x) ==>> bar($x)".parse().unwrap();
521 let input = "fn main() { foo(1+2); }"; 487 let input = "fn main() { foo(1+2); }";
522 488
523 let code = SourceFile::parse(input).tree(); 489 let code = SourceFile::parse(input).tree();
@@ -549,7 +515,7 @@ mod tests {
549 #[test] 515 #[test]
550 fn ssr_function_to_method() { 516 fn ssr_function_to_method() {
551 assert_ssr_transform( 517 assert_ssr_transform(
552 "my_function($a:expr, $b:expr) ==>> ($a).my_method($b)", 518 "my_function($a, $b) ==>> ($a).my_method($b)",
553 "loop { my_function( other_func(x, y), z + w) }", 519 "loop { my_function( other_func(x, y), z + w) }",
554 "loop { (other_func(x, y)).my_method(z + w) }", 520 "loop { (other_func(x, y)).my_method(z + w) }",
555 ) 521 )
@@ -558,7 +524,7 @@ mod tests {
558 #[test] 524 #[test]
559 fn ssr_nested_function() { 525 fn ssr_nested_function() {
560 assert_ssr_transform( 526 assert_ssr_transform(
561 "foo($a:expr, $b:expr, $c:expr) ==>> bar($c, baz($a, $b))", 527 "foo($a, $b, $c) ==>> bar($c, baz($a, $b))",
562 "fn main { foo (x + value.method(b), x+y-z, true && false) }", 528 "fn main { foo (x + value.method(b), x+y-z, true && false) }",
563 "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }", 529 "fn main { bar(true && false, baz(x + value.method(b), x+y-z)) }",
564 ) 530 )
@@ -567,7 +533,7 @@ mod tests {
567 #[test] 533 #[test]
568 fn ssr_expected_spacing() { 534 fn ssr_expected_spacing() {
569 assert_ssr_transform( 535 assert_ssr_transform(
570 "foo($x:expr) + bar() ==>> bar($x)", 536 "foo($x) + bar() ==>> bar($x)",
571 "fn main() { foo(5) + bar() }", 537 "fn main() { foo(5) + bar() }",
572 "fn main() { bar(5) }", 538 "fn main() { bar(5) }",
573 ); 539 );
@@ -576,7 +542,7 @@ mod tests {
576 #[test] 542 #[test]
577 fn ssr_with_extra_space() { 543 fn ssr_with_extra_space() {
578 assert_ssr_transform( 544 assert_ssr_transform(
579 "foo($x:expr ) + bar() ==>> bar($x)", 545 "foo($x ) + bar() ==>> bar($x)",
580 "fn main() { foo( 5 ) +bar( ) }", 546 "fn main() { foo( 5 ) +bar( ) }",
581 "fn main() { bar(5) }", 547 "fn main() { bar(5) }",
582 ); 548 );
@@ -585,7 +551,7 @@ mod tests {
585 #[test] 551 #[test]
586 fn ssr_keeps_nested_comment() { 552 fn ssr_keeps_nested_comment() {
587 assert_ssr_transform( 553 assert_ssr_transform(
588 "foo($x:expr) ==>> bar($x)", 554 "foo($x) ==>> bar($x)",
589 "fn main() { foo(other(5 /* using 5 */)) }", 555 "fn main() { foo(other(5 /* using 5 */)) }",
590 "fn main() { bar(other(5 /* using 5 */)) }", 556 "fn main() { bar(other(5 /* using 5 */)) }",
591 ) 557 )
@@ -594,7 +560,7 @@ mod tests {
594 #[test] 560 #[test]
595 fn ssr_keeps_comment() { 561 fn ssr_keeps_comment() {
596 assert_ssr_transform( 562 assert_ssr_transform(
597 "foo($x:expr) ==>> bar($x)", 563 "foo($x) ==>> bar($x)",
598 "fn main() { foo(5 /* using 5 */) }", 564 "fn main() { foo(5 /* using 5 */) }",
599 "fn main() { bar(5)/* using 5 */ }", 565 "fn main() { bar(5)/* using 5 */ }",
600 ) 566 )
@@ -603,7 +569,7 @@ mod tests {
603 #[test] 569 #[test]
604 fn ssr_struct_lit() { 570 fn ssr_struct_lit() {
605 assert_ssr_transform( 571 assert_ssr_transform(
606 "foo{a: $a:expr, b: $b:expr} ==>> foo::new($a, $b)", 572 "foo{a: $a, b: $b} ==>> foo::new($a, $b)",
607 "fn main() { foo{b:2, a:1} }", 573 "fn main() { foo{b:2, a:1} }",
608 "fn main() { foo::new(1, 2) }", 574 "fn main() { foo::new(1, 2) }",
609 ) 575 )
@@ -612,7 +578,7 @@ mod tests {
612 #[test] 578 #[test]
613 fn ssr_call_and_method_call() { 579 fn ssr_call_and_method_call() {
614 assert_ssr_transform( 580 assert_ssr_transform(
615 "foo::<'a>($a:expr, $b:expr)) ==>> foo2($a, $b)", 581 "foo::<'a>($a, $b)) ==>> foo2($a, $b)",
616 "fn main() { get().bar.foo::<'a>(1); }", 582 "fn main() { get().bar.foo::<'a>(1); }",
617 "fn main() { foo2(get().bar, 1); }", 583 "fn main() { foo2(get().bar, 1); }",
618 ) 584 )
@@ -621,7 +587,7 @@ mod tests {
621 #[test] 587 #[test]
622 fn ssr_method_call_and_call() { 588 fn ssr_method_call_and_call() {
623 assert_ssr_transform( 589 assert_ssr_transform(
624 "$o:expr.foo::<i32>($a:expr)) ==>> $o.foo2($a)", 590 "$o.foo::<i32>($a)) ==>> $o.foo2($a)",
625 "fn main() { X::foo::<i32>(x, 1); }", 591 "fn main() { X::foo::<i32>(x, 1); }",
626 "fn main() { x.foo2(1); }", 592 "fn main() { x.foo2(1); }",
627 ) 593 )
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs
index 5b7992920..45411b357 100644
--- a/crates/ra_ide/src/status.rs
+++ b/crates/ra_ide/src/status.rs
@@ -16,6 +16,7 @@ use ra_prof::{memory_usage, Bytes};
16use ra_syntax::{ast, Parse, SyntaxNode}; 16use ra_syntax::{ast, Parse, SyntaxNode};
17 17
18use crate::FileId; 18use crate::FileId;
19use rustc_hash::FxHashMap;
19 20
20fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { 21fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats {
21 db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>() 22 db.query(ra_db::ParseQuery).entries::<SyntaxTreeStats>()
@@ -123,20 +124,24 @@ struct LibrarySymbolsStats {
123 124
124impl fmt::Display for LibrarySymbolsStats { 125impl fmt::Display for LibrarySymbolsStats {
125 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 126 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
126 write!(fmt, "{} ({}) symbols", self.total, self.size,) 127 write!(fmt, "{} ({}) symbols", self.total, self.size)
127 } 128 }
128} 129}
129 130
130impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats { 131impl FromIterator<TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>
132 for LibrarySymbolsStats
133{
131 fn from_iter<T>(iter: T) -> LibrarySymbolsStats 134 fn from_iter<T>(iter: T) -> LibrarySymbolsStats
132 where 135 where
133 T: IntoIterator<Item = TableEntry<SourceRootId, Arc<SymbolIndex>>>, 136 T: IntoIterator<Item = TableEntry<(), Arc<FxHashMap<SourceRootId, SymbolIndex>>>>,
134 { 137 {
135 let mut res = LibrarySymbolsStats::default(); 138 let mut res = LibrarySymbolsStats::default();
136 for entry in iter { 139 for entry in iter {
137 let value = entry.value.unwrap(); 140 let value = entry.value.unwrap();
138 res.total += value.len(); 141 for symbols in value.values() {
139 res.size += value.memory_size(); 142 res.total += symbols.len();
143 res.size += symbols.memory_size();
144 }
140 } 145 }
141 res 146 res
142 } 147 }
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index ab45c364a..854b6cc6d 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -44,6 +44,7 @@ pub(crate) fn highlight(
44 db: &RootDatabase, 44 db: &RootDatabase,
45 file_id: FileId, 45 file_id: FileId,
46 range_to_highlight: Option<TextRange>, 46 range_to_highlight: Option<TextRange>,
47 syntactic_name_ref_highlighting: bool,
47) -> Vec<HighlightedRange> { 48) -> Vec<HighlightedRange> {
48 let _p = profile("highlight"); 49 let _p = profile("highlight");
49 let sema = Semantics::new(db); 50 let sema = Semantics::new(db);
@@ -104,6 +105,7 @@ pub(crate) fn highlight(
104 if let Some((highlight, binding_hash)) = highlight_element( 105 if let Some((highlight, binding_hash)) = highlight_element(
105 &sema, 106 &sema,
106 &mut bindings_shadow_count, 107 &mut bindings_shadow_count,
108 syntactic_name_ref_highlighting,
107 name.syntax().clone().into(), 109 name.syntax().clone().into(),
108 ) { 110 ) {
109 stack.add(HighlightedRange { 111 stack.add(HighlightedRange {
@@ -160,23 +162,25 @@ pub(crate) fn highlight(
160 // Check if macro takes a format string and remember it for highlighting later. 162 // Check if macro takes a format string and remember it for highlighting later.
161 // The macros that accept a format string expand to a compiler builtin macros 163 // The macros that accept a format string expand to a compiler builtin macros
162 // `format_args` and `format_args_nl`. 164 // `format_args` and `format_args_nl`.
163 if let Some(fmt_macro_call) = parent.parent().and_then(ast::MacroCall::cast) { 165 if let Some(name) = parent
164 if let Some(name) = 166 .parent()
165 fmt_macro_call.path().and_then(|p| p.segment()).and_then(|s| s.name_ref()) 167 .and_then(ast::MacroCall::cast)
166 { 168 .and_then(|mc| mc.path())
167 match name.text().as_str() { 169 .and_then(|p| p.segment())
168 "format_args" | "format_args_nl" => { 170 .and_then(|s| s.name_ref())
169 format_string = parent 171 {
170 .children_with_tokens() 172 match name.text().as_str() {
171 .filter(|t| t.kind() != WHITESPACE) 173 "format_args" | "format_args_nl" => {
172 .nth(1) 174 format_string = parent
173 .filter(|e| { 175 .children_with_tokens()
174 ast::String::can_cast(e.kind()) 176 .filter(|t| t.kind() != WHITESPACE)
175 || ast::RawString::can_cast(e.kind()) 177 .nth(1)
176 }) 178 .filter(|e| {
177 } 179 ast::String::can_cast(e.kind())
178 _ => {} 180 || ast::RawString::can_cast(e.kind())
181 })
179 } 182 }
183 _ => {}
180 } 184 }
181 } 185 }
182 186
@@ -198,15 +202,18 @@ pub(crate) fn highlight(
198 202
199 let is_format_string = format_string.as_ref() == Some(&element_to_highlight); 203 let is_format_string = format_string.as_ref() == Some(&element_to_highlight);
200 204
201 if let Some((highlight, binding_hash)) = 205 if let Some((highlight, binding_hash)) = highlight_element(
202 highlight_element(&sema, &mut bindings_shadow_count, element_to_highlight.clone()) 206 &sema,
203 { 207 &mut bindings_shadow_count,
208 syntactic_name_ref_highlighting,
209 element_to_highlight.clone(),
210 ) {
204 stack.add(HighlightedRange { range, highlight, binding_hash }); 211 stack.add(HighlightedRange { range, highlight, binding_hash });
205 if let Some(string) = 212 if let Some(string) =
206 element_to_highlight.as_token().cloned().and_then(ast::String::cast) 213 element_to_highlight.as_token().cloned().and_then(ast::String::cast)
207 { 214 {
208 stack.push();
209 if is_format_string { 215 if is_format_string {
216 stack.push();
210 string.lex_format_specifier(|piece_range, kind| { 217 string.lex_format_specifier(|piece_range, kind| {
211 if let Some(highlight) = highlight_format_specifier(kind) { 218 if let Some(highlight) = highlight_format_specifier(kind) {
212 stack.add(HighlightedRange { 219 stack.add(HighlightedRange {
@@ -216,13 +223,27 @@ pub(crate) fn highlight(
216 }); 223 });
217 } 224 }
218 }); 225 });
226 stack.pop();
227 }
228 // Highlight escape sequences
229 if let Some(char_ranges) = string.char_ranges() {
230 stack.push();
231 for (piece_range, _) in char_ranges.iter().filter(|(_, char)| char.is_ok()) {
232 if string.text()[piece_range.start().into()..].starts_with('\\') {
233 stack.add(HighlightedRange {
234 range: piece_range + range.start(),
235 highlight: HighlightTag::EscapeSequence.into(),
236 binding_hash: None,
237 });
238 }
239 }
240 stack.pop_and_inject(false);
219 } 241 }
220 stack.pop();
221 } else if let Some(string) = 242 } else if let Some(string) =
222 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) 243 element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
223 { 244 {
224 stack.push();
225 if is_format_string { 245 if is_format_string {
246 stack.push();
226 string.lex_format_specifier(|piece_range, kind| { 247 string.lex_format_specifier(|piece_range, kind| {
227 if let Some(highlight) = highlight_format_specifier(kind) { 248 if let Some(highlight) = highlight_format_specifier(kind) {
228 stack.add(HighlightedRange { 249 stack.add(HighlightedRange {
@@ -232,8 +253,8 @@ pub(crate) fn highlight(
232 }); 253 });
233 } 254 }
234 }); 255 });
256 stack.pop();
235 } 257 }
236 stack.pop();
237 } 258 }
238 } 259 }
239 } 260 }
@@ -408,6 +429,7 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
408fn highlight_element( 429fn highlight_element(
409 sema: &Semantics<RootDatabase>, 430 sema: &Semantics<RootDatabase>,
410 bindings_shadow_count: &mut FxHashMap<Name, u32>, 431 bindings_shadow_count: &mut FxHashMap<Name, u32>,
432 syntactic_name_ref_highlighting: bool,
411 element: SyntaxElement, 433 element: SyntaxElement,
412) -> Option<(Highlight, Option<u64>)> { 434) -> Option<(Highlight, Option<u64>)> {
413 let db = sema.db; 435 let db = sema.db;
@@ -461,6 +483,7 @@ fn highlight_element(
461 } 483 }
462 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), 484 NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
463 }, 485 },
486 None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref),
464 None => HighlightTag::UnresolvedReference.into(), 487 None => HighlightTag::UnresolvedReference.into(),
465 } 488 }
466 } 489 }
@@ -493,6 +516,9 @@ fn highlight_element(
493 h |= HighlightModifier::Unsafe; 516 h |= HighlightModifier::Unsafe;
494 h 517 h
495 } 518 }
519 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
520 Highlight::new(HighlightTag::Macro)
521 }
496 522
497 k if k.is_keyword() => { 523 k if k.is_keyword() => {
498 let h = Highlight::new(HighlightTag::Keyword); 524 let h = Highlight::new(HighlightTag::Keyword);
@@ -609,3 +635,53 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
609 635
610 tag.into() 636 tag.into()
611} 637}
638
639fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
640 let default = HighlightTag::UnresolvedReference;
641
642 let parent = match name.syntax().parent() {
643 Some(it) => it,
644 _ => return default.into(),
645 };
646
647 let tag = match parent.kind() {
648 METHOD_CALL_EXPR => HighlightTag::Function,
649 FIELD_EXPR => HighlightTag::Field,
650 PATH_SEGMENT => {
651 let path = match parent.parent().and_then(ast::Path::cast) {
652 Some(it) => it,
653 _ => return default.into(),
654 };
655 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
656 Some(it) => it,
657 _ => {
658 // within path, decide whether it is module or adt by checking for uppercase name
659 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
660 HighlightTag::Struct
661 } else {
662 HighlightTag::Module
663 }
664 .into();
665 }
666 };
667 let parent = match expr.syntax().parent() {
668 Some(it) => it,
669 None => return default.into(),
670 };
671
672 match parent.kind() {
673 CALL_EXPR => HighlightTag::Function,
674 _ => {
675 if name.text().chars().next().unwrap_or_default().is_uppercase() {
676 HighlightTag::Struct
677 } else {
678 HighlightTag::Constant
679 }
680 }
681 }
682 }
683 _ => default,
684 };
685
686 tag.into()
687}
diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs
index 5bada6252..99b6b25ab 100644
--- a/crates/ra_ide/src/syntax_highlighting/html.rs
+++ b/crates/ra_ide/src/syntax_highlighting/html.rs
@@ -19,7 +19,7 @@ pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: boo
19 ) 19 )
20 } 20 }
21 21
22 let ranges = highlight(db, file_id, None); 22 let ranges = highlight(db, file_id, None, false);
23 let text = parse.tree().syntax().to_string(); 23 let text = parse.tree().syntax().to_string();
24 let mut prev_pos = TextSize::from(0); 24 let mut prev_pos = TextSize::from(0);
25 let mut buf = String::new(); 25 let mut buf = String::new();
@@ -84,6 +84,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
84.variable { color: #DCDCCC; } 84.variable { color: #DCDCCC; }
85.format_specifier { color: #CC696B; } 85.format_specifier { color: #CC696B; }
86.mutable { text-decoration: underline; } 86.mutable { text-decoration: underline; }
87.unresolved_reference { color: #FC5555; }
88.escape_sequence { color: #94BFF3; }
87 89
88.keyword { color: #F0DFAF; font-weight: bold; } 90.keyword { color: #F0DFAF; font-weight: bold; }
89.keyword.unsafe { color: #BC8383; font-weight: bold; } 91.keyword.unsafe { color: #BC8383; font-weight: bold; }
diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs
index 3575a0fc6..929a5cc5c 100644
--- a/crates/ra_ide/src/syntax_highlighting/injection.rs
+++ b/crates/ra_ide/src/syntax_highlighting/injection.rs
@@ -53,6 +53,10 @@ pub(super) fn highlight_injection(
53/// Mapping from extracted documentation code to original code 53/// Mapping from extracted documentation code to original code
54type RangesMap = BTreeMap<TextSize, TextSize>; 54type RangesMap = BTreeMap<TextSize, TextSize>;
55 55
56const RUSTDOC_FENCE: &'static str = "```";
57const RUSTDOC_FENCE_TOKENS: &[&'static str] =
58 &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"];
59
56/// Extracts Rust code from documentation comments as well as a mapping from 60/// Extracts Rust code from documentation comments as well as a mapping from
57/// the extracted source code back to the original source ranges. 61/// the extracted source code back to the original source ranges.
58/// Lastly, a vector of new comment highlight ranges (spanning only the 62/// Lastly, a vector of new comment highlight ranges (spanning only the
@@ -67,6 +71,7 @@ pub(super) fn extract_doc_comments(
67 // Mapping from extracted documentation code to original code 71 // Mapping from extracted documentation code to original code
68 let mut range_mapping: RangesMap = BTreeMap::new(); 72 let mut range_mapping: RangesMap = BTreeMap::new();
69 let mut line_start = TextSize::try_from(prefix.len()).unwrap(); 73 let mut line_start = TextSize::try_from(prefix.len()).unwrap();
74 let mut is_codeblock = false;
70 let mut is_doctest = false; 75 let mut is_doctest = false;
71 // Replace the original, line-spanning comment ranges by new, only comment-prefix 76 // Replace the original, line-spanning comment ranges by new, only comment-prefix
72 // spanning comment ranges. 77 // spanning comment ranges.
@@ -76,8 +81,13 @@ pub(super) fn extract_doc_comments(
76 .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) 81 .filter_map(|el| el.into_token().and_then(ast::Comment::cast))
77 .filter(|comment| comment.kind().doc.is_some()) 82 .filter(|comment| comment.kind().doc.is_some())
78 .filter(|comment| { 83 .filter(|comment| {
79 if comment.text().contains("```") { 84 if let Some(idx) = comment.text().find(RUSTDOC_FENCE) {
80 is_doctest = !is_doctest; 85 is_codeblock = !is_codeblock;
86 // Check whether code is rust by inspecting fence guards
87 let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..];
88 let is_rust =
89 guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim()));
90 is_doctest = is_codeblock && is_rust;
81 false 91 false
82 } else { 92 } else {
83 is_doctest 93 is_doctest
@@ -137,7 +147,7 @@ pub(super) fn highlight_doc_comment(
137 let (analysis, tmp_file_id) = Analysis::from_single_file(text); 147 let (analysis, tmp_file_id) = Analysis::from_single_file(text);
138 148
139 stack.push(); 149 stack.push();
140 for mut h in analysis.highlight(tmp_file_id).unwrap() { 150 for mut h in analysis.with_db(|db| super::highlight(db, tmp_file_id, None, true)).unwrap() {
141 // Determine start offset and end offset in case of multi-line ranges 151 // Determine start offset and end offset in case of multi-line ranges
142 let mut start_offset = None; 152 let mut start_offset = None;
143 let mut end_offset = None; 153 let mut end_offset = None;
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index 94f466966..400d22fb6 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -23,6 +23,7 @@ pub enum HighlightTag {
23 Constant, 23 Constant,
24 Enum, 24 Enum,
25 EnumVariant, 25 EnumVariant,
26 EscapeSequence,
26 Field, 27 Field,
27 FormatSpecifier, 28 FormatSpecifier,
28 Function, 29 Function,
@@ -71,6 +72,7 @@ impl HighlightTag {
71 HighlightTag::Constant => "constant", 72 HighlightTag::Constant => "constant",
72 HighlightTag::Enum => "enum", 73 HighlightTag::Enum => "enum",
73 HighlightTag::EnumVariant => "enum_variant", 74 HighlightTag::EnumVariant => "enum_variant",
75 HighlightTag::EscapeSequence => "escape_sequence",
74 HighlightTag::Field => "field", 76 HighlightTag::Field => "field",
75 HighlightTag::FormatSpecifier => "format_specifier", 77 HighlightTag::FormatSpecifier => "format_specifier",
76 HighlightTag::Function => "function", 78 HighlightTag::Function => "function",
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 949bf59a0..b4d56a7a0 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -43,6 +43,12 @@ def_fn! {
43 } 43 }
44} 44}
45 45
46macro_rules! noop {
47 ($expr:expr) => {
48 $expr
49 }
50}
51
46// comment 52// comment
47fn main() { 53fn main() {
48 println!("Hello, {}!", 92); 54 println!("Hello, {}!", 92);
@@ -61,6 +67,8 @@ fn main() {
61 // Do nothing 67 // Do nothing
62 } 68 }
63 69
70 noop!(noop!(1));
71
64 let mut x = 42; 72 let mut x = 42;
65 let y = &mut x; 73 let y = &mut x;
66 let z = &y; 74 let z = &y;
@@ -238,6 +246,10 @@ fn main() {
238 246
239 println!(r"Hello, {}!", "world"); 247 println!(r"Hello, {}!", "world");
240 248
249 // escape sequences
250 println!("Hello\nWorld");
251 println!("\u{48}\x65\x6C\x6C\x6F World");
252
241 println!("{\x41}", A = 92); 253 println!("{\x41}", A = 92);
242 println!("{ничоси}", ничоси = 92); 254 println!("{ничоси}", ничоси = 92);
243}"# 255}"#
@@ -279,7 +291,13 @@ fn main() {
279fn test_highlight_doctest() { 291fn test_highlight_doctest() {
280 check_highlighting( 292 check_highlighting(
281 r#" 293 r#"
294struct Foo {
295 bar: bool,
296}
297
282impl Foo { 298impl Foo {
299 pub const bar: bool = true;
300
283 /// Constructs a new `Foo`. 301 /// Constructs a new `Foo`.
284 /// 302 ///
285 /// # Examples 303 /// # Examples
@@ -289,7 +307,7 @@ impl Foo {
289 /// let mut foo: Foo = Foo::new(); 307 /// let mut foo: Foo = Foo::new();
290 /// ``` 308 /// ```
291 pub const fn new() -> Foo { 309 pub const fn new() -> Foo {
292 Foo { } 310 Foo { bar: true }
293 } 311 }
294 312
295 /// `bar` method on `Foo`. 313 /// `bar` method on `Foo`.
@@ -297,11 +315,15 @@ impl Foo {
297 /// # Examples 315 /// # Examples
298 /// 316 ///
299 /// ``` 317 /// ```
318 /// use x::y;
319 ///
300 /// let foo = Foo::new(); 320 /// let foo = Foo::new();
301 /// 321 ///
302 /// // calls bar on foo 322 /// // calls bar on foo
303 /// assert!(foo.bar()); 323 /// assert!(foo.bar());
304 /// 324 ///
325 /// let bar = foo.bar || Foo::bar;
326 ///
305 /// /* multi-line 327 /// /* multi-line
306 /// comment */ 328 /// comment */
307 /// 329 ///
@@ -311,9 +333,13 @@ impl Foo {
311 /// 333 ///
312 /// ``` 334 /// ```
313 /// 335 ///
314 /// ``` 336 /// ```rust,no_run
315 /// let foobar = Foo::new().bar(); 337 /// let foobar = Foo::new().bar();
316 /// ``` 338 /// ```
339 ///
340 /// ```sh
341 /// echo 1
342 /// ```
317 pub fn foo(&self) -> bool { 343 pub fn foo(&self) -> bool {
318 true 344 true
319 } 345 }
@@ -322,7 +348,7 @@ impl Foo {
322 .trim(), 348 .trim(),
323 "crates/ra_ide/src/snapshots/highlight_doctest.html", 349 "crates/ra_ide/src/snapshots/highlight_doctest.html",
324 false, 350 false,
325 ) 351 );
326} 352}
327 353
328/// Highlights the code given by the `ra_fixture` argument, renders the 354/// Highlights the code given by the `ra_fixture` argument, renders the
diff --git a/crates/ra_ide_db/src/change.rs b/crates/ra_ide_db/src/change.rs
index 2fc796a85..78ee6a515 100644
--- a/crates/ra_ide_db/src/change.rs
+++ b/crates/ra_ide_db/src/change.rs
@@ -9,22 +9,15 @@ use ra_db::{
9 SourceRootId, 9 SourceRootId,
10}; 10};
11use ra_prof::{memory_usage, profile, Bytes}; 11use ra_prof::{memory_usage, profile, Bytes};
12use ra_syntax::SourceFile;
13#[cfg(not(feature = "wasm"))]
14use rayon::prelude::*;
15use rustc_hash::FxHashMap; 12use rustc_hash::FxHashMap;
16 13
17use crate::{ 14use crate::{symbol_index::SymbolsDatabase, RootDatabase};
18 symbol_index::{SymbolIndex, SymbolsDatabase},
19 RootDatabase,
20};
21 15
22#[derive(Default)] 16#[derive(Default)]
23pub struct AnalysisChange { 17pub struct AnalysisChange {
24 new_roots: Vec<(SourceRootId, bool)>, 18 new_roots: Vec<(SourceRootId, bool)>,
25 roots_changed: FxHashMap<SourceRootId, RootChange>, 19 roots_changed: FxHashMap<SourceRootId, RootChange>,
26 files_changed: Vec<(FileId, Arc<String>)>, 20 files_changed: Vec<(FileId, Arc<String>)>,
27 libraries_added: Vec<LibraryData>,
28 crate_graph: Option<CrateGraph>, 21 crate_graph: Option<CrateGraph>,
29} 22}
30 23
@@ -40,9 +33,6 @@ impl fmt::Debug for AnalysisChange {
40 if !self.files_changed.is_empty() { 33 if !self.files_changed.is_empty() {
41 d.field("files_changed", &self.files_changed.len()); 34 d.field("files_changed", &self.files_changed.len());
42 } 35 }
43 if !self.libraries_added.is_empty() {
44 d.field("libraries_added", &self.libraries_added.len());
45 }
46 if self.crate_graph.is_some() { 36 if self.crate_graph.is_some() {
47 d.field("crate_graph", &self.crate_graph); 37 d.field("crate_graph", &self.crate_graph);
48 } 38 }
@@ -79,10 +69,6 @@ impl AnalysisChange {
79 self.roots_changed.entry(root_id).or_default().removed.push(file); 69 self.roots_changed.entry(root_id).or_default().removed.push(file);
80 } 70 }
81 71
82 pub fn add_library(&mut self, data: LibraryData) {
83 self.libraries_added.push(data)
84 }
85
86 pub fn set_crate_graph(&mut self, graph: CrateGraph) { 72 pub fn set_crate_graph(&mut self, graph: CrateGraph) {
87 self.crate_graph = Some(graph); 73 self.crate_graph = Some(graph);
88 } 74 }
@@ -116,47 +102,6 @@ impl fmt::Debug for RootChange {
116 } 102 }
117} 103}
118 104
119pub struct LibraryData {
120 root_id: SourceRootId,
121 root_change: RootChange,
122 symbol_index: SymbolIndex,
123}
124
125impl fmt::Debug for LibraryData {
126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127 f.debug_struct("LibraryData")
128 .field("root_id", &self.root_id)
129 .field("root_change", &self.root_change)
130 .field("n_symbols", &self.symbol_index.len())
131 .finish()
132 }
133}
134
135impl LibraryData {
136 pub fn prepare(
137 root_id: SourceRootId,
138 files: Vec<(FileId, RelativePathBuf, Arc<String>)>,
139 ) -> LibraryData {
140 let _p = profile("LibraryData::prepare");
141
142 #[cfg(not(feature = "wasm"))]
143 let iter = files.par_iter();
144 #[cfg(feature = "wasm")]
145 let iter = files.iter();
146
147 let symbol_index = SymbolIndex::for_files(iter.map(|(file_id, _, text)| {
148 let parse = SourceFile::parse(text);
149 (*file_id, parse)
150 }));
151 let mut root_change = RootChange::default();
152 root_change.added = files
153 .into_iter()
154 .map(|(file_id, path, text)| AddFile { file_id, path, text })
155 .collect();
156 LibraryData { root_id, root_change, symbol_index }
157 }
158}
159
160const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100); 105const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100);
161 106
162impl RootDatabase { 107impl RootDatabase {
@@ -171,6 +116,7 @@ impl RootDatabase {
171 log::info!("apply_change {:?}", change); 116 log::info!("apply_change {:?}", change);
172 if !change.new_roots.is_empty() { 117 if !change.new_roots.is_empty() {
173 let mut local_roots = Vec::clone(&self.local_roots()); 118 let mut local_roots = Vec::clone(&self.local_roots());
119 let mut libraries = Vec::clone(&self.library_roots());
174 for (root_id, is_local) in change.new_roots { 120 for (root_id, is_local) in change.new_roots {
175 let root = 121 let root =
176 if is_local { SourceRoot::new_local() } else { SourceRoot::new_library() }; 122 if is_local { SourceRoot::new_local() } else { SourceRoot::new_library() };
@@ -178,9 +124,12 @@ impl RootDatabase {
178 self.set_source_root_with_durability(root_id, Arc::new(root), durability); 124 self.set_source_root_with_durability(root_id, Arc::new(root), durability);
179 if is_local { 125 if is_local {
180 local_roots.push(root_id); 126 local_roots.push(root_id);
127 } else {
128 libraries.push(root_id)
181 } 129 }
182 } 130 }
183 self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); 131 self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
132 self.set_library_roots_with_durability(Arc::new(libraries), Durability::HIGH);
184 } 133 }
185 134
186 for (root_id, root_change) in change.roots_changed { 135 for (root_id, root_change) in change.roots_changed {
@@ -192,24 +141,6 @@ impl RootDatabase {
192 let durability = durability(&source_root); 141 let durability = durability(&source_root);
193 self.set_file_text_with_durability(file_id, text, durability) 142 self.set_file_text_with_durability(file_id, text, durability)
194 } 143 }
195 if !change.libraries_added.is_empty() {
196 let mut libraries = Vec::clone(&self.library_roots());
197 for library in change.libraries_added {
198 libraries.push(library.root_id);
199 self.set_source_root_with_durability(
200 library.root_id,
201 Arc::new(SourceRoot::new_library()),
202 Durability::HIGH,
203 );
204 self.set_library_symbols_with_durability(
205 library.root_id,
206 Arc::new(library.symbol_index),
207 Durability::HIGH,
208 );
209 self.apply_root_change(library.root_id, library.root_change);
210 }
211 self.set_library_roots_with_durability(Arc::new(libraries), Durability::HIGH);
212 }
213 if let Some(crate_graph) = change.crate_graph { 144 if let Some(crate_graph) = change.crate_graph {
214 self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH) 145 self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH)
215 } 146 }
diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs
index f40ae8304..0bbd3c3e5 100644
--- a/crates/ra_ide_db/src/source_change.rs
+++ b/crates/ra_ide_db/src/source_change.rs
@@ -3,7 +3,7 @@
3//! 3//!
4//! It can be viewed as a dual for `AnalysisChange`. 4//! It can be viewed as a dual for `AnalysisChange`.
5 5
6use ra_db::{FileId, RelativePathBuf, SourceRootId}; 6use ra_db::FileId;
7use ra_text_edit::TextEdit; 7use ra_text_edit::TextEdit;
8 8
9#[derive(Debug, Clone)] 9#[derive(Debug, Clone)]
@@ -44,8 +44,8 @@ impl From<Vec<SourceFileEdit>> for SourceChange {
44 44
45#[derive(Debug, Clone)] 45#[derive(Debug, Clone)]
46pub enum FileSystemEdit { 46pub enum FileSystemEdit {
47 CreateFile { source_root: SourceRootId, path: RelativePathBuf }, 47 CreateFile { anchor: FileId, dst: String },
48 MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, 48 MoveFile { src: FileId, anchor: FileId, dst: String },
49} 49}
50 50
51impl From<FileSystemEdit> for SourceChange { 51impl From<FileSystemEdit> for SourceChange {
diff --git a/crates/ra_ide_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs
index aab918973..25c99813f 100644
--- a/crates/ra_ide_db/src/symbol_index.rs
+++ b/crates/ra_ide_db/src/symbol_index.rs
@@ -34,14 +34,15 @@ use ra_db::{
34 salsa::{self, ParallelDatabase}, 34 salsa::{self, ParallelDatabase},
35 CrateId, FileId, SourceDatabaseExt, SourceRootId, 35 CrateId, FileId, SourceDatabaseExt, SourceRootId,
36}; 36};
37use ra_prof::profile;
37use ra_syntax::{ 38use ra_syntax::{
38 ast::{self, NameOwner}, 39 ast::{self, NameOwner},
39 match_ast, AstNode, Parse, SmolStr, SourceFile, 40 match_ast, AstNode, Parse, SmolStr, SourceFile,
40 SyntaxKind::{self, *}, 41 SyntaxKind::{self, *},
41 SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent, 42 SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent,
42}; 43};
43#[cfg(not(feature = "wasm"))]
44use rayon::prelude::*; 44use rayon::prelude::*;
45use rustc_hash::FxHashMap;
45 46
46use crate::RootDatabase; 47use crate::RootDatabase;
47 48
@@ -86,10 +87,9 @@ impl Query {
86} 87}
87 88
88#[salsa::query_group(SymbolsDatabaseStorage)] 89#[salsa::query_group(SymbolsDatabaseStorage)]
89pub trait SymbolsDatabase: hir::db::HirDatabase { 90pub trait SymbolsDatabase: hir::db::HirDatabase + SourceDatabaseExt + ParallelDatabase {
90 fn file_symbols(&self, file_id: FileId) -> Arc<SymbolIndex>; 91 fn file_symbols(&self, file_id: FileId) -> Arc<SymbolIndex>;
91 #[salsa::input] 92 fn library_symbols(&self) -> Arc<FxHashMap<SourceRootId, SymbolIndex>>;
92 fn library_symbols(&self, id: SourceRootId) -> Arc<SymbolIndex>;
93 /// The set of "local" (that is, from the current workspace) roots. 93 /// The set of "local" (that is, from the current workspace) roots.
94 /// Files in local roots are assumed to change frequently. 94 /// Files in local roots are assumed to change frequently.
95 #[salsa::input] 95 #[salsa::input]
@@ -100,6 +100,29 @@ pub trait SymbolsDatabase: hir::db::HirDatabase {
100 fn library_roots(&self) -> Arc<Vec<SourceRootId>>; 100 fn library_roots(&self) -> Arc<Vec<SourceRootId>>;
101} 101}
102 102
103fn library_symbols(
104 db: &(impl SymbolsDatabase + ParallelDatabase),
105) -> Arc<FxHashMap<SourceRootId, SymbolIndex>> {
106 let _p = profile("library_symbols");
107
108 let roots = db.library_roots();
109 let res = roots
110 .iter()
111 .map(|&root_id| {
112 let root = db.source_root(root_id);
113 let files = root
114 .walk()
115 .map(|it| (it, SourceDatabaseExt::file_text(db, it)))
116 .collect::<Vec<_>>();
117 let symbol_index = SymbolIndex::for_files(
118 files.into_par_iter().map(|(file, text)| (file, SourceFile::parse(&text))),
119 );
120 (root_id, symbol_index)
121 })
122 .collect();
123 Arc::new(res)
124}
125
103fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex> { 126fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex> {
104 db.check_canceled(); 127 db.check_canceled();
105 let parse = db.parse(file_id); 128 let parse = db.parse(file_id);
@@ -112,9 +135,9 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex>
112} 135}
113 136
114/// Need to wrap Snapshot to provide `Clone` impl for `map_with` 137/// Need to wrap Snapshot to provide `Clone` impl for `map_with`
115struct Snap(salsa::Snapshot<RootDatabase>); 138struct Snap<DB>(DB);
116impl Clone for Snap { 139impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
117 fn clone(&self) -> Snap { 140 fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
118 Snap(self.0.snapshot()) 141 Snap(self.0.snapshot())
119 } 142 }
120} 143}
@@ -143,19 +166,11 @@ impl Clone for Snap {
143pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { 166pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
144 let _p = ra_prof::profile("world_symbols").detail(|| query.query.clone()); 167 let _p = ra_prof::profile("world_symbols").detail(|| query.query.clone());
145 168
146 let buf: Vec<Arc<SymbolIndex>> = if query.libs { 169 let tmp1;
147 let snap = Snap(db.snapshot()); 170 let tmp2;
148 #[cfg(not(feature = "wasm"))] 171 let buf: Vec<&SymbolIndex> = if query.libs {
149 let buf = db 172 tmp1 = db.library_symbols();
150 .library_roots() 173 tmp1.values().collect()
151 .par_iter()
152 .map_with(snap, |db, &lib_id| db.0.library_symbols(lib_id))
153 .collect();
154
155 #[cfg(feature = "wasm")]
156 let buf = db.library_roots().iter().map(|&lib_id| snap.0.library_symbols(lib_id)).collect();
157
158 buf
159 } else { 174 } else {
160 let mut files = Vec::new(); 175 let mut files = Vec::new();
161 for &root in db.local_roots().iter() { 176 for &root in db.local_roots().iter() {
@@ -164,14 +179,11 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
164 } 179 }
165 180
166 let snap = Snap(db.snapshot()); 181 let snap = Snap(db.snapshot());
167 #[cfg(not(feature = "wasm"))] 182 tmp2 = files
168 let buf = 183 .par_iter()
169 files.par_iter().map_with(snap, |db, &file_id| db.0.file_symbols(file_id)).collect(); 184 .map_with(snap, |db, &file_id| db.0.file_symbols(file_id))
170 185 .collect::<Vec<_>>();
171 #[cfg(feature = "wasm")] 186 tmp2.iter().map(|it| &**it).collect()
172 let buf = files.iter().map(|&file_id| snap.0.file_symbols(file_id)).collect();
173
174 buf
175 }; 187 };
176 query.search(&buf) 188 query.search(&buf)
177} 189}
@@ -191,14 +203,11 @@ pub fn crate_symbols(db: &RootDatabase, krate: CrateId, query: Query) -> Vec<Fil
191 203
192 let snap = Snap(db.snapshot()); 204 let snap = Snap(db.snapshot());
193 205
194 #[cfg(not(feature = "wasm"))]
195 let buf = files 206 let buf = files
196 .par_iter() 207 .par_iter()
197 .map_with(snap, |db, &file_id| db.0.file_symbols(file_id)) 208 .map_with(snap, |db, &file_id| db.0.file_symbols(file_id))
198 .collect::<Vec<_>>(); 209 .collect::<Vec<_>>();
199 210 let buf = buf.iter().map(|it| &**it).collect::<Vec<_>>();
200 #[cfg(feature = "wasm")]
201 let buf = files.iter().map(|&file_id| snap.0.file_symbols(file_id)).collect::<Vec<_>>();
202 211
203 query.search(&buf) 212 query.search(&buf)
204} 213}
@@ -245,12 +254,8 @@ impl SymbolIndex {
245 lhs_chars.cmp(rhs_chars) 254 lhs_chars.cmp(rhs_chars)
246 } 255 }
247 256
248 #[cfg(not(feature = "wasm"))]
249 symbols.par_sort_by(cmp); 257 symbols.par_sort_by(cmp);
250 258
251 #[cfg(feature = "wasm")]
252 symbols.sort_by(cmp);
253
254 let mut builder = fst::MapBuilder::memory(); 259 let mut builder = fst::MapBuilder::memory();
255 260
256 let mut last_batch_start = 0; 261 let mut last_batch_start = 0;
@@ -284,7 +289,6 @@ impl SymbolIndex {
284 self.map.as_fst().size() + self.symbols.len() * mem::size_of::<FileSymbol>() 289 self.map.as_fst().size() + self.symbols.len() * mem::size_of::<FileSymbol>()
285 } 290 }
286 291
287 #[cfg(not(feature = "wasm"))]
288 pub(crate) fn for_files( 292 pub(crate) fn for_files(
289 files: impl ParallelIterator<Item = (FileId, Parse<ast::SourceFile>)>, 293 files: impl ParallelIterator<Item = (FileId, Parse<ast::SourceFile>)>,
290 ) -> SymbolIndex { 294 ) -> SymbolIndex {
@@ -294,16 +298,6 @@ impl SymbolIndex {
294 SymbolIndex::new(symbols) 298 SymbolIndex::new(symbols)
295 } 299 }
296 300
297 #[cfg(feature = "wasm")]
298 pub(crate) fn for_files(
299 files: impl Iterator<Item = (FileId, Parse<ast::SourceFile>)>,
300 ) -> SymbolIndex {
301 let symbols = files
302 .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id))
303 .collect::<Vec<_>>();
304 SymbolIndex::new(symbols)
305 }
306
307 fn range_to_map_value(start: usize, end: usize) -> u64 { 301 fn range_to_map_value(start: usize, end: usize) -> u64 {
308 debug_assert![start <= (std::u32::MAX as usize)]; 302 debug_assert![start <= (std::u32::MAX as usize)];
309 debug_assert![end <= (std::u32::MAX as usize)]; 303 debug_assert![end <= (std::u32::MAX as usize)];
@@ -319,7 +313,7 @@ impl SymbolIndex {
319} 313}
320 314
321impl Query { 315impl Query {
322 pub(crate) fn search(self, indices: &[Arc<SymbolIndex>]) -> Vec<FileSymbol> { 316 pub(crate) fn search(self, indices: &[&SymbolIndex]) -> Vec<FileSymbol> {
323 let mut op = fst::map::OpBuilder::new(); 317 let mut op = fst::map::OpBuilder::new();
324 for file_symbols in indices.iter() { 318 for file_symbols in indices.iter() {
325 let automaton = fst::automaton::Subsequence::new(&self.lowercased); 319 let automaton = fst::automaton::Subsequence::new(&self.lowercased);
diff --git a/crates/ra_parser/src/grammar/expressions.rs b/crates/ra_parser/src/grammar/expressions.rs
index d6e8df32a..6e72eea66 100644
--- a/crates/ra_parser/src/grammar/expressions.rs
+++ b/crates/ra_parser/src/grammar/expressions.rs
@@ -50,10 +50,8 @@ fn expr_no_struct(p: &mut Parser) {
50} 50}
51 51
52fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool { 52fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool {
53 match kind { 53 let forbid = matches!(kind, BIN_EXPR | RANGE_EXPR);
54 BIN_EXPR | RANGE_EXPR | IF_EXPR => false, 54 !forbid
55 _ => true,
56 }
57} 55}
58 56
59pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi) { 57pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi) {
diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs
index cb0e27dce..9541362f5 100644
--- a/crates/ra_project_model/src/lib.rs
+++ b/crates/ra_project_model/src/lib.rs
@@ -29,13 +29,7 @@ pub enum ProjectWorkspace {
29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`. 29 /// Project workspace was discovered by running `cargo metadata` and `rustc --print sysroot`.
30 Cargo { cargo: CargoWorkspace, sysroot: Sysroot }, 30 Cargo { cargo: CargoWorkspace, sysroot: Sysroot },
31 /// Project workspace was manually specified using a `rust-project.json` file. 31 /// Project workspace was manually specified using a `rust-project.json` file.
32 Json { project: JsonProject }, 32 Json { project: JsonProject, project_location: PathBuf },
33}
34
35impl From<JsonProject> for ProjectWorkspace {
36 fn from(project: JsonProject) -> ProjectWorkspace {
37 ProjectWorkspace::Json { project }
38 }
39} 33}
40 34
41/// `PackageRoot` describes a package root folder. 35/// `PackageRoot` describes a package root folder.
@@ -164,10 +158,15 @@ impl ProjectWorkspace {
164 format!("Failed to open json file {}", project_json.display()) 158 format!("Failed to open json file {}", project_json.display())
165 })?; 159 })?;
166 let reader = BufReader::new(file); 160 let reader = BufReader::new(file);
161 let project_location = match project_json.parent() {
162 Some(parent) => PathBuf::from(parent),
163 None => PathBuf::new(),
164 };
167 ProjectWorkspace::Json { 165 ProjectWorkspace::Json {
168 project: from_reader(reader).with_context(|| { 166 project: from_reader(reader).with_context(|| {
169 format!("Failed to deserialize json file {}", project_json.display()) 167 format!("Failed to deserialize json file {}", project_json.display())
170 })?, 168 })?,
169 project_location: project_location,
171 } 170 }
172 } 171 }
173 ProjectManifest::CargoToml(cargo_toml) => { 172 ProjectManifest::CargoToml(cargo_toml) => {
@@ -200,9 +199,11 @@ impl ProjectWorkspace {
200 /// the root is a member of the current workspace 199 /// the root is a member of the current workspace
201 pub fn to_roots(&self) -> Vec<PackageRoot> { 200 pub fn to_roots(&self) -> Vec<PackageRoot> {
202 match self { 201 match self {
203 ProjectWorkspace::Json { project } => { 202 ProjectWorkspace::Json { project, project_location } => project
204 project.roots.iter().map(|r| PackageRoot::new_member(r.path.clone())).collect() 203 .roots
205 } 204 .iter()
205 .map(|r| PackageRoot::new_member(project_location.join(&r.path)))
206 .collect(),
206 ProjectWorkspace::Cargo { cargo, sysroot } => cargo 207 ProjectWorkspace::Cargo { cargo, sysroot } => cargo
207 .packages() 208 .packages()
208 .map(|pkg| PackageRoot { 209 .map(|pkg| PackageRoot {
@@ -219,7 +220,7 @@ impl ProjectWorkspace {
219 220
220 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> { 221 pub fn proc_macro_dylib_paths(&self) -> Vec<PathBuf> {
221 match self { 222 match self {
222 ProjectWorkspace::Json { project } => project 223 ProjectWorkspace::Json { project, .. } => project
223 .crates 224 .crates
224 .iter() 225 .iter()
225 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref()) 226 .filter_map(|krate| krate.proc_macro_dylib_path.as_ref())
@@ -235,7 +236,7 @@ impl ProjectWorkspace {
235 236
236 pub fn n_packages(&self) -> usize { 237 pub fn n_packages(&self) -> usize {
237 match self { 238 match self {
238 ProjectWorkspace::Json { project } => project.crates.len(), 239 ProjectWorkspace::Json { project, .. } => project.crates.len(),
239 ProjectWorkspace::Cargo { cargo, sysroot } => { 240 ProjectWorkspace::Cargo { cargo, sysroot } => {
240 cargo.packages().len() + sysroot.crates().len() 241 cargo.packages().len() + sysroot.crates().len()
241 } 242 }
@@ -251,13 +252,14 @@ impl ProjectWorkspace {
251 ) -> CrateGraph { 252 ) -> CrateGraph {
252 let mut crate_graph = CrateGraph::default(); 253 let mut crate_graph = CrateGraph::default();
253 match self { 254 match self {
254 ProjectWorkspace::Json { project } => { 255 ProjectWorkspace::Json { project, project_location } => {
255 let crates: FxHashMap<_, _> = project 256 let crates: FxHashMap<_, _> = project
256 .crates 257 .crates
257 .iter() 258 .iter()
258 .enumerate() 259 .enumerate()
259 .filter_map(|(seq_index, krate)| { 260 .filter_map(|(seq_index, krate)| {
260 let file_id = load(&krate.root_module)?; 261 let file_path = project_location.join(&krate.root_module);
262 let file_id = load(&file_path)?;
261 let edition = match krate.edition { 263 let edition = match krate.edition {
262 json_project::Edition::Edition2015 => Edition::Edition2015, 264 json_project::Edition::Edition2015 => Edition::Edition2015,
263 json_project::Edition::Edition2018 => Edition::Edition2018, 265 json_project::Edition::Edition2018 => Edition::Edition2018,
@@ -540,7 +542,7 @@ impl ProjectWorkspace {
540 ProjectWorkspace::Cargo { cargo, .. } => { 542 ProjectWorkspace::Cargo { cargo, .. } => {
541 Some(cargo.workspace_root()).filter(|root| path.starts_with(root)) 543 Some(cargo.workspace_root()).filter(|root| path.starts_with(root))
542 } 544 }
543 ProjectWorkspace::Json { project: JsonProject { roots, .. } } => roots 545 ProjectWorkspace::Json { project: JsonProject { roots, .. }, .. } => roots
544 .iter() 546 .iter()
545 .find(|root| path.starts_with(&root.path)) 547 .find(|root| path.starts_with(&root.path))
546 .map(|root| root.path.as_ref()), 548 .map(|root| root.path.as_ref()),
diff --git a/crates/ra_syntax/src/algo.rs b/crates/ra_syntax/src/algo.rs
index 664894d1f..f7a885eb3 100644
--- a/crates/ra_syntax/src/algo.rs
+++ b/crates/ra_syntax/src/algo.rs
@@ -290,6 +290,11 @@ impl<'a> SyntaxRewriter<'a> {
290 N::cast(self.rewrite(node.syntax())).unwrap() 290 N::cast(self.rewrite(node.syntax())).unwrap()
291 } 291 }
292 292
293 /// Returns a node that encompasses all replacements to be done by this rewriter.
294 ///
295 /// Passing the returned node to `rewrite` will apply all replacements queued up in `self`.
296 ///
297 /// Returns `None` when there are no replacements.
293 pub fn rewrite_root(&self) -> Option<SyntaxNode> { 298 pub fn rewrite_root(&self) -> Option<SyntaxNode> {
294 assert!(self.f.is_none()); 299 assert!(self.f.is_none());
295 self.replacements 300 self.replacements
@@ -298,6 +303,9 @@ impl<'a> SyntaxRewriter<'a> {
298 SyntaxElement::Node(it) => it.clone(), 303 SyntaxElement::Node(it) => it.clone(),
299 SyntaxElement::Token(it) => it.parent(), 304 SyntaxElement::Token(it) => it.parent(),
300 }) 305 })
306 // If we only have one replacement, we must return its parent node, since `rewrite` does
307 // not replace the node passed to it.
308 .map(|it| it.parent().unwrap_or(it))
301 .fold1(|a, b| least_common_ancestor(&a, &b).unwrap()) 309 .fold1(|a, b| least_common_ancestor(&a, &b).unwrap())
302 } 310 }
303 311
diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs
index 61e686da5..a33a35cc1 100644
--- a/crates/ra_syntax/src/lib.rs
+++ b/crates/ra_syntax/src/lib.rs
@@ -51,7 +51,8 @@ pub use crate::{
51 ptr::{AstPtr, SyntaxNodePtr}, 51 ptr::{AstPtr, SyntaxNodePtr},
52 syntax_error::SyntaxError, 52 syntax_error::SyntaxError,
53 syntax_node::{ 53 syntax_node::{
54 Direction, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken, SyntaxTreeBuilder, 54 Direction, NodeOrToken, SyntaxElement, SyntaxElementChildren, SyntaxNode,
55 SyntaxNodeChildren, SyntaxToken, SyntaxTreeBuilder,
55 }, 56 },
56}; 57};
57pub use ra_parser::{SyntaxKind, T}; 58pub use ra_parser::{SyntaxKind, T};
diff --git a/crates/ra_syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast b/crates/ra_syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast
index 0656fdf73..4e3fa704e 100644
--- a/crates/ra_syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast
+++ b/crates/ra_syntax/test_data/parser/inline/err/0009_attr_on_expr_not_allowed.rast
@@ -56,4 +56,3 @@ [email protected]
56 [email protected] "}" 56 [email protected] "}"
57 [email protected] "\n" 57 [email protected] "\n"
58error 24..24: attributes are not allowed on BIN_EXPR 58error 24..24: attributes are not allowed on BIN_EXPR
59error 44..44: attributes are not allowed on IF_EXPR
diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs
index 8d071ab1c..99e3f7173 100644
--- a/crates/rust-analyzer/src/bin/main.rs
+++ b/crates/rust-analyzer/src/bin/main.rs
@@ -108,11 +108,11 @@ fn run_server() -> Result<()> {
108 config.update(value); 108 config.update(value);
109 } 109 }
110 config.update_caps(&initialize_params.capabilities); 110 config.update_caps(&initialize_params.capabilities);
111 let cwd = std::env::current_dir()?;
112 config.root_path =
113 initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
111 114
112 if config.linked_projects.is_empty() { 115 if config.linked_projects.is_empty() {
113 let cwd = std::env::current_dir()?;
114 let root =
115 initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
116 let workspace_roots = initialize_params 116 let workspace_roots = initialize_params
117 .workspace_folders 117 .workspace_folders
118 .map(|workspaces| { 118 .map(|workspaces| {
@@ -122,7 +122,7 @@ fn run_server() -> Result<()> {
122 .collect::<Vec<_>>() 122 .collect::<Vec<_>>()
123 }) 123 })
124 .filter(|workspaces| !workspaces.is_empty()) 124 .filter(|workspaces| !workspaces.is_empty())
125 .unwrap_or_else(|| vec![root]); 125 .unwrap_or_else(|| vec![config.root_path.clone()]);
126 126
127 config.linked_projects = ProjectManifest::discover_all(&workspace_roots) 127 config.linked_projects = ProjectManifest::discover_all(&workspace_roots)
128 .into_iter() 128 .into_iter()
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 44f856f6b..5c22dce0d 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -2,7 +2,7 @@
2 2
3use ra_cfg::CfgExpr; 3use ra_cfg::CfgExpr;
4use ra_ide::{FileId, RunnableKind, TestId}; 4use ra_ide::{FileId, RunnableKind, TestId};
5use ra_project_model::{self, ProjectWorkspace, TargetKind}; 5use ra_project_model::{self, TargetKind};
6 6
7use crate::{global_state::GlobalStateSnapshot, Result}; 7use crate::{global_state::GlobalStateSnapshot, Result};
8 8
@@ -89,27 +89,23 @@ impl CargoTargetSpec {
89 } 89 }
90 90
91 pub(crate) fn for_file( 91 pub(crate) fn for_file(
92 world: &GlobalStateSnapshot, 92 global_state_snapshot: &GlobalStateSnapshot,
93 file_id: FileId, 93 file_id: FileId,
94 ) -> Result<Option<CargoTargetSpec>> { 94 ) -> Result<Option<CargoTargetSpec>> {
95 let &crate_id = match world.analysis().crate_for(file_id)?.first() { 95 let crate_id = match global_state_snapshot.analysis().crate_for(file_id)?.first() {
96 Some(crate_id) => crate_id, 96 Some(crate_id) => *crate_id,
97 None => return Ok(None), 97 None => return Ok(None),
98 }; 98 };
99 let file_id = world.analysis().crate_root(crate_id)?; 99 let (cargo_ws, target) = match global_state_snapshot.cargo_target_for_crate_root(crate_id) {
100 let path = world.file_id_to_path(file_id); 100 Some(it) => it,
101 let res = world.workspaces.iter().find_map(|ws| match ws { 101 None => return Ok(None),
102 ProjectWorkspace::Cargo { cargo, .. } => { 102 };
103 let tgt = cargo.target_by_root(&path)?; 103 let res = CargoTargetSpec {
104 Some(CargoTargetSpec { 104 package: cargo_ws.package_flag(&cargo_ws[cargo_ws[target].package]),
105 package: cargo.package_flag(&cargo[cargo[tgt].package]), 105 target: cargo_ws[target].name.clone(),
106 target: cargo[tgt].name.clone(), 106 target_kind: cargo_ws[target].kind,
107 target_kind: cargo[tgt].kind, 107 };
108 }) 108 Ok(Some(res))
109 }
110 ProjectWorkspace::Json { .. } => None,
111 });
112 Ok(res)
113 } 109 }
114 110
115 pub(crate) fn push_to(self, buf: &mut Vec<String>, kind: &RunnableKind) { 111 pub(crate) fn push_to(self, buf: &mut Vec<String>, kind: &RunnableKind) {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 1253db836..0df7427cb 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -9,6 +9,7 @@
9 9
10use std::{ffi::OsString, path::PathBuf}; 10use std::{ffi::OsString, path::PathBuf};
11 11
12use crate::diagnostics::DiagnosticsConfig;
12use lsp_types::ClientCapabilities; 13use lsp_types::ClientCapabilities;
13use ra_flycheck::FlycheckConfig; 14use ra_flycheck::FlycheckConfig;
14use ra_ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig}; 15use ra_ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig};
@@ -20,6 +21,7 @@ pub struct Config {
20 pub client_caps: ClientCapsConfig, 21 pub client_caps: ClientCapsConfig,
21 22
22 pub publish_diagnostics: bool, 23 pub publish_diagnostics: bool,
24 pub diagnostics: DiagnosticsConfig,
23 pub lru_capacity: Option<usize>, 25 pub lru_capacity: Option<usize>,
24 pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>, 26 pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
25 pub files: FilesConfig, 27 pub files: FilesConfig,
@@ -38,12 +40,13 @@ pub struct Config {
38 40
39 pub with_sysroot: bool, 41 pub with_sysroot: bool,
40 pub linked_projects: Vec<LinkedProject>, 42 pub linked_projects: Vec<LinkedProject>,
43 pub root_path: PathBuf,
41} 44}
42 45
43#[derive(Debug, Clone)] 46#[derive(Debug, Clone)]
44pub enum LinkedProject { 47pub enum LinkedProject {
45 ProjectManifest(ProjectManifest), 48 ProjectManifest(ProjectManifest),
46 JsonProject(JsonProject), 49 InlineJsonProject(JsonProject),
47} 50}
48 51
49impl From<ProjectManifest> for LinkedProject { 52impl From<ProjectManifest> for LinkedProject {
@@ -54,7 +57,7 @@ impl From<ProjectManifest> for LinkedProject {
54 57
55impl From<JsonProject> for LinkedProject { 58impl From<JsonProject> for LinkedProject {
56 fn from(v: JsonProject) -> Self { 59 fn from(v: JsonProject) -> Self {
57 LinkedProject::JsonProject(v) 60 LinkedProject::InlineJsonProject(v)
58 } 61 }
59} 62}
60 63
@@ -135,6 +138,7 @@ impl Default for Config {
135 138
136 with_sysroot: true, 139 with_sysroot: true,
137 publish_diagnostics: true, 140 publish_diagnostics: true,
141 diagnostics: DiagnosticsConfig::default(),
138 lru_capacity: None, 142 lru_capacity: None,
139 proc_macro_srv: None, 143 proc_macro_srv: None,
140 files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() }, 144 files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() },
@@ -167,6 +171,7 @@ impl Default for Config {
167 lens: LensConfig::default(), 171 lens: LensConfig::default(),
168 hover: HoverConfig::default(), 172 hover: HoverConfig::default(),
169 linked_projects: Vec::new(), 173 linked_projects: Vec::new(),
174 root_path: PathBuf::new(),
170 } 175 }
171 } 176 }
172} 177}
@@ -182,6 +187,8 @@ impl Config {
182 187
183 set(value, "/withSysroot", &mut self.with_sysroot); 188 set(value, "/withSysroot", &mut self.with_sysroot);
184 set(value, "/diagnostics/enable", &mut self.publish_diagnostics); 189 set(value, "/diagnostics/enable", &mut self.publish_diagnostics);
190 set(value, "/diagnostics/warningsAsInfo", &mut self.diagnostics.warnings_as_info);
191 set(value, "/diagnostics/warningsAsHint", &mut self.diagnostics.warnings_as_hint);
185 set(value, "/lruCapacity", &mut self.lru_capacity); 192 set(value, "/lruCapacity", &mut self.lru_capacity);
186 self.files.watcher = match get(value, "/files/watcher") { 193 self.files.watcher = match get(value, "/files/watcher") {
187 Some("client") => FilesWatcher::Client, 194 Some("client") => FilesWatcher::Client,
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index 25856c543..290609e7f 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -11,6 +11,12 @@ use crate::lsp_ext;
11pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; 11pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>;
12 12
13#[derive(Debug, Default, Clone)] 13#[derive(Debug, Default, Clone)]
14pub struct DiagnosticsConfig {
15 pub warnings_as_info: Vec<String>,
16 pub warnings_as_hint: Vec<String>,
17}
18
19#[derive(Debug, Default, Clone)]
14pub struct DiagnosticCollection { 20pub struct DiagnosticCollection {
15 pub native: HashMap<FileId, Vec<Diagnostic>>, 21 pub native: HashMap<FileId, Vec<Diagnostic>>,
16 pub check: HashMap<FileId, Vec<Diagnostic>>, 22 pub check: HashMap<FileId, Vec<Diagnostic>>,
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_hint.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_hint.snap
new file mode 100644
index 000000000..f0273315e
--- /dev/null
+++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_hint.snap
@@ -0,0 +1,86 @@
1---
2source: crates/rust-analyzer/src/diagnostics/to_proto.rs
3expression: diag
4---
5[
6 MappedRustDiagnostic {
7 location: Location {
8 uri: "file:///test/driver/subcommand/repl.rs",
9 range: Range {
10 start: Position {
11 line: 290,
12 character: 8,
13 },
14 end: Position {
15 line: 290,
16 character: 11,
17 },
18 },
19 },
20 diagnostic: Diagnostic {
21 range: Range {
22 start: Position {
23 line: 290,
24 character: 8,
25 },
26 end: Position {
27 line: 290,
28 character: 11,
29 },
30 },
31 severity: Some(
32 Hint,
33 ),
34 code: Some(
35 String(
36 "unused_variables",
37 ),
38 ),
39 source: Some(
40 "rustc",
41 ),
42 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
43 related_information: None,
44 tags: Some(
45 [
46 Unnecessary,
47 ],
48 ),
49 },
50 fixes: [
51 CodeAction {
52 title: "consider prefixing with an underscore",
53 id: None,
54 group: None,
55 kind: Some(
56 "quickfix",
57 ),
58 command: None,
59 edit: Some(
60 SnippetWorkspaceEdit {
61 changes: Some(
62 {
63 "file:///test/driver/subcommand/repl.rs": [
64 TextEdit {
65 range: Range {
66 start: Position {
67 line: 290,
68 character: 8,
69 },
70 end: Position {
71 line: 290,
72 character: 11,
73 },
74 },
75 new_text: "_foo",
76 },
77 ],
78 },
79 ),
80 document_changes: None,
81 },
82 ),
83 },
84 ],
85 },
86]
diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_info.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_info.snap
new file mode 100644
index 000000000..85fd050fd
--- /dev/null
+++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable_as_info.snap
@@ -0,0 +1,86 @@
1---
2source: crates/rust-analyzer/src/diagnostics/to_proto.rs
3expression: diag
4---
5[
6 MappedRustDiagnostic {
7 location: Location {
8 uri: "file:///test/driver/subcommand/repl.rs",
9 range: Range {
10 start: Position {
11 line: 290,
12 character: 8,
13 },
14 end: Position {
15 line: 290,
16 character: 11,
17 },
18 },
19 },
20 diagnostic: Diagnostic {
21 range: Range {
22 start: Position {
23 line: 290,
24 character: 8,
25 },
26 end: Position {
27 line: 290,
28 character: 11,
29 },
30 },
31 severity: Some(
32 Information,
33 ),
34 code: Some(
35 String(
36 "unused_variables",
37 ),
38 ),
39 source: Some(
40 "rustc",
41 ),
42 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
43 related_information: None,
44 tags: Some(
45 [
46 Unnecessary,
47 ],
48 ),
49 },
50 fixes: [
51 CodeAction {
52 title: "consider prefixing with an underscore",
53 id: None,
54 group: None,
55 kind: Some(
56 "quickfix",
57 ),
58 command: None,
59 edit: Some(
60 SnippetWorkspaceEdit {
61 changes: Some(
62 {
63 "file:///test/driver/subcommand/repl.rs": [
64 TextEdit {
65 range: Range {
66 start: Position {
67 line: 290,
68 character: 8,
69 },
70 end: Position {
71 line: 290,
72 character: 11,
73 },
74 },
75 new_text: "_foo",
76 },
77 ],
78 },
79 ),
80 document_changes: None,
81 },
82 ),
83 },
84 ],
85 },
86]
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 24ff9b280..ba74f15f3 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -9,14 +9,24 @@ use lsp_types::{
9use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; 9use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion};
10use stdx::format_to; 10use stdx::format_to;
11 11
12use super::DiagnosticsConfig;
12use crate::{lsp_ext, to_proto::url_from_abs_path}; 13use crate::{lsp_ext, to_proto::url_from_abs_path};
13 14
14/// Converts a Rust level string to a LSP severity 15/// Determines the LSP severity from a diagnostic
15fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { 16fn map_diagnostic_to_severity(
16 let res = match val { 17 config: &DiagnosticsConfig,
18 val: &ra_flycheck::Diagnostic,
19) -> Option<DiagnosticSeverity> {
20 let res = match val.level {
17 DiagnosticLevel::Ice => DiagnosticSeverity::Error, 21 DiagnosticLevel::Ice => DiagnosticSeverity::Error,
18 DiagnosticLevel::Error => DiagnosticSeverity::Error, 22 DiagnosticLevel::Error => DiagnosticSeverity::Error,
19 DiagnosticLevel::Warning => DiagnosticSeverity::Warning, 23 DiagnosticLevel::Warning => match &val.code {
24 Some(code) if config.warnings_as_hint.contains(&code.code) => DiagnosticSeverity::Hint,
25 Some(code) if config.warnings_as_info.contains(&code.code) => {
26 DiagnosticSeverity::Information
27 }
28 _ => DiagnosticSeverity::Warning,
29 },
20 DiagnosticLevel::Note => DiagnosticSeverity::Information, 30 DiagnosticLevel::Note => DiagnosticSeverity::Information,
21 DiagnosticLevel::Help => DiagnosticSeverity::Hint, 31 DiagnosticLevel::Help => DiagnosticSeverity::Hint,
22 DiagnosticLevel::Unknown => return None, 32 DiagnosticLevel::Unknown => return None,
@@ -172,6 +182,7 @@ pub(crate) struct MappedRustDiagnostic {
172/// 182///
173/// If the diagnostic has no primary span this will return `None` 183/// If the diagnostic has no primary span this will return `None`
174pub(crate) fn map_rust_diagnostic_to_lsp( 184pub(crate) fn map_rust_diagnostic_to_lsp(
185 config: &DiagnosticsConfig,
175 rd: &ra_flycheck::Diagnostic, 186 rd: &ra_flycheck::Diagnostic,
176 workspace_root: &Path, 187 workspace_root: &Path,
177) -> Vec<MappedRustDiagnostic> { 188) -> Vec<MappedRustDiagnostic> {
@@ -180,7 +191,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
180 return Vec::new(); 191 return Vec::new();
181 } 192 }
182 193
183 let severity = map_level_to_severity(rd.level); 194 let severity = map_diagnostic_to_severity(config, rd);
184 195
185 let mut source = String::from("rustc"); 196 let mut source = String::from("rustc");
186 let mut code = rd.code.as_ref().map(|c| c.code.clone()); 197 let mut code = rd.code.as_ref().map(|c| c.code.clone());
@@ -328,7 +339,7 @@ mod tests {
328 ); 339 );
329 340
330 let workspace_root = Path::new("/test/"); 341 let workspace_root = Path::new("/test/");
331 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 342 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
332 insta::assert_debug_snapshot!(diag); 343 insta::assert_debug_snapshot!(diag);
333 } 344 }
334 345
@@ -410,7 +421,183 @@ mod tests {
410 ); 421 );
411 422
412 let workspace_root = Path::new("/test/"); 423 let workspace_root = Path::new("/test/");
413 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 424 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
425 insta::assert_debug_snapshot!(diag);
426 }
427
428 #[test]
429 #[cfg(not(windows))]
430 fn snap_rustc_unused_variable_as_info() {
431 let diag = parse_diagnostic(
432 r##"{
433 "message": "unused variable: `foo`",
434 "code": {
435 "code": "unused_variables",
436 "explanation": null
437 },
438 "level": "warning",
439 "spans": [
440 {
441 "file_name": "driver/subcommand/repl.rs",
442 "byte_start": 9228,
443 "byte_end": 9231,
444 "line_start": 291,
445 "line_end": 291,
446 "column_start": 9,
447 "column_end": 12,
448 "is_primary": true,
449 "text": [
450 {
451 "text": " let foo = 42;",
452 "highlight_start": 9,
453 "highlight_end": 12
454 }
455 ],
456 "label": null,
457 "suggested_replacement": null,
458 "suggestion_applicability": null,
459 "expansion": null
460 }
461 ],
462 "children": [
463 {
464 "message": "#[warn(unused_variables)] on by default",
465 "code": null,
466 "level": "note",
467 "spans": [],
468 "children": [],
469 "rendered": null
470 },
471 {
472 "message": "consider prefixing with an underscore",
473 "code": null,
474 "level": "help",
475 "spans": [
476 {
477 "file_name": "driver/subcommand/repl.rs",
478 "byte_start": 9228,
479 "byte_end": 9231,
480 "line_start": 291,
481 "line_end": 291,
482 "column_start": 9,
483 "column_end": 12,
484 "is_primary": true,
485 "text": [
486 {
487 "text": " let foo = 42;",
488 "highlight_start": 9,
489 "highlight_end": 12
490 }
491 ],
492 "label": null,
493 "suggested_replacement": "_foo",
494 "suggestion_applicability": "MachineApplicable",
495 "expansion": null
496 }
497 ],
498 "children": [],
499 "rendered": null
500 }
501 ],
502 "rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n"
503 }"##,
504 );
505
506 let config = DiagnosticsConfig {
507 warnings_as_info: vec!["unused_variables".to_string()],
508 ..DiagnosticsConfig::default()
509 };
510
511 let workspace_root = Path::new("/test/");
512 let diag = map_rust_diagnostic_to_lsp(&config, &diag, workspace_root);
513 insta::assert_debug_snapshot!(diag);
514 }
515
516 #[test]
517 #[cfg(not(windows))]
518 fn snap_rustc_unused_variable_as_hint() {
519 let diag = parse_diagnostic(
520 r##"{
521 "message": "unused variable: `foo`",
522 "code": {
523 "code": "unused_variables",
524 "explanation": null
525 },
526 "level": "warning",
527 "spans": [
528 {
529 "file_name": "driver/subcommand/repl.rs",
530 "byte_start": 9228,
531 "byte_end": 9231,
532 "line_start": 291,
533 "line_end": 291,
534 "column_start": 9,
535 "column_end": 12,
536 "is_primary": true,
537 "text": [
538 {
539 "text": " let foo = 42;",
540 "highlight_start": 9,
541 "highlight_end": 12
542 }
543 ],
544 "label": null,
545 "suggested_replacement": null,
546 "suggestion_applicability": null,
547 "expansion": null
548 }
549 ],
550 "children": [
551 {
552 "message": "#[warn(unused_variables)] on by default",
553 "code": null,
554 "level": "note",
555 "spans": [],
556 "children": [],
557 "rendered": null
558 },
559 {
560 "message": "consider prefixing with an underscore",
561 "code": null,
562 "level": "help",
563 "spans": [
564 {
565 "file_name": "driver/subcommand/repl.rs",
566 "byte_start": 9228,
567 "byte_end": 9231,
568 "line_start": 291,
569 "line_end": 291,
570 "column_start": 9,
571 "column_end": 12,
572 "is_primary": true,
573 "text": [
574 {
575 "text": " let foo = 42;",
576 "highlight_start": 9,
577 "highlight_end": 12
578 }
579 ],
580 "label": null,
581 "suggested_replacement": "_foo",
582 "suggestion_applicability": "MachineApplicable",
583 "expansion": null
584 }
585 ],
586 "children": [],
587 "rendered": null
588 }
589 ],
590 "rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n"
591 }"##,
592 );
593
594 let config = DiagnosticsConfig {
595 warnings_as_hint: vec!["unused_variables".to_string()],
596 ..DiagnosticsConfig::default()
597 };
598
599 let workspace_root = Path::new("/test/");
600 let diag = map_rust_diagnostic_to_lsp(&config, &diag, workspace_root);
414 insta::assert_debug_snapshot!(diag); 601 insta::assert_debug_snapshot!(diag);
415 } 602 }
416 603
@@ -534,7 +721,7 @@ mod tests {
534 ); 721 );
535 722
536 let workspace_root = Path::new("/test/"); 723 let workspace_root = Path::new("/test/");
537 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 724 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
538 insta::assert_debug_snapshot!(diag); 725 insta::assert_debug_snapshot!(diag);
539 } 726 }
540 727
@@ -654,7 +841,7 @@ mod tests {
654 ); 841 );
655 842
656 let workspace_root = Path::new("/test/"); 843 let workspace_root = Path::new("/test/");
657 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 844 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
658 insta::assert_debug_snapshot!(diag); 845 insta::assert_debug_snapshot!(diag);
659 } 846 }
660 847
@@ -697,7 +884,7 @@ mod tests {
697 ); 884 );
698 885
699 let workspace_root = Path::new("/test/"); 886 let workspace_root = Path::new("/test/");
700 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 887 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
701 insta::assert_debug_snapshot!(diag); 888 insta::assert_debug_snapshot!(diag);
702 } 889 }
703 890
@@ -968,7 +1155,7 @@ mod tests {
968 ); 1155 );
969 1156
970 let workspace_root = Path::new("/test/"); 1157 let workspace_root = Path::new("/test/");
971 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 1158 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
972 insta::assert_debug_snapshot!(diag); 1159 insta::assert_debug_snapshot!(diag);
973 } 1160 }
974 1161
@@ -1197,7 +1384,7 @@ mod tests {
1197 ); 1384 );
1198 1385
1199 let workspace_root = Path::new("/test/"); 1386 let workspace_root = Path::new("/test/");
1200 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 1387 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
1201 insta::assert_debug_snapshot!(diag); 1388 insta::assert_debug_snapshot!(diag);
1202 } 1389 }
1203 1390
@@ -1330,7 +1517,7 @@ mod tests {
1330 ); 1517 );
1331 1518
1332 let workspace_root = Path::new("/test/"); 1519 let workspace_root = Path::new("/test/");
1333 let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); 1520 let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root);
1334 insta::assert_debug_snapshot!(diag); 1521 insta::assert_debug_snapshot!(diag);
1335 } 1522 }
1336} 1523}
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs
index 9d5685d88..ca95d776a 100644
--- a/crates/rust-analyzer/src/global_state.rs
+++ b/crates/rust-analyzer/src/global_state.rs
@@ -12,12 +12,9 @@ use crossbeam_channel::{unbounded, Receiver};
12use lsp_types::Url; 12use lsp_types::Url;
13use parking_lot::RwLock; 13use parking_lot::RwLock;
14use ra_flycheck::{Flycheck, FlycheckConfig}; 14use ra_flycheck::{Flycheck, FlycheckConfig};
15use ra_ide::{ 15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, SourceRootId};
16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, 16use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
17}; 17use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsTask, Watch};
18use ra_project_model::{ProcMacroClient, ProjectWorkspace};
19use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
20use relative_path::RelativePathBuf;
21use stdx::format_to; 18use stdx::format_to;
22 19
23use crate::{ 20use crate::{
@@ -28,25 +25,21 @@ use crate::{
28 vfs_glob::{Glob, RustPackageFilterBuilder}, 25 vfs_glob::{Glob, RustPackageFilterBuilder},
29 LspError, Result, 26 LspError, Result,
30}; 27};
31use ra_db::ExternSourceId; 28use ra_db::{CrateId, ExternSourceId};
32use rustc_hash::{FxHashMap, FxHashSet}; 29use rustc_hash::{FxHashMap, FxHashSet};
33 30
34fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { 31fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
35 // FIXME: Figure out the multi-workspace situation 32 // FIXME: Figure out the multi-workspace situation
36 workspaces 33 workspaces.iter().find_map(|w| match w {
37 .iter() 34 ProjectWorkspace::Cargo { cargo, .. } => {
38 .find_map(|w| match w {
39 ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
40 ProjectWorkspace::Json { .. } => None,
41 })
42 .map(|cargo| {
43 let cargo_project_root = cargo.workspace_root().to_path_buf(); 35 let cargo_project_root = cargo.workspace_root().to_path_buf();
44 Some(Flycheck::new(config.clone(), cargo_project_root)) 36 Some(Flycheck::new(config.clone(), cargo_project_root))
45 }) 37 }
46 .unwrap_or_else(|| { 38 ProjectWorkspace::Json { .. } => {
47 log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); 39 log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
48 None 40 None
49 }) 41 }
42 })
50} 43}
51 44
52/// `GlobalState` is the primary mutable state of the language server 45/// `GlobalState` is the primary mutable state of the language server
@@ -195,32 +188,18 @@ impl GlobalState {
195 188
196 /// Returns a vec of libraries 189 /// Returns a vec of libraries
197 /// FIXME: better API here 190 /// FIXME: better API here
198 pub fn process_changes( 191 pub fn process_changes(&mut self, roots_scanned: &mut usize) -> bool {
199 &mut self,
200 roots_scanned: &mut usize,
201 ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
202 let changes = self.vfs.write().commit_changes(); 192 let changes = self.vfs.write().commit_changes();
203 if changes.is_empty() { 193 if changes.is_empty() {
204 return None; 194 return false;
205 } 195 }
206 let mut libs = Vec::new();
207 let mut change = AnalysisChange::new(); 196 let mut change = AnalysisChange::new();
208 for c in changes { 197 for c in changes {
209 match c { 198 match c {
210 VfsChange::AddRoot { root, files } => { 199 VfsChange::AddRoot { root, files } => {
211 let root_path = self.vfs.read().root2path(root); 200 *roots_scanned += 1;
212 let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r)); 201 for (file, path, text) in files {
213 if is_local { 202 change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
214 *roots_scanned += 1;
215 for (file, path, text) in files {
216 change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
217 }
218 } else {
219 let files = files
220 .into_iter()
221 .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text))
222 .collect();
223 libs.push((SourceRootId(root.0), files));
224 } 203 }
225 } 204 }
226 VfsChange::AddFile { root, file, path, text } => { 205 VfsChange::AddFile { root, file, path, text } => {
@@ -235,13 +214,7 @@ impl GlobalState {
235 } 214 }
236 } 215 }
237 self.analysis_host.apply_change(change); 216 self.analysis_host.apply_change(change);
238 Some(libs) 217 true
239 }
240
241 pub fn add_lib(&mut self, data: LibraryData) {
242 let mut change = AnalysisChange::new();
243 change.add_library(data);
244 self.analysis_host.apply_change(change);
245 } 218 }
246 219
247 pub fn snapshot(&self) -> GlobalStateSnapshot { 220 pub fn snapshot(&self) -> GlobalStateSnapshot {
@@ -290,20 +263,31 @@ impl GlobalStateSnapshot {
290 file_id_to_url(&self.vfs.read(), id) 263 file_id_to_url(&self.vfs.read(), id)
291 } 264 }
292 265
293 pub fn file_id_to_path(&self, id: FileId) -> PathBuf {
294 self.vfs.read().file2path(VfsFile(id.0))
295 }
296
297 pub fn file_line_endings(&self, id: FileId) -> LineEndings { 266 pub fn file_line_endings(&self, id: FileId) -> LineEndings {
298 self.vfs.read().file_line_endings(VfsFile(id.0)) 267 self.vfs.read().file_line_endings(VfsFile(id.0))
299 } 268 }
300 269
301 pub fn path_to_url(&self, root: SourceRootId, path: &RelativePathBuf) -> Url { 270 pub fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
302 let base = self.vfs.read().root2path(VfsRoot(root.0)); 271 let mut base = self.vfs.read().file2path(VfsFile(file_id.0));
303 let path = path.to_path(base); 272 base.pop();
273 let path = base.join(path);
304 url_from_abs_path(&path) 274 url_from_abs_path(&path)
305 } 275 }
306 276
277 pub(crate) fn cargo_target_for_crate_root(
278 &self,
279 crate_id: CrateId,
280 ) -> Option<(&CargoWorkspace, Target)> {
281 let file_id = self.analysis().crate_root(crate_id).ok()?;
282 let path = self.vfs.read().file2path(VfsFile(file_id.0));
283 self.workspaces.iter().find_map(|ws| match ws {
284 ProjectWorkspace::Cargo { cargo, .. } => {
285 cargo.target_by_root(&path).map(|it| (cargo, it))
286 }
287 ProjectWorkspace::Json { .. } => None,
288 })
289 }
290
307 pub fn status(&self) -> String { 291 pub fn status(&self) -> String {
308 let mut buf = String::new(); 292 let mut buf = String::new();
309 if self.workspaces.is_empty() { 293 if self.workspaces.is_empty() {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 8ec571b70..08b0a5a16 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -24,11 +24,10 @@ use lsp_types::{
24 WorkDoneProgressReport, 24 WorkDoneProgressReport,
25}; 25};
26use ra_flycheck::{CheckTask, Status}; 26use ra_flycheck::{CheckTask, Status};
27use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; 27use ra_ide::{Canceled, FileId, LineIndex};
28use ra_prof::profile; 28use ra_prof::profile;
29use ra_project_model::{PackageRoot, ProjectWorkspace}; 29use ra_project_model::{PackageRoot, ProjectWorkspace};
30use ra_vfs::{VfsTask, Watch}; 30use ra_vfs::{VfsTask, Watch};
31use relative_path::RelativePathBuf;
32use rustc_hash::FxHashSet; 31use rustc_hash::FxHashSet;
33use serde::{de::DeserializeOwned, Serialize}; 32use serde::{de::DeserializeOwned, Serialize};
34use threadpool::ThreadPool; 33use threadpool::ThreadPool;
@@ -121,7 +120,12 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
121 }) 120 })
122 .ok() 121 .ok()
123 } 122 }
124 LinkedProject::JsonProject(it) => Some(it.clone().into()), 123 LinkedProject::InlineJsonProject(it) => {
124 Some(ra_project_model::ProjectWorkspace::Json {
125 project: it.clone(),
126 project_location: config.root_path.clone(),
127 })
128 }
125 }) 129 })
126 .collect::<Vec<_>>() 130 .collect::<Vec<_>>()
127 }; 131 };
@@ -169,12 +173,10 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
169 173
170 let pool = ThreadPool::default(); 174 let pool = ThreadPool::default();
171 let (task_sender, task_receiver) = unbounded::<Task>(); 175 let (task_sender, task_receiver) = unbounded::<Task>();
172 let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>();
173 176
174 log::info!("server initialized, serving requests"); 177 log::info!("server initialized, serving requests");
175 { 178 {
176 let task_sender = task_sender; 179 let task_sender = task_sender;
177 let libdata_sender = libdata_sender;
178 loop { 180 loop {
179 log::trace!("selecting"); 181 log::trace!("selecting");
180 let event = select! { 182 let event = select! {
@@ -187,7 +189,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
187 Ok(task) => Event::Vfs(task), 189 Ok(task) => Event::Vfs(task),
188 Err(RecvError) => return Err("vfs died".into()), 190 Err(RecvError) => return Err("vfs died".into()),
189 }, 191 },
190 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
191 recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { 192 recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task {
192 Ok(task) => Event::CheckWatcher(task), 193 Ok(task) => Event::CheckWatcher(task),
193 Err(RecvError) => return Err("check watcher died".into()), 194 Err(RecvError) => return Err("check watcher died".into()),
@@ -198,15 +199,7 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
198 break; 199 break;
199 }; 200 };
200 } 201 }
201 loop_turn( 202 loop_turn(&pool, &task_sender, &connection, &mut global_state, &mut loop_state, event)?;
202 &pool,
203 &task_sender,
204 &libdata_sender,
205 &connection,
206 &mut global_state,
207 &mut loop_state,
208 event,
209 )?;
210 } 203 }
211 } 204 }
212 global_state.analysis_host.request_cancellation(); 205 global_state.analysis_host.request_cancellation();
@@ -214,7 +207,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
214 task_receiver.into_iter().for_each(|task| { 207 task_receiver.into_iter().for_each(|task| {
215 on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state) 208 on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state)
216 }); 209 });
217 libdata_receiver.into_iter().for_each(drop);
218 log::info!("...tasks have finished"); 210 log::info!("...tasks have finished");
219 log::info!("joining threadpool..."); 211 log::info!("joining threadpool...");
220 pool.join(); 212 pool.join();
@@ -238,7 +230,6 @@ enum Event {
238 Msg(Message), 230 Msg(Message),
239 Task(Task), 231 Task(Task),
240 Vfs(VfsTask), 232 Vfs(VfsTask),
241 Lib(LibraryData),
242 CheckWatcher(CheckTask), 233 CheckWatcher(CheckTask),
243} 234}
244 235
@@ -274,7 +265,6 @@ impl fmt::Debug for Event {
274 Event::Msg(it) => fmt::Debug::fmt(it, f), 265 Event::Msg(it) => fmt::Debug::fmt(it, f),
275 Event::Task(it) => fmt::Debug::fmt(it, f), 266 Event::Task(it) => fmt::Debug::fmt(it, f),
276 Event::Vfs(it) => fmt::Debug::fmt(it, f), 267 Event::Vfs(it) => fmt::Debug::fmt(it, f),
277 Event::Lib(it) => fmt::Debug::fmt(it, f),
278 Event::CheckWatcher(it) => fmt::Debug::fmt(it, f), 268 Event::CheckWatcher(it) => fmt::Debug::fmt(it, f),
279 } 269 }
280 } 270 }
@@ -286,10 +276,6 @@ struct LoopState {
286 pending_responses: FxHashSet<RequestId>, 276 pending_responses: FxHashSet<RequestId>,
287 pending_requests: PendingRequests, 277 pending_requests: PendingRequests,
288 subscriptions: Subscriptions, 278 subscriptions: Subscriptions,
289 // We try not to index more than MAX_IN_FLIGHT_LIBS libraries at the same
290 // time to always have a thread ready to react to input.
291 in_flight_libraries: usize,
292 pending_libraries: Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>,
293 workspace_loaded: bool, 279 workspace_loaded: bool,
294 roots_progress_reported: Option<usize>, 280 roots_progress_reported: Option<usize>,
295 roots_scanned: usize, 281 roots_scanned: usize,
@@ -310,7 +296,6 @@ impl LoopState {
310fn loop_turn( 296fn loop_turn(
311 pool: &ThreadPool, 297 pool: &ThreadPool,
312 task_sender: &Sender<Task>, 298 task_sender: &Sender<Task>,
313 libdata_sender: &Sender<LibraryData>,
314 connection: &Connection, 299 connection: &Connection,
315 global_state: &mut GlobalState, 300 global_state: &mut GlobalState,
316 loop_state: &mut LoopState, 301 loop_state: &mut LoopState,
@@ -334,12 +319,6 @@ fn loop_turn(
334 Event::Vfs(task) => { 319 Event::Vfs(task) => {
335 global_state.vfs.write().handle_task(task); 320 global_state.vfs.write().handle_task(task);
336 } 321 }
337 Event::Lib(lib) => {
338 global_state.add_lib(lib);
339 global_state.maybe_collect_garbage();
340 loop_state.in_flight_libraries -= 1;
341 loop_state.roots_scanned += 1;
342 }
343 Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, 322 Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?,
344 Event::Msg(msg) => match msg { 323 Event::Msg(msg) => match msg {
345 Message::Request(req) => on_request( 324 Message::Request(req) => on_request(
@@ -385,36 +364,12 @@ fn loop_turn(
385 }, 364 },
386 }; 365 };
387 366
388 let mut state_changed = false; 367 let mut state_changed = global_state.process_changes(&mut loop_state.roots_scanned);
389 if let Some(changes) = global_state.process_changes(&mut loop_state.roots_scanned) {
390 state_changed = true;
391 loop_state.pending_libraries.extend(changes);
392 }
393
394 let max_in_flight_libs = pool.max_count().saturating_sub(2).max(1);
395 while loop_state.in_flight_libraries < max_in_flight_libs {
396 let (root, files) = match loop_state.pending_libraries.pop() {
397 Some(it) => it,
398 None => break,
399 };
400
401 loop_state.in_flight_libraries += 1;
402 let sender = libdata_sender.clone();
403 pool.execute(move || {
404 log::info!("indexing {:?} ... ", root);
405 let data = LibraryData::prepare(root, files);
406 sender.send(data).unwrap();
407 });
408 }
409 368
410 let show_progress = 369 let show_progress =
411 !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; 370 !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress;
412 371
413 if !loop_state.workspace_loaded 372 if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total {
414 && loop_state.roots_scanned == loop_state.roots_total
415 && loop_state.pending_libraries.is_empty()
416 && loop_state.in_flight_libraries == 0
417 {
418 state_changed = true; 373 state_changed = true;
419 loop_state.workspace_loaded = true; 374 loop_state.workspace_loaded = true;
420 if let Some(flycheck) = &global_state.flycheck { 375 if let Some(flycheck) = &global_state.flycheck {
@@ -664,14 +619,11 @@ fn apply_document_changes(
664 mut line_index: Cow<'_, LineIndex>, 619 mut line_index: Cow<'_, LineIndex>,
665 content_changes: Vec<TextDocumentContentChangeEvent>, 620 content_changes: Vec<TextDocumentContentChangeEvent>,
666) { 621) {
667 // Remove when https://github.com/rust-analyzer/rust-analyzer/issues/4263 is fixed.
668 let backup_text = old_text.clone();
669 let backup_changes = content_changes.clone();
670
671 // The changes we got must be applied sequentially, but can cross lines so we 622 // The changes we got must be applied sequentially, but can cross lines so we
672 // have to keep our line index updated. 623 // have to keep our line index updated.
673 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we 624 // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
674 // remember the last valid line in the index and only rebuild it if needed. 625 // remember the last valid line in the index and only rebuild it if needed.
626 // The VFS will normalize the end of lines to `\n`.
675 enum IndexValid { 627 enum IndexValid {
676 All, 628 All,
677 UpToLineExclusive(u64), 629 UpToLineExclusive(u64),
@@ -695,19 +647,7 @@ fn apply_document_changes(
695 } 647 }
696 index_valid = IndexValid::UpToLineExclusive(range.start.line); 648 index_valid = IndexValid::UpToLineExclusive(range.start.line);
697 let range = from_proto::text_range(&line_index, range); 649 let range = from_proto::text_range(&line_index, range);
698 let mut text = old_text.to_owned(); 650 old_text.replace_range(Range::<usize>::from(range), &change.text);
699 match std::panic::catch_unwind(move || {
700 text.replace_range(Range::<usize>::from(range), &change.text);
701 text
702 }) {
703 Ok(t) => *old_text = t,
704 Err(e) => {
705 eprintln!("Bug in incremental text synchronization. Please report the following output on https://github.com/rust-analyzer/rust-analyzer/issues/4263");
706 dbg!(&backup_text);
707 dbg!(&backup_changes);
708 std::panic::resume_unwind(e);
709 }
710 }
711 } 651 }
712 None => { 652 None => {
713 *old_text = change.text; 653 *old_text = change.text;
@@ -729,6 +669,7 @@ fn on_check_task(
729 669
730 CheckTask::AddDiagnostic { workspace_root, diagnostic } => { 670 CheckTask::AddDiagnostic { workspace_root, diagnostic } => {
731 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( 671 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
672 &global_state.config.diagnostics,
732 &diagnostic, 673 &diagnostic,
733 &workspace_root, 674 &workspace_root,
734 ); 675 );
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index 6f125c37c..2ea63d33b 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -45,6 +45,7 @@ define_semantic_token_types![
45 (UNION, "union"), 45 (UNION, "union"),
46 (UNRESOLVED_REFERENCE, "unresolvedReference"), 46 (UNRESOLVED_REFERENCE, "unresolvedReference"),
47 (FORMAT_SPECIFIER, "formatSpecifier"), 47 (FORMAT_SPECIFIER, "formatSpecifier"),
48 (ESCAPE_SEQUENCE, "escapeSequence"),
48]; 49];
49 50
50macro_rules! define_semantic_token_modifiers { 51macro_rules! define_semantic_token_modifiers {
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 881aa1c55..055c97455 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -324,6 +324,7 @@ fn semantic_token_type_and_modifiers(
324 HighlightTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE, 324 HighlightTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE,
325 HighlightTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER, 325 HighlightTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER,
326 HighlightTag::Operator => lsp_types::SemanticTokenType::OPERATOR, 326 HighlightTag::Operator => lsp_types::SemanticTokenType::OPERATOR,
327 HighlightTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE,
327 }; 328 };
328 329
329 for modifier in highlight.modifiers.iter() { 330 for modifier in highlight.modifiers.iter() {
@@ -528,13 +529,13 @@ pub(crate) fn resource_op(
528 file_system_edit: FileSystemEdit, 529 file_system_edit: FileSystemEdit,
529) -> lsp_types::ResourceOp { 530) -> lsp_types::ResourceOp {
530 match file_system_edit { 531 match file_system_edit {
531 FileSystemEdit::CreateFile { source_root, path } => { 532 FileSystemEdit::CreateFile { anchor, dst } => {
532 let uri = snap.path_to_url(source_root, &path); 533 let uri = snap.anchored_path(anchor, &dst);
533 lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) 534 lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None })
534 } 535 }
535 FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { 536 FileSystemEdit::MoveFile { src, anchor, dst } => {
536 let old_uri = snap.file_id_to_url(src); 537 let old_uri = snap.file_id_to_url(src);
537 let new_uri = snap.path_to_url(dst_source_root, &dst_path); 538 let new_uri = snap.anchored_path(anchor, &dst);
538 lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) 539 lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None })
539 } 540 }
540 } 541 }
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index 2141bfc20..981565cd7 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -10,17 +10,17 @@
10pub mod mark; 10pub mod mark;
11 11
12use std::{ 12use std::{
13 fs, 13 env, fs,
14 path::{Path, PathBuf}, 14 path::{Path, PathBuf},
15}; 15};
16 16
17pub use ra_cfg::CfgOptions; 17use serde_json::Value;
18use stdx::split1; 18use stdx::split1;
19use text_size::{TextRange, TextSize};
19 20
21pub use ra_cfg::CfgOptions;
20pub use relative_path::{RelativePath, RelativePathBuf}; 22pub use relative_path::{RelativePath, RelativePathBuf};
21pub use rustc_hash::FxHashMap; 23pub use rustc_hash::FxHashMap;
22use serde_json::Value;
23use text_size::{TextRange, TextSize};
24 24
25pub use difference::Changeset as __Changeset; 25pub use difference::Changeset as __Changeset;
26 26
@@ -625,8 +625,6 @@ pub fn skip_slow_tests() -> bool {
625 should_skip 625 should_skip
626} 626}
627 627
628const REWRITE: bool = false;
629
630/// Asserts that `expected` and `actual` strings are equal. If they differ only 628/// Asserts that `expected` and `actual` strings are equal. If they differ only
631/// in trailing or leading whitespace the test won't fail and 629/// in trailing or leading whitespace the test won't fail and
632/// the contents of `actual` will be written to the file located at `path`. 630/// the contents of `actual` will be written to the file located at `path`.
@@ -642,7 +640,7 @@ fn assert_equal_text(expected: &str, actual: &str, path: &Path) {
642 fs::write(path, actual).unwrap(); 640 fs::write(path, actual).unwrap();
643 return; 641 return;
644 } 642 }
645 if REWRITE { 643 if env::var("UPDATE_EXPECTATIONS").is_ok() {
646 println!("rewriting {}", pretty_path.display()); 644 println!("rewriting {}", pretty_path.display());
647 fs::write(path, actual).unwrap(); 645 fs::write(path, actual).unwrap();
648 return; 646 return;
diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml
new file mode 100644
index 000000000..c03e6363b
--- /dev/null
+++ b/crates/vfs/Cargo.toml
@@ -0,0 +1,14 @@
1[package]
2name = "vfs"
3version = "0.1.0"
4authors = ["rust-analyzer developers"]
5edition = "2018"
6
7[dependencies]
8rustc-hash = "1.0"
9jod-thread = "0.1.0"
10walkdir = "2.3.1"
11globset = "0.4.5"
12crossbeam-channel = "0.4.0"
13
14paths = { path = "../paths" }
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
new file mode 100644
index 000000000..7dc721f7e
--- /dev/null
+++ b/crates/vfs/src/file_set.rs
@@ -0,0 +1,99 @@
1//! Partitions a list of files into disjoint subsets.
2//!
3//! Files which do not belong to any explicitly configured `FileSet` belong to
4//! the default `FileSet`.
5use std::{cmp, fmt, iter};
6
7use paths::AbsPathBuf;
8use rustc_hash::FxHashMap;
9
10use crate::{FileId, Vfs, VfsPath};
11
12#[derive(Default, Clone, Eq, PartialEq)]
13pub struct FileSet {
14 files: FxHashMap<VfsPath, FileId>,
15 paths: FxHashMap<FileId, VfsPath>,
16}
17
18impl FileSet {
19 pub fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> {
20 let mut base = self.paths[&anchor].clone();
21 base.pop();
22 let path = base.join(path);
23 let res = self.files.get(&path).copied();
24 res
25 }
26 pub fn insert(&mut self, file_id: FileId, path: VfsPath) {
27 self.files.insert(path.clone(), file_id);
28 self.paths.insert(file_id, path);
29 }
30 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
31 self.paths.keys().copied()
32 }
33}
34
35impl fmt::Debug for FileSet {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("FileSet").field("n_files", &self.files.len()).finish()
38 }
39}
40
41#[derive(Debug)]
42pub struct FileSetConfig {
43 n_file_sets: usize,
44 roots: Vec<(AbsPathBuf, usize)>,
45}
46
47impl FileSetConfig {
48 pub fn builder() -> FileSetConfigBuilder {
49 FileSetConfigBuilder::default()
50 }
51 pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> {
52 let mut res = vec![FileSet::default(); self.len()];
53 for (file_id, path) in vfs.iter() {
54 let root = self.classify(&path);
55 res[root].insert(file_id, path)
56 }
57 res
58 }
59 fn len(&self) -> usize {
60 self.n_file_sets
61 }
62 fn classify(&self, path: &VfsPath) -> usize {
63 for (root, idx) in self.roots.iter() {
64 if let Some(path) = path.as_path() {
65 if path.starts_with(root) {
66 return *idx;
67 }
68 }
69 }
70 self.len() - 1
71 }
72}
73
74pub struct FileSetConfigBuilder {
75 roots: Vec<Vec<AbsPathBuf>>,
76}
77
78impl Default for FileSetConfigBuilder {
79 fn default() -> Self {
80 FileSetConfigBuilder { roots: Vec::new() }
81 }
82}
83
84impl FileSetConfigBuilder {
85 pub fn add_file_set(&mut self, roots: Vec<AbsPathBuf>) {
86 self.roots.push(roots)
87 }
88 pub fn build(self) -> FileSetConfig {
89 let n_file_sets = self.roots.len() + 1;
90 let mut roots: Vec<(AbsPathBuf, usize)> = self
91 .roots
92 .into_iter()
93 .enumerate()
94 .flat_map(|(i, paths)| paths.into_iter().zip(iter::repeat(i)))
95 .collect();
96 roots.sort_by_key(|(path, _)| cmp::Reverse(path.to_string_lossy().len()));
97 FileSetConfig { n_file_sets, roots }
98 }
99}
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
new file mode 100644
index 000000000..75ce61cf9
--- /dev/null
+++ b/crates/vfs/src/lib.rs
@@ -0,0 +1,138 @@
1//! # Virtual File System
2//!
3//! VFS stores all files read by rust-analyzer. Reading file contents from VFS
4//! always returns the same contents, unless VFS was explicitly modified with
5//! `set_file_contents`. All changes to VFS are logged, and can be retrieved via
6//! `take_changes` method. The pack of changes is then pushed to `salsa` and
7//! triggers incremental recomputation.
8//!
9//! Files in VFS are identified with `FileId`s -- interned paths. The notion of
10//! the path, `VfsPath` is somewhat abstract: at the moment, it is represented
11//! as an `std::path::PathBuf` internally, but this is an implementation detail.
12//!
13//! VFS doesn't do IO or file watching itself. For that, see the `loader`
14//! module. `loader::Handle` is an object-safe trait which abstracts both file
15//! loading and file watching. `Handle` is dynamically configured with a set of
16//! directory entries which should be scanned and watched. `Handle` then
17//! asynchronously pushes file changes. Directory entries are configured in
18//! free-form via list of globs, it's up to the `Handle` to interpret the globs
19//! in any specific way.
20//!
21//! A simple `WalkdirLoaderHandle` is provided, which doesn't implement watching
22//! and just scans the directory using walkdir.
23//!
24//! VFS stores a flat list of files. `FileSet` can partition this list of files
25//! into disjoint sets of files. Traversal-like operations (including getting
26//! the neighbor file by the relative path) are handled by the `FileSet`.
27//! `FileSet`s are also pushed to salsa and cause it to re-check `mod foo;`
28//! declarations when files are created or deleted.
29//!
30//! `file_set::FileSet` and `loader::Entry` play similar, but different roles.
31//! Both specify the "set of paths/files", one is geared towards file watching,
32//! the other towards salsa changes. In particular, single `file_set::FileSet`
33//! may correspond to several `loader::Entry`. For example, a crate from
34//! crates.io which uses code generation would have two `Entries` -- for sources
35//! in `~/.cargo`, and for generated code in `./target/debug/build`. It will
36//! have a single `FileSet` which unions the two sources.
37mod vfs_path;
38mod path_interner;
39pub mod file_set;
40pub mod loader;
41pub mod walkdir_loader;
42
43use std::{fmt, mem};
44
45use crate::path_interner::PathInterner;
46
47pub use crate::vfs_path::VfsPath;
48pub use paths::{AbsPath, AbsPathBuf};
49
50#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
51pub struct FileId(pub u32);
52
53#[derive(Default)]
54pub struct Vfs {
55 interner: PathInterner,
56 data: Vec<Option<Vec<u8>>>,
57 changes: Vec<ChangedFile>,
58}
59
60pub struct ChangedFile {
61 pub file_id: FileId,
62 pub change_kind: ChangeKind,
63}
64
65impl ChangedFile {
66 pub fn exists(&self) -> bool {
67 self.change_kind != ChangeKind::Delete
68 }
69 pub fn is_created_or_deleted(&self) -> bool {
70 matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete)
71 }
72}
73
74#[derive(Eq, PartialEq)]
75pub enum ChangeKind {
76 Create,
77 Modify,
78 Delete,
79}
80
81impl Vfs {
82 pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
83 self.interner.get(path).filter(|&it| self.get(it).is_some())
84 }
85 pub fn file_path(&self, file_id: FileId) -> VfsPath {
86 self.interner.lookup(file_id).clone()
87 }
88 pub fn file_contents(&self, file_id: FileId) -> &[u8] {
89 self.get(file_id).as_deref().unwrap()
90 }
91 pub fn iter(&self) -> impl Iterator<Item = (FileId, VfsPath)> + '_ {
92 (0..self.data.len())
93 .map(|it| FileId(it as u32))
94 .filter(move |&file_id| self.get(file_id).is_some())
95 .map(move |file_id| {
96 let path = self.interner.lookup(file_id).clone();
97 (file_id, path)
98 })
99 }
100 pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) {
101 let file_id = self.alloc_file_id(path);
102 let change_kind = match (&self.get(file_id), &contents) {
103 (None, None) => return,
104 (None, Some(_)) => ChangeKind::Create,
105 (Some(_), None) => ChangeKind::Delete,
106 (Some(old), Some(new)) if old == new => return,
107 (Some(_), Some(_)) => ChangeKind::Modify,
108 };
109
110 *self.get_mut(file_id) = contents;
111 self.changes.push(ChangedFile { file_id, change_kind })
112 }
113 pub fn has_changes(&self) -> bool {
114 !self.changes.is_empty()
115 }
116 pub fn take_changes(&mut self) -> Vec<ChangedFile> {
117 mem::take(&mut self.changes)
118 }
119 fn alloc_file_id(&mut self, path: VfsPath) -> FileId {
120 let file_id = self.interner.intern(path);
121 let idx = file_id.0 as usize;
122 let len = self.data.len().max(idx + 1);
123 self.data.resize_with(len, || None);
124 file_id
125 }
126 fn get(&self, file_id: FileId) -> &Option<Vec<u8>> {
127 &self.data[file_id.0 as usize]
128 }
129 fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> {
130 &mut self.data[file_id.0 as usize]
131 }
132}
133
134impl fmt::Debug for Vfs {
135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136 f.debug_struct("Vfs").field("n_files", &self.data.len()).finish()
137 }
138}
diff --git a/crates/vfs/src/loader.rs b/crates/vfs/src/loader.rs
new file mode 100644
index 000000000..5a0ca68f3
--- /dev/null
+++ b/crates/vfs/src/loader.rs
@@ -0,0 +1,69 @@
1//! Object safe interface for file watching and reading.
2use std::fmt;
3
4use paths::AbsPathBuf;
5
6pub enum Entry {
7 Files(Vec<AbsPathBuf>),
8 Directory { path: AbsPathBuf, globs: Vec<String> },
9}
10
11pub struct Config {
12 pub load: Vec<Entry>,
13 pub watch: Vec<usize>,
14}
15
16pub enum Message {
17 DidSwitchConfig { n_entries: usize },
18 DidLoadAllEntries,
19 Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
20}
21
22pub type Sender = Box<dyn Fn(Message) + Send>;
23
24pub trait Handle: fmt::Debug {
25 fn spawn(sender: Sender) -> Self
26 where
27 Self: Sized;
28 fn set_config(&mut self, config: Config);
29 fn invalidate(&mut self, path: AbsPathBuf);
30 fn load_sync(&mut self, path: &AbsPathBuf) -> Option<Vec<u8>>;
31}
32
33impl Entry {
34 pub fn rs_files_recursively(base: AbsPathBuf) -> Entry {
35 Entry::Directory { path: base, globs: globs(&["*.rs"]) }
36 }
37 pub fn local_cargo_package(base: AbsPathBuf) -> Entry {
38 Entry::Directory { path: base, globs: globs(&["*.rs", "!/target/"]) }
39 }
40 pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry {
41 Entry::Directory {
42 path: base,
43 globs: globs(&["*.rs", "!/tests/", "!/examples/", "!/benches/"]),
44 }
45 }
46}
47
48fn globs(globs: &[&str]) -> Vec<String> {
49 globs.iter().map(|it| it.to_string()).collect()
50}
51
52impl fmt::Debug for Message {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 Message::Loaded { files } => {
56 f.debug_struct("Loaded").field("n_files", &files.len()).finish()
57 }
58 Message::DidSwitchConfig { n_entries } => {
59 f.debug_struct("DidSwitchConfig").field("n_entries", n_entries).finish()
60 }
61 Message::DidLoadAllEntries => f.debug_struct("DidLoadAllEntries").finish(),
62 }
63 }
64}
65
66#[test]
67fn handle_is_object_safe() {
68 fn _assert(_: &dyn Handle) {}
69}
diff --git a/crates/vfs/src/path_interner.rs b/crates/vfs/src/path_interner.rs
new file mode 100644
index 000000000..4f70d61e8
--- /dev/null
+++ b/crates/vfs/src/path_interner.rs
@@ -0,0 +1,31 @@
1//! Maps paths to compact integer ids. We don't care about clearings paths which
2//! no longer exist -- the assumption is total size of paths we ever look at is
3//! not too big.
4use rustc_hash::FxHashMap;
5
6use crate::{FileId, VfsPath};
7
8#[derive(Default)]
9pub(crate) struct PathInterner {
10 map: FxHashMap<VfsPath, FileId>,
11 vec: Vec<VfsPath>,
12}
13
14impl PathInterner {
15 pub(crate) fn get(&self, path: &VfsPath) -> Option<FileId> {
16 self.map.get(path).copied()
17 }
18 pub(crate) fn intern(&mut self, path: VfsPath) -> FileId {
19 if let Some(id) = self.get(&path) {
20 return id;
21 }
22 let id = FileId(self.vec.len() as u32);
23 self.map.insert(path.clone(), id);
24 self.vec.push(path);
25 id
26 }
27
28 pub(crate) fn lookup(&self, id: FileId) -> &VfsPath {
29 &self.vec[id.0 as usize]
30 }
31}
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs
new file mode 100644
index 000000000..de5dc0bf3
--- /dev/null
+++ b/crates/vfs/src/vfs_path.rs
@@ -0,0 +1,49 @@
1//! Abstract-ish representation of paths for VFS.
2use std::fmt;
3
4use paths::{AbsPath, AbsPathBuf};
5
6/// Long-term, we want to support files which do not reside in the file-system,
7/// so we treat VfsPaths as opaque identifiers.
8#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
9pub struct VfsPath(VfsPathRepr);
10
11impl VfsPath {
12 pub fn as_path(&self) -> Option<&AbsPath> {
13 match &self.0 {
14 VfsPathRepr::PathBuf(it) => Some(it.as_path()),
15 }
16 }
17 pub fn join(&self, path: &str) -> VfsPath {
18 match &self.0 {
19 VfsPathRepr::PathBuf(it) => {
20 let res = it.join(path).normalize();
21 VfsPath(VfsPathRepr::PathBuf(res))
22 }
23 }
24 }
25 pub fn pop(&mut self) -> bool {
26 match &mut self.0 {
27 VfsPathRepr::PathBuf(it) => it.pop(),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
33enum VfsPathRepr {
34 PathBuf(AbsPathBuf),
35}
36
37impl From<AbsPathBuf> for VfsPath {
38 fn from(v: AbsPathBuf) -> Self {
39 VfsPath(VfsPathRepr::PathBuf(v))
40 }
41}
42
43impl fmt::Display for VfsPath {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match &self.0 {
46 VfsPathRepr::PathBuf(it) => fmt::Display::fmt(&it.display(), f),
47 }
48 }
49}
diff --git a/crates/vfs/src/walkdir_loader.rs b/crates/vfs/src/walkdir_loader.rs
new file mode 100644
index 000000000..13e59e3f3
--- /dev/null
+++ b/crates/vfs/src/walkdir_loader.rs
@@ -0,0 +1,108 @@
1//! A walkdir-based implementation of `loader::Handle`, which doesn't try to
2//! watch files.
3use std::convert::TryFrom;
4
5use globset::{Glob, GlobSetBuilder};
6use paths::{AbsPath, AbsPathBuf};
7use walkdir::WalkDir;
8
9use crate::loader;
10
11#[derive(Debug)]
12pub struct WalkdirLoaderHandle {
13 // Relative order of fields below is significant.
14 sender: crossbeam_channel::Sender<Message>,
15 _thread: jod_thread::JoinHandle,
16}
17
18enum Message {
19 Config(loader::Config),
20 Invalidate(AbsPathBuf),
21}
22
23impl loader::Handle for WalkdirLoaderHandle {
24 fn spawn(sender: loader::Sender) -> WalkdirLoaderHandle {
25 let actor = WalkdirLoaderActor { sender };
26 let (sender, receiver) = crossbeam_channel::unbounded::<Message>();
27 let thread = jod_thread::spawn(move || actor.run(receiver));
28 WalkdirLoaderHandle { sender, _thread: thread }
29 }
30 fn set_config(&mut self, config: loader::Config) {
31 self.sender.send(Message::Config(config)).unwrap()
32 }
33 fn invalidate(&mut self, path: AbsPathBuf) {
34 self.sender.send(Message::Invalidate(path)).unwrap();
35 }
36 fn load_sync(&mut self, path: &AbsPathBuf) -> Option<Vec<u8>> {
37 read(path)
38 }
39}
40
41struct WalkdirLoaderActor {
42 sender: loader::Sender,
43}
44
45impl WalkdirLoaderActor {
46 fn run(mut self, receiver: crossbeam_channel::Receiver<Message>) {
47 for msg in receiver {
48 match msg {
49 Message::Config(config) => {
50 self.send(loader::Message::DidSwitchConfig { n_entries: config.load.len() });
51 for entry in config.load.into_iter() {
52 let files = self.load_entry(entry);
53 self.send(loader::Message::Loaded { files });
54 }
55 drop(config.watch);
56 self.send(loader::Message::DidLoadAllEntries);
57 }
58 Message::Invalidate(path) => {
59 let contents = read(path.as_path());
60 let files = vec![(path, contents)];
61 self.send(loader::Message::Loaded { files });
62 }
63 }
64 }
65 }
66 fn load_entry(&mut self, entry: loader::Entry) -> Vec<(AbsPathBuf, Option<Vec<u8>>)> {
67 match entry {
68 loader::Entry::Files(files) => files
69 .into_iter()
70 .map(|file| {
71 let contents = read(file.as_path());
72 (file, contents)
73 })
74 .collect::<Vec<_>>(),
75 loader::Entry::Directory { path, globs } => {
76 let globset = {
77 let mut builder = GlobSetBuilder::new();
78 for glob in &globs {
79 builder.add(Glob::new(glob).unwrap());
80 }
81 builder.build().unwrap()
82 };
83
84 let files = WalkDir::new(path)
85 .into_iter()
86 .filter_map(|it| it.ok())
87 .filter(|it| it.file_type().is_file())
88 .map(|it| it.into_path())
89 .map(|it| AbsPathBuf::try_from(it).unwrap())
90 .filter(|it| globset.is_match(&it));
91
92 files
93 .map(|file| {
94 let contents = read(file.as_path());
95 (file, contents)
96 })
97 .collect()
98 }
99 }
100 }
101 fn send(&mut self, msg: loader::Message) {
102 (self.sender)(msg)
103 }
104}
105
106fn read(path: &AbsPath) -> Option<Vec<u8>> {
107 std::fs::read(path).ok()
108}
diff --git a/docs/dev/README.md b/docs/dev/README.md
index 1ce8666e3..1b63d8223 100644
--- a/docs/dev/README.md
+++ b/docs/dev/README.md
@@ -1,6 +1,6 @@
1# Contributing Quick Start 1# Contributing Quick Start
2 2
3Rust Analyzer is just a usual rust project, which is organized as a Cargo 3Rust Analyzer is an ordinary Rust project, which is organized as a Cargo
4workspace, builds on stable and doesn't depend on C libraries. So, just 4workspace, builds on stable and doesn't depend on C libraries. So, just
5 5
6``` 6```
@@ -65,7 +65,7 @@ directory).
65 65
66# Launching rust-analyzer 66# Launching rust-analyzer
67 67
68Debugging language server can be tricky: LSP is rather chatty, so driving it 68Debugging the language server can be tricky: LSP is rather chatty, so driving it
69from the command line is not really feasible, driving it via VS Code requires 69from the command line is not really feasible, driving it via VS Code requires
70interacting with two processes. 70interacting with two processes.
71 71
@@ -73,14 +73,14 @@ For this reason, the best way to see how rust-analyzer works is to find a
73relevant test and execute it (VS Code includes an action for running a single 73relevant test and execute it (VS Code includes an action for running a single
74test). 74test).
75 75
76However, launching a VS Code instance with locally build language server is 76However, launching a VS Code instance with a locally built language server is
77possible. There's **"Run Extension (Debug Build)"** launch configuration for this. 77possible. There's **"Run Extension (Debug Build)"** launch configuration for this.
78 78
79In general, I use one of the following workflows for fixing bugs and 79In general, I use one of the following workflows for fixing bugs and
80implementing features. 80implementing features.
81 81
82If the problem concerns only internal parts of rust-analyzer (i.e. I don't need 82If the problem concerns only internal parts of rust-analyzer (i.e. I don't need
83to touch `rust-analyzer` crate or TypeScript code), there is a unit-test for it. 83to touch the `rust-analyzer` crate or TypeScript code), there is a unit-test for it.
84So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and 84So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and
85then just do printf-driven development/debugging. As a sanity check after I'm 85then just do printf-driven development/debugging. As a sanity check after I'm
86done, I use `cargo xtask install --server` and **Reload Window** action in VS 86done, I use `cargo xtask install --server` and **Reload Window** action in VS
@@ -88,8 +88,8 @@ Code to sanity check that the thing works as I expect.
88 88
89If the problem concerns only the VS Code extension, I use **Run Installed Extension** 89If the problem concerns only the VS Code extension, I use **Run Installed Extension**
90launch configuration from `launch.json`. Notably, this uses the usual 90launch configuration from `launch.json`. Notably, this uses the usual
91`rust-analyzer` binary from `PATH`. For this it is important to have the following 91`rust-analyzer` binary from `PATH`. For this, it is important to have the following
92in `setting.json` file: 92in your `settings.json` file:
93```json 93```json
94{ 94{
95 "rust-analyzer.serverPath": "rust-analyzer" 95 "rust-analyzer.serverPath": "rust-analyzer"
@@ -107,7 +107,7 @@ things up, sometimes I open a temporary hello-world project which has
107`"rust-analyzer.withSysroot": false` in `.code/settings.json`. This flag causes 107`"rust-analyzer.withSysroot": false` in `.code/settings.json`. This flag causes
108rust-analyzer to skip loading the sysroot, which greatly reduces the amount of 108rust-analyzer to skip loading the sysroot, which greatly reduces the amount of
109things rust-analyzer needs to do, and makes printf's more useful. Note that you 109things rust-analyzer needs to do, and makes printf's more useful. Note that you
110should only use `eprint!` family of macros for debugging: stdout is used for LSP 110should only use the `eprint!` family of macros for debugging: stdout is used for LSP
111communication, and `print!` would break it. 111communication, and `print!` would break it.
112 112
113If I need to fix something simultaneously in the server and in the client, I 113If I need to fix something simultaneously in the server and in the client, I
@@ -119,20 +119,20 @@ performance optimizations, or for bug minimization.
119 119
120# Code Style & Review Process 120# Code Style & Review Process
121 121
122Our approach to "clean code" is two fold: 122Our approach to "clean code" is two-fold:
123 123
124* We generally don't block PRs on style changes. 124* We generally don't block PRs on style changes.
125* At the same time, all code in rust-analyzer is constantly refactored. 125* At the same time, all code in rust-analyzer is constantly refactored.
126 126
127It is explicitly OK for reviewer to flag only some nits in the PR, and than send a follow up cleanup PR for things which are easier to explain by example, cc-ing the original author. 127It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author.
128Sending small cleanup PRs (like rename a single local variable) is encouraged. 128Sending small cleanup PRs (like renaming a single local variable) is encouraged.
129 129
130## Scale of Changes 130## Scale of Changes
131 131
132Everyone knows that it's better to send small & focused pull requests. 132Everyone knows that it's better to send small & focused pull requests.
133The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. 133The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
134 134
135The main thing too keep an eye on is the boundaries between various components. 135The main things to keep an eye on are the boundaries between various components.
136There are three kinds of changes: 136There are three kinds of changes:
137 137
1381. Internals of a single component are changed. 1381. Internals of a single component are changed.
@@ -144,20 +144,20 @@ There are three kinds of changes:
144 A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups. 144 A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups.
145 145
1463. A new dependency between components is introduced. 1463. A new dependency between components is introduced.
147 Specifically, you add a `pub use` reexport from another crate or you add a new line to `[dependencies]` section of `Cargo.toml`. 147 Specifically, you add a `pub use` reexport from another crate or you add a new line to the `[dependencies]` section of `Cargo.toml`.
148 A good example here would be adding reference search capability to the assists crates. 148 A good example here would be adding reference search capability to the assists crates.
149 149
150For the first group, the change is generally merged as long as: 150For the first group, the change is generally merged as long as:
151 151
152* it works for the happy case, 152* it works for the happy case,
153* it has tests, 153* it has tests,
154* it doesn't panic for unhappy case. 154* it doesn't panic for the unhappy case.
155 155
156For the second group, the change would be subjected to quite a bit of scrutiny and iteration. 156For the second group, the change would be subjected to quite a bit of scrutiny and iteration.
157The new API needs to be right (or at least easy to change later). 157The new API needs to be right (or at least easy to change later).
158The actual implementation doesn't matter that much. 158The actual implementation doesn't matter that much.
159It's very important to minimize the amount of changed lines of code for changes of the second kind. 159It's very important to minimize the amount of changed lines of code for changes of the second kind.
160Often, you start doing change of the first kind, only to realise that you need to elevate to a change of the second kind. 160Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind.
161In this case, we'll probably ask you to split API changes into a separate PR. 161In this case, we'll probably ask you to split API changes into a separate PR.
162 162
163Changes of the third group should be pretty rare, so we don't specify any specific process for them. 163Changes of the third group should be pretty rare, so we don't specify any specific process for them.
@@ -236,6 +236,13 @@ struct Foo {
236} 236}
237``` 237```
238 238
239## Variable Naming
240
241We generally use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
242The default name is a lowercased name of the type: `global_state: GlobalState`.
243Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
244The default name for "result of the function" local variable is `res`.
245
239## Preconditions 246## Preconditions
240 247
241Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee): 248Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee):
@@ -258,8 +265,8 @@ fn frobnicate(walrus: Option<Walrus>) {
258 265
259## Premature Pessimization 266## Premature Pessimization
260 267
261While we don't specifically optimize code yet, avoid writing the code which is slower than it needs to be. 268While we don't specifically optimize code yet, avoid writing code which is slower than it needs to be.
262Don't allocate a `Vec` were an iterator would do, don't allocate strings needlessly. 269Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly.
263 270
264```rust 271```rust
265// Good 272// Good
@@ -298,7 +305,7 @@ always obvious from the low-level code.
298## Incomplete syntax trees 305## Incomplete syntax trees
299 306
300Syntax trees are by design incomplete and do not enforce well-formedness. 307Syntax trees are by design incomplete and do not enforce well-formedness.
301If ast method returns an `Option`, it *can* be `None` at runtime, even if this is forbidden by the grammar. 308If an AST method returns an `Option`, it *can* be `None` at runtime, even if this is forbidden by the grammar.
302 309
303## LSP independence 310## LSP independence
304 311
@@ -326,7 +333,7 @@ The results are 100% Rust specific though.
326 333
327## Parser Tests 334## Parser Tests
328 335
329Test for parser (`ra_parser`) live in `ra_syntax` crate (see `test_data` direcotory). 336Tests for the parser (`ra_parser`) live in the `ra_syntax` crate (see `test_data` directory).
330There are two kinds of tests: 337There are two kinds of tests:
331 338
332* Manually written test cases in `parser/ok` and `parser/err` 339* Manually written test cases in `parser/ok` and `parser/err`
@@ -335,6 +342,12 @@ There are two kinds of tests:
335The purpose of inline tests is not to achieve full coverage by test cases, but to explain to the reader of the code what each particular `if` and `match` is responsible for. 342The purpose of inline tests is not to achieve full coverage by test cases, but to explain to the reader of the code what each particular `if` and `match` is responsible for.
336If you are tempted to add a large inline test, it might be a good idea to leave only the simplest example in place, and move the test to a manual `parser/ok` test. 343If you are tempted to add a large inline test, it might be a good idea to leave only the simplest example in place, and move the test to a manual `parser/ok` test.
337 344
345To update test data, run with `UPDATE_EXPECTATIONS` variable:
346
347```bash
348env UPDATE_EXPECTATIONS=1 cargo qt
349```
350
338# Logging 351# Logging
339 352
340Logging is done by both rust-analyzer and VS Code, so it might be tricky to 353Logging is done by both rust-analyzer and VS Code, so it might be tricky to
@@ -361,7 +374,7 @@ To log all communication between the server and the client, there are two choice
361 [@DJMcNab](https://github.com/DJMcNab) for setting this awesome infra up! 374 [@DJMcNab](https://github.com/DJMcNab) for setting this awesome infra up!
362 375
363 376
364There's also two VS Code commands which might be of interest: 377There are also two VS Code commands which might be of interest:
365 378
366* `Rust Analyzer: Status` shows some memory-usage statistics. To take full 379* `Rust Analyzer: Status` shows some memory-usage statistics. To take full
367 advantage of it, you need to compile rust-analyzer with jemalloc support: 380 advantage of it, you need to compile rust-analyzer with jemalloc support:
diff --git a/editors/code/package.json b/editors/code/package.json
index e2027970d..3acc375f6 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -525,6 +525,24 @@
525 "markdownDescription": "Internal config for debugging, disables loading of sysroot crates", 525 "markdownDescription": "Internal config for debugging, disables loading of sysroot crates",
526 "type": "boolean", 526 "type": "boolean",
527 "default": true 527 "default": true
528 },
529 "rust-analyzer.diagnostics.warningsAsInfo": {
530 "type": "array",
531 "uniqueItems": true,
532 "items": {
533 "type": "string"
534 },
535 "description": "List of warnings that should be displayed with info severity.\nThe warnings will be indicated by a blue squiggly underline in code and a blue icon in the problems panel.",
536 "default": []
537 },
538 "rust-analyzer.diagnostics.warningsAsHint": {
539 "type": "array",
540 "uniqueItems": true,
541 "items": {
542 "type": "string"
543 },
544 "description": "List of warnings 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 te problems panel.",
545 "default": []
528 } 546 }
529 } 547 }
530 }, 548 },