diff options
Diffstat (limited to 'crates')
76 files changed, 4595 insertions, 2010 deletions
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] | ||
2 | name = "paths" | ||
3 | version = "0.1.0" | ||
4 | authors = ["rust-analyzer developers"] | ||
5 | edition = "2018" | ||
6 | |||
7 | [lib] | ||
8 | doctest = false | ||
diff --git a/crates/paths/src/lib.rs b/crates/paths/src/lib.rs new file mode 100644 index 000000000..190c50913 --- /dev/null +++ b/crates/paths/src/lib.rs | |||
@@ -0,0 +1,126 @@ | |||
1 | //! Thin wrappers around `std::path`, distinguishing between absolute and | ||
2 | //! relative paths. | ||
3 | use std::{ | ||
4 | convert::{TryFrom, TryInto}, | ||
5 | io, ops, | ||
6 | path::{Component, Path, PathBuf}, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
10 | pub struct AbsPathBuf(PathBuf); | ||
11 | |||
12 | impl From<AbsPathBuf> for PathBuf { | ||
13 | fn from(AbsPathBuf(path_buf): AbsPathBuf) -> PathBuf { | ||
14 | path_buf | ||
15 | } | ||
16 | } | ||
17 | |||
18 | impl ops::Deref for AbsPathBuf { | ||
19 | type Target = AbsPath; | ||
20 | fn deref(&self) -> &AbsPath { | ||
21 | self.as_path() | ||
22 | } | ||
23 | } | ||
24 | |||
25 | impl AsRef<Path> for AbsPathBuf { | ||
26 | fn as_ref(&self) -> &Path { | ||
27 | self.0.as_path() | ||
28 | } | ||
29 | } | ||
30 | |||
31 | impl 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 | |||
41 | impl 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 | |||
48 | impl AbsPathBuf { | ||
49 | pub fn canonicalized(path: &Path) -> io::Result<AbsPathBuf> { | ||
50 | path.canonicalize().map(|it| AbsPathBuf::try_from(it).unwrap()) | ||
51 | } | ||
52 | pub fn as_path(&self) -> &AbsPath { | ||
53 | AbsPath::new_unchecked(self.0.as_path()) | ||
54 | } | ||
55 | pub fn pop(&mut self) -> bool { | ||
56 | self.0.pop() | ||
57 | } | ||
58 | } | ||
59 | |||
60 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
61 | #[repr(transparent)] | ||
62 | pub struct AbsPath(Path); | ||
63 | |||
64 | impl ops::Deref for AbsPath { | ||
65 | type Target = Path; | ||
66 | fn deref(&self) -> &Path { | ||
67 | &self.0 | ||
68 | } | ||
69 | } | ||
70 | |||
71 | impl AsRef<Path> for AbsPath { | ||
72 | fn as_ref(&self) -> &Path { | ||
73 | &self.0 | ||
74 | } | ||
75 | } | ||
76 | |||
77 | impl<'a> TryFrom<&'a Path> for &'a AbsPath { | ||
78 | type Error = &'a Path; | ||
79 | fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> { | ||
80 | if !path.is_absolute() { | ||
81 | return Err(path); | ||
82 | } | ||
83 | Ok(AbsPath::new_unchecked(path)) | ||
84 | } | ||
85 | } | ||
86 | |||
87 | impl AbsPath { | ||
88 | fn new_unchecked(path: &Path) -> &AbsPath { | ||
89 | unsafe { &*(path as *const Path as *const AbsPath) } | ||
90 | } | ||
91 | |||
92 | pub fn join(&self, path: impl AsRef<Path>) -> AbsPathBuf { | ||
93 | self.as_ref().join(path).try_into().unwrap() | ||
94 | } | ||
95 | pub fn normalize(&self) -> AbsPathBuf { | ||
96 | AbsPathBuf(normalize_path(&self.0)) | ||
97 | } | ||
98 | } | ||
99 | |||
100 | // https://github.com/rust-lang/cargo/blob/79c769c3d7b4c2cf6a93781575b7f592ef974255/src/cargo/util/paths.rs#L60-L85 | ||
101 | fn normalize_path(path: &Path) -> PathBuf { | ||
102 | let mut components = path.components().peekable(); | ||
103 | let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { | ||
104 | components.next(); | ||
105 | PathBuf::from(c.as_os_str()) | ||
106 | } else { | ||
107 | PathBuf::new() | ||
108 | }; | ||
109 | |||
110 | for component in components { | ||
111 | match component { | ||
112 | Component::Prefix(..) => unreachable!(), | ||
113 | Component::RootDir => { | ||
114 | ret.push(component.as_os_str()); | ||
115 | } | ||
116 | Component::CurDir => {} | ||
117 | Component::ParentDir => { | ||
118 | ret.pop(); | ||
119 | } | ||
120 | Component::Normal(c) => { | ||
121 | ret.push(c); | ||
122 | } | ||
123 | } | ||
124 | } | ||
125 | ret | ||
126 | } | ||
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 | ||
138 | fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool { | 138 | fn 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 @@ | |||
1 | use hir; | 1 | use hir; |
2 | use ra_syntax::{ast, AstNode, SmolStr, TextRange}; | 2 | use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode}; |
3 | 3 | ||
4 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; | 4 | use 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`. | ||
82 | fn 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 | |||
104 | fn 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 | |||
124 | fn 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)] |
77 | mod tests { | 145 | mod 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" |
87 | std::fmt::Debug<|> | 155 | std::fmt::Debug<|> |
88 | ", | 156 | ", |
89 | " | 157 | r" |
90 | use std::fmt::Debug; | 158 | use std::fmt::Debug; |
91 | 159 | ||
92 | Debug | 160 | Debug |
@@ -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" |
101 | std::fmt::Debug<|> | 169 | std::fmt::Debug<|> |
102 | 170 | ||
103 | fn main() { | 171 | fn main() { |
104 | } | 172 | } |
105 | ", | 173 | ", |
106 | " | 174 | r" |
107 | use std::fmt::Debug; | 175 | use std::fmt::Debug; |
108 | 176 | ||
109 | Debug | 177 | Debug |
@@ -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" |
122 | fn main() { | 190 | fn main() { |
123 | } | 191 | } |
124 | 192 | ||
125 | std::fmt::Debug<|> | 193 | std::fmt::Debug<|> |
126 | ", | 194 | ", |
127 | " | 195 | r" |
128 | use std::fmt::Debug; | 196 | use std::fmt::Debug; |
129 | 197 | ||
130 | fn main() { | 198 | fn 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" |
143 | std::fmt<|>::Debug | 211 | std::fmt<|>::Debug |
144 | ", | 212 | ", |
145 | " | 213 | r" |
146 | use std::fmt; | 214 | use std::fmt; |
147 | 215 | ||
148 | fmt::Debug | 216 | fmt::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" |
158 | use stdx; | 226 | use stdx; |
159 | 227 | ||
160 | impl std::fmt::Debug<|> for Foo { | 228 | impl std::fmt::Debug<|> for Foo { |
161 | } | 229 | } |
162 | ", | 230 | ", |
163 | " | 231 | r" |
164 | use stdx; | 232 | use stdx; |
165 | use std::fmt::Debug; | 233 | use 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" |
178 | impl std::fmt::Debug<|> for Foo { | 246 | impl std::fmt::Debug<|> for Foo { |
179 | } | 247 | } |
180 | ", | 248 | ", |
181 | " | 249 | r" |
182 | use std::fmt::Debug; | 250 | use std::fmt::Debug; |
183 | 251 | ||
184 | impl Debug for Foo { | 252 | impl 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" |
212 | use std::fmt; | 280 | use std::fmt; |
213 | 281 | ||
214 | impl std::io<|> for Foo { | 282 | impl std::io<|> for Foo { |
215 | } | 283 | } |
216 | ", | 284 | ", |
217 | " | 285 | r" |
218 | use std::{io, fmt}; | 286 | use std::{io, fmt}; |
219 | 287 | ||
220 | impl io for Foo { | 288 | impl 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" |
231 | use std::fmt; | 299 | use std::fmt; |
232 | 300 | ||
233 | impl std::fmt::Debug<|> for Foo { | 301 | impl std::fmt::Debug<|> for Foo { |
234 | } | 302 | } |
235 | ", | 303 | ", |
236 | " | 304 | r" |
237 | use std::fmt::{self, Debug, }; | 305 | use std::fmt::{self, Debug, }; |
238 | 306 | ||
239 | impl Debug for Foo { | 307 | impl 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" |
250 | use std::fmt::Debug; | 318 | use std::fmt::Debug; |
251 | 319 | ||
252 | impl std::fmt<|> for Foo { | 320 | impl std::fmt<|> for Foo { |
253 | } | 321 | } |
254 | ", | 322 | ", |
255 | " | 323 | r" |
256 | use std::fmt::{self, Debug}; | 324 | use std::fmt::{self, Debug}; |
257 | 325 | ||
258 | impl fmt for Foo { | 326 | impl 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" |
269 | use std::fmt::{Debug, nested::{Display}}; | 337 | use std::fmt::{Debug, nested::{Display}}; |
270 | 338 | ||
271 | impl std::fmt::nested<|> for Foo { | 339 | impl std::fmt::nested<|> for Foo { |
272 | } | 340 | } |
273 | ", | 341 | ", |
274 | " | 342 | r" |
275 | use std::fmt::{Debug, nested::{Display, self}}; | 343 | use std::fmt::{Debug, nested::{Display, self}}; |
276 | 344 | ||
277 | impl nested for Foo { | 345 | impl 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" |
288 | use std::fmt::{Debug, nested::{self, Display}}; | 356 | use std::fmt::{Debug, nested::{self, Display}}; |
289 | 357 | ||
290 | impl std::fmt::nested<|> for Foo { | 358 | impl std::fmt::nested<|> for Foo { |
291 | } | 359 | } |
292 | ", | 360 | ", |
293 | " | 361 | r" |
294 | use std::fmt::{Debug, nested::{self, Display}}; | 362 | use std::fmt::{Debug, nested::{self, Display}}; |
295 | 363 | ||
296 | impl nested for Foo { | 364 | impl 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" |
307 | use std::fmt::{Debug, nested::{Display}}; | 375 | use std::fmt::{Debug, nested::{Display}}; |
308 | 376 | ||
309 | impl std::fmt::nested::Debug<|> for Foo { | 377 | impl std::fmt::nested::Debug<|> for Foo { |
310 | } | 378 | } |
311 | ", | 379 | ", |
312 | " | 380 | r" |
313 | use std::fmt::{Debug, nested::{Display, Debug}}; | 381 | use std::fmt::{Debug, nested::{Display, Debug}}; |
314 | 382 | ||
315 | impl Debug for Foo { | 383 | impl 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" |
326 | use std::fmt::Debug; | 394 | use std::fmt::Debug; |
327 | 395 | ||
328 | impl std::fmt::nested::Display<|> for Foo { | 396 | impl std::fmt::nested::Display<|> for Foo { |
329 | } | 397 | } |
330 | ", | 398 | ", |
331 | " | 399 | r" |
332 | use std::fmt::{nested::Display, Debug}; | 400 | use std::fmt::{nested::Display, Debug}; |
333 | 401 | ||
334 | impl Display for Foo { | 402 | impl 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" |
345 | use std::fmt::nested::Debug; | 413 | use std::fmt::nested::Debug; |
346 | 414 | ||
347 | impl std::fmt::Display<|> for Foo { | 415 | impl std::fmt::Display<|> for Foo { |
348 | } | 416 | } |
349 | ", | 417 | ", |
350 | " | 418 | r" |
351 | use std::fmt::{Display, nested::Debug}; | 419 | use std::fmt::{Display, nested::Debug}; |
352 | 420 | ||
353 | impl Display for Foo { | 421 | impl 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" |
364 | use crate::{ | 432 | use crate::{ |
365 | ty::{Substs, Ty}, | 433 | ty::{Substs, Ty}, |
366 | AssocItem, | 434 | AssocItem, |
@@ -368,7 +436,7 @@ use crate::{ | |||
368 | 436 | ||
369 | fn foo() { crate::ty::lower<|>::trait_env() } | 437 | fn foo() { crate::ty::lower<|>::trait_env() } |
370 | ", | 438 | ", |
371 | " | 439 | r" |
372 | use crate::{ | 440 | use 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" |
387 | use std::fmt as foo; | 455 | use std::fmt as foo; |
388 | 456 | ||
389 | impl foo::Debug<|> for Foo { | 457 | impl foo::Debug<|> for Foo { |
390 | } | 458 | } |
391 | ", | 459 | ", |
392 | " | 460 | r" |
393 | use std::fmt as foo; | 461 | use std::fmt as foo; |
394 | 462 | ||
395 | impl Debug for Foo { | 463 | impl 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" |
406 | impl foo<|> for Foo { | 474 | impl 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" |
417 | use std::fmt<|>; | 485 | use 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" |
427 | mod foo { | 495 | mod foo { |
428 | mod bar { | 496 | mod bar { |
429 | std::fmt::Debug<|> | 497 | std::fmt::Debug<|> |
430 | } | 498 | } |
431 | } | 499 | } |
432 | ", | 500 | ", |
433 | " | 501 | r" |
434 | mod foo { | 502 | mod 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 | ||
452 | fn main() { | 520 | fn main() { |
453 | std::fmt::Debug<|> | 521 | std::fmt::Debug<|> |
454 | } | 522 | } |
455 | ", | 523 | ", |
456 | " | 524 | r" |
457 | #![allow(dead_code)] | 525 | #![allow(dead_code)] |
458 | use std::fmt::Debug; | 526 | use 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" | ||
540 | fn main() { | ||
541 | std::fmt::Debug<|>; | ||
542 | let x: std::fmt::Debug = std::fmt::Debug; | ||
543 | } | ||
544 | ", | ||
545 | r" | ||
546 | use std::fmt::Debug; | ||
547 | |||
548 | fn 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" | ||
561 | mod 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 | |||
571 | fn f() { | ||
572 | std::fmt::Debug; | ||
573 | } | ||
574 | ", | ||
575 | r" | ||
576 | mod 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 | |||
588 | fn 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" | ||
600 | fn main() { | ||
601 | std::fmt::Debug<|>; | ||
602 | } | ||
603 | |||
604 | mod sub { | ||
605 | fn f() { | ||
606 | std::fmt::Debug; | ||
607 | } | ||
608 | } | ||
609 | ", | ||
610 | r" | ||
611 | use std::fmt::Debug; | ||
612 | |||
613 | fn main() { | ||
614 | Debug; | ||
615 | } | ||
616 | |||
617 | mod 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" | ||
631 | use std::fmt::Display; | ||
632 | |||
633 | fn main() { | ||
634 | std::fmt<|>; | ||
635 | } | ||
636 | ", | ||
637 | r" | ||
638 | use std::fmt::{self, Display}; | ||
639 | |||
640 | fn 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 | ||
14 | use crate::assist_config::SnippetCap; | 14 | use crate::assist_config::SnippetCap; |
15 | 15 | ||
16 | pub(crate) use insert_use::insert_use_statement; | 16 | pub(crate) use insert_use::{find_insert_use_container, insert_use_statement}; |
17 | 17 | ||
18 | #[derive(Clone, Copy, Debug)] | 18 | #[derive(Clone, Copy, Debug)] |
19 | pub(crate) enum Cursor<'a> { | 19 | pub(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::{ | |||
12 | use ra_text_edit::TextEditBuilder; | 12 | use ra_text_edit::TextEditBuilder; |
13 | 13 | ||
14 | use crate::assist_context::AssistContext; | 14 | use crate::assist_context::AssistContext; |
15 | use either::Either; | ||
16 | |||
17 | /// Determines the containing syntax node in which to insert a `use` statement affecting `position`. | ||
18 | pub(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/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 1a9f6cc76..ffd5278ec 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs | |||
@@ -26,8 +26,8 @@ use hir_ty::{ | |||
26 | autoderef, | 26 | autoderef, |
27 | display::{HirDisplayError, HirFormatter}, | 27 | display::{HirDisplayError, HirFormatter}, |
28 | expr::ExprValidator, | 28 | expr::ExprValidator, |
29 | method_resolution, ApplicationTy, Canonical, InEnvironment, Substs, TraitEnvironment, Ty, | 29 | method_resolution, ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs, |
30 | TyDefId, TypeCtor, | 30 | TraitEnvironment, Ty, TyDefId, TypeCtor, |
31 | }; | 31 | }; |
32 | use ra_db::{CrateId, CrateName, Edition, FileId}; | 32 | use ra_db::{CrateId, CrateName, Edition, FileId}; |
33 | use ra_prof::profile; | 33 | use ra_prof::profile; |
@@ -186,6 +186,22 @@ impl ModuleDef { | |||
186 | 186 | ||
187 | module.visibility_of(db, self) | 187 | module.visibility_of(db, self) |
188 | } | 188 | } |
189 | |||
190 | pub fn name(self, db: &dyn HirDatabase) -> Option<Name> { | ||
191 | match self { | ||
192 | ModuleDef::Adt(it) => Some(it.name(db)), | ||
193 | ModuleDef::Trait(it) => Some(it.name(db)), | ||
194 | ModuleDef::Function(it) => Some(it.name(db)), | ||
195 | ModuleDef::EnumVariant(it) => Some(it.name(db)), | ||
196 | ModuleDef::TypeAlias(it) => Some(it.name(db)), | ||
197 | |||
198 | ModuleDef::Module(it) => it.name(db), | ||
199 | ModuleDef::Const(it) => it.name(db), | ||
200 | ModuleDef::Static(it) => it.name(db), | ||
201 | |||
202 | ModuleDef::BuiltinType(it) => Some(it.as_name()), | ||
203 | } | ||
204 | } | ||
189 | } | 205 | } |
190 | 206 | ||
191 | pub use hir_def::{ | 207 | pub use hir_def::{ |
@@ -1359,6 +1375,27 @@ impl Type { | |||
1359 | Some(adt.into()) | 1375 | Some(adt.into()) |
1360 | } | 1376 | } |
1361 | 1377 | ||
1378 | pub fn as_dyn_trait(&self) -> Option<Trait> { | ||
1379 | self.ty.value.dyn_trait().map(Into::into) | ||
1380 | } | ||
1381 | |||
1382 | pub fn as_impl_traits(&self, db: &dyn HirDatabase) -> Option<Vec<Trait>> { | ||
1383 | self.ty.value.impl_trait_bounds(db).map(|it| { | ||
1384 | it.into_iter() | ||
1385 | .filter_map(|pred| match pred { | ||
1386 | hir_ty::GenericPredicate::Implemented(trait_ref) => { | ||
1387 | Some(Trait::from(trait_ref.trait_)) | ||
1388 | } | ||
1389 | _ => None, | ||
1390 | }) | ||
1391 | .collect() | ||
1392 | }) | ||
1393 | } | ||
1394 | |||
1395 | pub fn as_associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<Trait> { | ||
1396 | self.ty.value.associated_type_parent_trait(db).map(Into::into) | ||
1397 | } | ||
1398 | |||
1362 | // FIXME: provide required accessors such that it becomes implementable from outside. | 1399 | // FIXME: provide required accessors such that it becomes implementable from outside. |
1363 | pub fn is_equal_for_find_impls(&self, other: &Type) -> bool { | 1400 | pub fn is_equal_for_find_impls(&self, other: &Type) -> bool { |
1364 | match (&self.ty.value, &other.ty.value) { | 1401 | match (&self.ty.value, &other.ty.value) { |
@@ -1380,6 +1417,80 @@ impl Type { | |||
1380 | ty: InEnvironment { value: ty, environment: self.ty.environment.clone() }, | 1417 | ty: InEnvironment { value: ty, environment: self.ty.environment.clone() }, |
1381 | } | 1418 | } |
1382 | } | 1419 | } |
1420 | |||
1421 | pub fn walk(&self, db: &dyn HirDatabase, mut cb: impl FnMut(Type)) { | ||
1422 | // TypeWalk::walk for a Ty at first visits parameters and only after that the Ty itself. | ||
1423 | // We need a different order here. | ||
1424 | |||
1425 | fn walk_substs( | ||
1426 | db: &dyn HirDatabase, | ||
1427 | type_: &Type, | ||
1428 | substs: &Substs, | ||
1429 | cb: &mut impl FnMut(Type), | ||
1430 | ) { | ||
1431 | for ty in substs.iter() { | ||
1432 | walk_type(db, &type_.derived(ty.clone()), cb); | ||
1433 | } | ||
1434 | } | ||
1435 | |||
1436 | fn walk_bounds( | ||
1437 | db: &dyn HirDatabase, | ||
1438 | type_: &Type, | ||
1439 | bounds: &[GenericPredicate], | ||
1440 | cb: &mut impl FnMut(Type), | ||
1441 | ) { | ||
1442 | for pred in bounds { | ||
1443 | match pred { | ||
1444 | GenericPredicate::Implemented(trait_ref) => { | ||
1445 | cb(type_.clone()); | ||
1446 | walk_substs(db, type_, &trait_ref.substs, cb); | ||
1447 | } | ||
1448 | _ => (), | ||
1449 | } | ||
1450 | } | ||
1451 | } | ||
1452 | |||
1453 | fn walk_type(db: &dyn HirDatabase, type_: &Type, cb: &mut impl FnMut(Type)) { | ||
1454 | let ty = type_.ty.value.strip_references(); | ||
1455 | match ty { | ||
1456 | Ty::Apply(ApplicationTy { ctor, parameters }) => { | ||
1457 | match ctor { | ||
1458 | TypeCtor::Adt(_) => { | ||
1459 | cb(type_.derived(ty.clone())); | ||
1460 | } | ||
1461 | TypeCtor::AssociatedType(_) => { | ||
1462 | if let Some(_) = ty.associated_type_parent_trait(db) { | ||
1463 | cb(type_.derived(ty.clone())); | ||
1464 | } | ||
1465 | } | ||
1466 | _ => (), | ||
1467 | } | ||
1468 | |||
1469 | // adt params, tuples, etc... | ||
1470 | walk_substs(db, type_, parameters, cb); | ||
1471 | } | ||
1472 | Ty::Opaque(opaque_ty) => { | ||
1473 | if let Some(bounds) = ty.impl_trait_bounds(db) { | ||
1474 | walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb); | ||
1475 | } | ||
1476 | |||
1477 | walk_substs(db, type_, &opaque_ty.parameters, cb); | ||
1478 | } | ||
1479 | Ty::Placeholder(_) => { | ||
1480 | if let Some(bounds) = ty.impl_trait_bounds(db) { | ||
1481 | walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb); | ||
1482 | } | ||
1483 | } | ||
1484 | Ty::Dyn(bounds) => { | ||
1485 | walk_bounds(db, &type_.derived(ty.clone()), bounds.as_ref(), cb); | ||
1486 | } | ||
1487 | |||
1488 | _ => (), | ||
1489 | } | ||
1490 | } | ||
1491 | |||
1492 | walk_type(db, self, &mut cb); | ||
1493 | } | ||
1383 | } | 1494 | } |
1384 | 1495 | ||
1385 | impl HirDisplay for Type { | 1496 | impl HirDisplay for Type { |
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 @@ | |||
3 | use std::any::Any; | 3 | use std::any::Any; |
4 | 4 | ||
5 | use hir_expand::diagnostics::Diagnostic; | 5 | use hir_expand::diagnostics::Diagnostic; |
6 | use ra_db::RelativePathBuf; | ||
7 | use ra_syntax::{ast, AstPtr, SyntaxNodePtr}; | 6 | use ra_syntax::{ast, AstPtr, SyntaxNodePtr}; |
8 | 7 | ||
9 | use hir_expand::{HirFileId, InFile}; | 8 | use hir_expand::{HirFileId, InFile}; |
@@ -12,7 +11,7 @@ use hir_expand::{HirFileId, InFile}; | |||
12 | pub struct UnresolvedModule { | 11 | pub 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 | ||
18 | impl Diagnostic for UnresolvedModule { | 17 | impl Diagnostic for UnresolvedModule { |
diff --git a/crates/ra_hir_def/src/nameres.rs b/crates/ra_hir_def/src/nameres.rs index f279c2ad4..b8560fdc9 100644 --- a/crates/ra_hir_def/src/nameres.rs +++ b/crates/ra_hir_def/src/nameres.rs | |||
@@ -119,13 +119,6 @@ impl Default for ModuleOrigin { | |||
119 | } | 119 | } |
120 | 120 | ||
121 | impl ModuleOrigin { | 121 | impl ModuleOrigin { |
122 | pub(crate) fn not_sure_file(file: Option<FileId>, declaration: AstId<ast::Module>) -> Self { | ||
123 | match file { | ||
124 | None => ModuleOrigin::Inline { definition: declaration }, | ||
125 | Some(definition) => ModuleOrigin::File { declaration, definition }, | ||
126 | } | ||
127 | } | ||
128 | |||
129 | fn declaration(&self) -> Option<AstId<ast::Module>> { | 122 | fn declaration(&self) -> Option<AstId<ast::Module>> { |
130 | match self { | 123 | match self { |
131 | ModuleOrigin::File { declaration: module, .. } | 124 | ModuleOrigin::File { declaration: module, .. } |
@@ -296,7 +289,6 @@ pub enum ModuleSource { | |||
296 | 289 | ||
297 | mod diagnostics { | 290 | mod diagnostics { |
298 | use hir_expand::diagnostics::DiagnosticSink; | 291 | use hir_expand::diagnostics::DiagnosticSink; |
299 | use ra_db::RelativePathBuf; | ||
300 | use ra_syntax::{ast, AstPtr}; | 292 | use ra_syntax::{ast, AstPtr}; |
301 | 293 | ||
302 | use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId}; | 294 | use crate::{db::DefDatabase, diagnostics::UnresolvedModule, nameres::LocalModuleId, AstId}; |
@@ -306,7 +298,7 @@ mod diagnostics { | |||
306 | UnresolvedModule { | 298 | UnresolvedModule { |
307 | module: LocalModuleId, | 299 | module: LocalModuleId, |
308 | declaration: AstId<ast::Module>, | 300 | declaration: AstId<ast::Module>, |
309 | candidate: RelativePathBuf, | 301 | candidate: String, |
310 | }, | 302 | }, |
311 | } | 303 | } |
312 | 304 | ||
diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 976e5e585..77baa4c69 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs | |||
@@ -825,7 +825,10 @@ impl ModCollector<'_, '_> { | |||
825 | let modules = &mut self.def_collector.def_map.modules; | 825 | let modules = &mut self.def_collector.def_map.modules; |
826 | let res = modules.alloc(ModuleData::default()); | 826 | let res = modules.alloc(ModuleData::default()); |
827 | modules[res].parent = Some(self.module_id); | 827 | modules[res].parent = Some(self.module_id); |
828 | modules[res].origin = ModuleOrigin::not_sure_file(definition, declaration); | 828 | modules[res].origin = match definition { |
829 | None => ModuleOrigin::Inline { definition: declaration }, | ||
830 | Some(definition) => ModuleOrigin::File { declaration, definition }, | ||
831 | }; | ||
829 | for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() { | 832 | for (name, mac) in modules[self.module_id].scope.collect_legacy_macros() { |
830 | modules[res].scope.define_legacy_macro(name, mac) | 833 | modules[res].scope.define_legacy_macro(name, mac) |
831 | } | 834 | } |
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 | ||
155 | impl Path { | 155 | impl 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..5495ce284 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: | ||
89 | //! | ||
90 | //! r_1, .., r_a, p_2, .., p_n | ||
76 | //! | 91 | //! |
77 | //! This returns zero or more new pattern-stacks, as follows. We look at the pattern `p_1` | 92 | //! * 1.2. `p_1 = c'(r_1, .., r_a')` where `c ≠ c'`. We discard the current stack and return |
78 | //! on top of the stack, and we have four cases: | 93 | //! nothing. |
79 | //! 1.1. `p_1 = c(r_1, .., r_a)`, i.e. the top of the stack has constructor `c`. We | 94 | //! * 1.3. `p_1 = _`. We push onto the stack as many wildcards as the constructor `c` has |
80 | //! push onto the stack the arguments of this constructor, and return the result: | 95 | //! arguments (its arity), and return the resulting stack: |
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 | //! | 96 | //! |
92 | //! 2. We can pop a wildcard off the top of the stack. This is called `D(p)`, where `p` is | 97 | //! _, .., _, p_2, .., p_n |
93 | //! a pattern-stack. | ||
94 | //! This is used when we know there are missing constructor cases, but there might be | ||
95 | //! existing wildcard patterns, so to check the usefulness of the matrix, we have to check | ||
96 | //! all its *other* components. | ||
97 | //! | 98 | //! |
98 | //! It is computed as follows. We look at the pattern `p_1` on top of the stack, | 99 | //! * 1.4. `p_1 = r_1 | r_2`. We expand the OR-pattern and then recurse on each resulting stack: |
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 | //! | 100 | //! |
108 | //! Note that the OR-patterns are not always used directly in Rust, but are used to derive the | 101 | //! S(c, (r_1, p_2, .., p_n)) |
109 | //! exhaustive integer matching rules, so they're written here for posterity. | 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: | ||
149 | //! | ||
150 | //! ```text | ||
151 | //! U(P, p) := U(S(c, P), S(c, p)) | ||
152 | //! ``` | ||
127 | //! | 153 | //! |
128 | //! Inductive step. (`n > 0`, i.e., whether there's at least one column | 154 | //! This special case is handled in `is_useful_specialized`. |
129 | //! [which may then be expanded into further columns later]) | ||
130 | //! We're going to match on the top of the new pattern-stack, `p_1`. | ||
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 | //! | 155 | //! |
138 | //! For example, if `P` is: | 156 | //! For example, if `P` is: |
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 | //! | 157 | //! |
148 | //! - If `p_1 == _`, then we look at the list of constructors that appear in the first | 158 | //! ```text |
149 | //! component of the rows of `P`: | 159 | //! [ |
150 | //! + If there are some constructors that aren't present, then we might think that the | 160 | //! [Some(true), _], |
151 | //! wildcard `_` is useful, since it covers those constructors that weren't covered | 161 | //! [None, 0], |
152 | //! before. | 162 | //! ] |
153 | //! That's almost correct, but only works if there were no wildcards in those first | 163 | //! ``` |
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 | //! | 164 | //! |
158 | //! For example, if `P` is: | 165 | //! and `p` is `[Some(false), 0]`, then we don't care about row 2 since we know `p` only |
159 | //! [ | 166 | //! matches values that row 2 doesn't. For row 1 however, we need to dig into the |
160 | //! [_, true, _], | 167 | //! arguments of `Some` to know whether some new value is covered. So we compute |
161 | //! [None, false, 1], | 168 | //! `U([[true, _]], [false, 0])`. |
162 | //! ] | ||
163 | //! and `p` is [_, false, _], the `Some` constructor doesn't appear in `P`. So if we | ||
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 | //! | 169 | //! |
167 | //! + Otherwise, all possible constructors (for the relevant type) are present. In this | 170 | //! - If `p_1 == _`, then we look at the list of constructors that appear in the first component of |
168 | //! case we must check whether the wildcard pattern covers any unmatched value. For | 171 | //! the rows of `P`: |
169 | //! that, we can think of the `_` pattern as a big OR-pattern that covers all | 172 | //! - If there are some constructors that aren't present, then we might think that the |
170 | //! possible constructors. For `Option`, that would mean `_ = None | Some(_)` for | 173 | //! wildcard `_` is useful, since it covers those constructors that weren't covered |
171 | //! example. The wildcard pattern is useful in this case if it is useful when | 174 | //! before. |
172 | //! specialized to one of the possible constructors. So we compute: | 175 | //! That's almost correct, but only works if there were no wildcards in those first |
173 | //! `U(P, p) := ∃(k ϵ constructors) U(S(k, P), S(k, p))` | 176 | //! components. So we need to check that `p` is useful with respect to the rows that |
177 | //! start with a wildcard, if there are any. This is where `D` comes in: | ||
178 | //! `U(P, p) := U(D(P), D(p))` | ||
174 | //! | 179 | //! |
175 | //! For example, if `P` is: | 180 | //! For example, if `P` is: |
176 | //! [ | 181 | //! ```text |
177 | //! [Some(true), _], | 182 | //! [ |
178 | //! [None, false], | 183 | //! [_, true, _], |
179 | //! ] | 184 | //! [None, false, 1], |
180 | //! and `p` is [_, false], both `None` and `Some` constructors appear in the first | 185 | //! ] |
181 | //! components of `P`. We will therefore try popping both constructors in turn: we | 186 | //! ``` |
182 | //! compute U([[true, _]], [_, false]) for the `Some` constructor, and U([[false]], | 187 | //! and `p` is `[_, false, _]`, the `Some` constructor doesn't appear in `P`. So if we |
183 | //! [false]) for the `None` constructor. The first case returns true, so we know that | 188 | //! only had row 2, we'd know that `p` is useful. However row 1 starts with a |
184 | //! `p` is useful for `P`. Indeed, it matches `[Some(false), _]` that wasn't matched | 189 | //! wildcard, so we need to check whether `U([[true, _]], [false, 1])`. |
185 | //! before. | ||
186 | //! | 190 | //! |
187 | //! - If `p_1 == r_1 | r_2`, then the usefulness depends on each `r_i` separately: | 191 | //! - Otherwise, all possible constructors (for the relevant type) are present. In this |
188 | //! `U(P, p) := U(P, (r_1, p_2, .., p_n)) | 192 | //! case we must check whether the wildcard pattern covers any unmatched value. For |
189 | //! || U(P, (r_2, p_2, .., p_n))` | 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 | //! ``` | ||
190 | use std::sync::Arc; | 219 | use std::sync::Arc; |
191 | 220 | ||
192 | use smallvec::{smallvec, SmallVec}; | 221 | use smallvec::{smallvec, SmallVec}; |
@@ -283,20 +312,16 @@ impl PatStack { | |||
283 | Self(v) | 312 | Self(v) |
284 | } | 313 | } |
285 | 314 | ||
286 | fn is_empty(&self) -> bool { | ||
287 | self.0.is_empty() | ||
288 | } | ||
289 | |||
290 | fn head(&self) -> PatIdOrWild { | ||
291 | self.0[0] | ||
292 | } | ||
293 | |||
294 | fn get_head(&self) -> Option<PatIdOrWild> { | 315 | fn get_head(&self) -> Option<PatIdOrWild> { |
295 | self.0.first().copied() | 316 | self.0.first().copied() |
296 | } | 317 | } |
297 | 318 | ||
319 | fn tail(&self) -> &[PatIdOrWild] { | ||
320 | self.0.get(1..).unwrap_or(&[]) | ||
321 | } | ||
322 | |||
298 | fn to_tail(&self) -> PatStack { | 323 | fn to_tail(&self) -> PatStack { |
299 | Self::from_slice(&self.0[1..]) | 324 | Self::from_slice(self.tail()) |
300 | } | 325 | } |
301 | 326 | ||
302 | fn replace_head_with<I, T>(&self, pats: I) -> PatStack | 327 | fn replace_head_with<I, T>(&self, pats: I) -> PatStack |
@@ -318,7 +343,7 @@ impl PatStack { | |||
318 | /// | 343 | /// |
319 | /// See the module docs and the associated documentation in rustc for details. | 344 | /// See the module docs and the associated documentation in rustc for details. |
320 | fn specialize_wildcard(&self, cx: &MatchCheckCtx) -> Option<PatStack> { | 345 | fn specialize_wildcard(&self, cx: &MatchCheckCtx) -> Option<PatStack> { |
321 | if matches!(self.head().as_pat(cx), Pat::Wild) { | 346 | if matches!(self.get_head()?.as_pat(cx), Pat::Wild) { |
322 | Some(self.to_tail()) | 347 | Some(self.to_tail()) |
323 | } else { | 348 | } else { |
324 | None | 349 | None |
@@ -333,7 +358,13 @@ impl PatStack { | |||
333 | cx: &MatchCheckCtx, | 358 | cx: &MatchCheckCtx, |
334 | constructor: &Constructor, | 359 | constructor: &Constructor, |
335 | ) -> MatchCheckResult<Option<PatStack>> { | 360 | ) -> MatchCheckResult<Option<PatStack>> { |
336 | let result = match (self.head().as_pat(cx), constructor) { | 361 | let head = match self.get_head() { |
362 | Some(head) => head, | ||
363 | None => return Ok(None), | ||
364 | }; | ||
365 | |||
366 | let head_pat = head.as_pat(cx); | ||
367 | let result = match (head_pat, constructor) { | ||
337 | (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => { | 368 | (Pat::Tuple { args: ref pat_ids, ellipsis }, Constructor::Tuple { arity: _ }) => { |
338 | if ellipsis.is_some() { | 369 | if ellipsis.is_some() { |
339 | // If there are ellipsis here, we should add the correct number of | 370 | // If there are ellipsis here, we should add the correct number of |
@@ -360,7 +391,7 @@ impl PatStack { | |||
360 | (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), | 391 | (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), |
361 | (Pat::Path(_), Constructor::Enum(constructor)) => { | 392 | (Pat::Path(_), Constructor::Enum(constructor)) => { |
362 | // unit enum variants become `Pat::Path` | 393 | // unit enum variants become `Pat::Path` |
363 | let pat_id = self.head().as_id().expect("we know this isn't a wild"); | 394 | let pat_id = head.as_id().expect("we know this isn't a wild"); |
364 | if !enum_variant_matches(cx, pat_id, *constructor) { | 395 | if !enum_variant_matches(cx, pat_id, *constructor) { |
365 | None | 396 | None |
366 | } else { | 397 | } else { |
@@ -371,7 +402,7 @@ impl PatStack { | |||
371 | Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, | 402 | Pat::TupleStruct { args: ref pat_ids, ellipsis, .. }, |
372 | Constructor::Enum(enum_constructor), | 403 | Constructor::Enum(enum_constructor), |
373 | ) => { | 404 | ) => { |
374 | let pat_id = self.head().as_id().expect("we know this isn't a wild"); | 405 | let pat_id = head.as_id().expect("we know this isn't a wild"); |
375 | if !enum_variant_matches(cx, pat_id, *enum_constructor) { | 406 | if !enum_variant_matches(cx, pat_id, *enum_constructor) { |
376 | None | 407 | None |
377 | } else { | 408 | } else { |
@@ -411,7 +442,7 @@ impl PatStack { | |||
411 | } | 442 | } |
412 | } | 443 | } |
413 | (Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => { | 444 | (Pat::Record { args: ref arg_patterns, .. }, Constructor::Enum(e)) => { |
414 | let pat_id = self.head().as_id().expect("we know this isn't a wild"); | 445 | let pat_id = head.as_id().expect("we know this isn't a wild"); |
415 | if !enum_variant_matches(cx, pat_id, *e) { | 446 | if !enum_variant_matches(cx, pat_id, *e) { |
416 | None | 447 | None |
417 | } else { | 448 | } else { |
@@ -457,7 +488,7 @@ impl PatStack { | |||
457 | ) -> MatchCheckResult<PatStack> { | 488 | ) -> MatchCheckResult<PatStack> { |
458 | assert_eq!( | 489 | assert_eq!( |
459 | Pat::Wild, | 490 | Pat::Wild, |
460 | self.head().as_pat(cx), | 491 | self.get_head().expect("expand_wildcard called on empty PatStack").as_pat(cx), |
461 | "expand_wildcard must only be called on PatStack with wild at head", | 492 | "expand_wildcard must only be called on PatStack with wild at head", |
462 | ); | 493 | ); |
463 | 494 | ||
@@ -475,7 +506,6 @@ impl PatStack { | |||
475 | } | 506 | } |
476 | } | 507 | } |
477 | 508 | ||
478 | #[derive(Debug)] | ||
479 | /// A collection of PatStack. | 509 | /// A collection of PatStack. |
480 | /// | 510 | /// |
481 | /// This type is modeled from the struct of the same name in `rustc`. | 511 | /// This type is modeled from the struct of the same name in `rustc`. |
@@ -502,7 +532,7 @@ impl Matrix { | |||
502 | } | 532 | } |
503 | 533 | ||
504 | fn heads(&self) -> Vec<PatIdOrWild> { | 534 | fn heads(&self) -> Vec<PatIdOrWild> { |
505 | self.0.iter().map(|p| p.head()).collect() | 535 | self.0.iter().flat_map(|p| p.get_head()).collect() |
506 | } | 536 | } |
507 | 537 | ||
508 | /// Computes `D(self)` for each contained PatStack. | 538 | /// Computes `D(self)` for each contained PatStack. |
@@ -589,13 +619,16 @@ pub(crate) fn is_useful( | |||
589 | _ => (), | 619 | _ => (), |
590 | } | 620 | } |
591 | 621 | ||
592 | if v.is_empty() { | 622 | let head = match v.get_head() { |
593 | let result = if matrix.is_empty() { Usefulness::Useful } else { Usefulness::NotUseful }; | 623 | Some(head) => head, |
624 | None => { | ||
625 | let result = if matrix.is_empty() { Usefulness::Useful } else { Usefulness::NotUseful }; | ||
594 | 626 | ||
595 | return Ok(result); | 627 | return Ok(result); |
596 | } | 628 | } |
629 | }; | ||
597 | 630 | ||
598 | if let Pat::Or(pat_ids) = v.head().as_pat(cx) { | 631 | if let Pat::Or(pat_ids) = head.as_pat(cx) { |
599 | let mut found_unimplemented = false; | 632 | let mut found_unimplemented = false; |
600 | let any_useful = pat_ids.iter().any(|&pat_id| { | 633 | let any_useful = pat_ids.iter().any(|&pat_id| { |
601 | let v = PatStack::from_pattern(pat_id); | 634 | let v = PatStack::from_pattern(pat_id); |
@@ -619,7 +652,7 @@ pub(crate) fn is_useful( | |||
619 | }; | 652 | }; |
620 | } | 653 | } |
621 | 654 | ||
622 | if let Some(constructor) = pat_constructor(cx, v.head())? { | 655 | if let Some(constructor) = pat_constructor(cx, head)? { |
623 | let matrix = matrix.specialize_constructor(&cx, &constructor)?; | 656 | let matrix = matrix.specialize_constructor(&cx, &constructor)?; |
624 | let v = v | 657 | let v = v |
625 | .specialize_constructor(&cx, &constructor)? | 658 | .specialize_constructor(&cx, &constructor)? |
@@ -808,194 +841,193 @@ mod tests { | |||
808 | 841 | ||
809 | pub(super) use crate::{diagnostics::MissingMatchArms, test_db::TestDB}; | 842 | pub(super) use crate::{diagnostics::MissingMatchArms, test_db::TestDB}; |
810 | 843 | ||
811 | pub(super) fn check_diagnostic_message(content: &str) -> String { | 844 | pub(super) fn check_diagnostic_message(ra_fixture: &str) -> String { |
812 | TestDB::with_single_file(content).0.diagnostic::<MissingMatchArms>().0 | 845 | TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>().0 |
813 | } | 846 | } |
814 | 847 | ||
815 | pub(super) fn check_diagnostic(content: &str) { | 848 | pub(super) fn check_diagnostic(ra_fixture: &str) { |
816 | let diagnostic_count = | 849 | let diagnostic_count = |
817 | TestDB::with_single_file(content).0.diagnostic::<MissingMatchArms>().1; | 850 | TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>().1; |
818 | 851 | ||
819 | assert_eq!(1, diagnostic_count, "no diagnostic reported"); | 852 | assert_eq!(1, diagnostic_count, "no diagnostic reported"); |
820 | } | 853 | } |
821 | 854 | ||
822 | pub(super) fn check_no_diagnostic(content: &str) { | 855 | pub(super) fn check_no_diagnostic(ra_fixture: &str) { |
823 | let diagnostic_count = | 856 | let (s, diagnostic_count) = |
824 | TestDB::with_single_file(content).0.diagnostic::<MissingMatchArms>().1; | 857 | TestDB::with_single_file(ra_fixture).0.diagnostic::<MissingMatchArms>(); |
825 | 858 | ||
826 | assert_eq!(0, diagnostic_count, "expected no diagnostic, found one"); | 859 | assert_eq!(0, diagnostic_count, "expected no diagnostic, found one: {}", s); |
827 | } | 860 | } |
828 | 861 | ||
829 | #[test] | 862 | #[test] |
830 | fn empty_tuple_no_arms_diagnostic_message() { | 863 | fn empty_tuple_no_arms_diagnostic_message() { |
831 | let content = r" | ||
832 | fn test_fn() { | ||
833 | match () { | ||
834 | } | ||
835 | } | ||
836 | "; | ||
837 | |||
838 | assert_snapshot!( | 864 | assert_snapshot!( |
839 | check_diagnostic_message(content), | 865 | check_diagnostic_message(r" |
866 | fn test_fn() { | ||
867 | match () { | ||
868 | } | ||
869 | } | ||
870 | "), | ||
840 | @"\"()\": Missing match arm\n" | 871 | @"\"()\": Missing match arm\n" |
841 | ); | 872 | ); |
842 | } | 873 | } |
843 | 874 | ||
844 | #[test] | 875 | #[test] |
845 | fn empty_tuple_no_arms() { | 876 | fn empty_tuple_no_arms() { |
846 | let content = r" | 877 | check_diagnostic( |
878 | r" | ||
847 | fn test_fn() { | 879 | fn test_fn() { |
848 | match () { | 880 | match () { |
849 | } | 881 | } |
850 | } | 882 | } |
851 | "; | 883 | ", |
852 | 884 | ); | |
853 | check_diagnostic(content); | ||
854 | } | 885 | } |
855 | 886 | ||
856 | #[test] | 887 | #[test] |
857 | fn empty_tuple_wild() { | 888 | fn empty_tuple_wild() { |
858 | let content = r" | 889 | check_no_diagnostic( |
890 | r" | ||
859 | fn test_fn() { | 891 | fn test_fn() { |
860 | match () { | 892 | match () { |
861 | _ => {} | 893 | _ => {} |
862 | } | 894 | } |
863 | } | 895 | } |
864 | "; | 896 | ", |
865 | 897 | ); | |
866 | check_no_diagnostic(content); | ||
867 | } | 898 | } |
868 | 899 | ||
869 | #[test] | 900 | #[test] |
870 | fn empty_tuple_no_diagnostic() { | 901 | fn empty_tuple_no_diagnostic() { |
871 | let content = r" | 902 | check_no_diagnostic( |
903 | r" | ||
872 | fn test_fn() { | 904 | fn test_fn() { |
873 | match () { | 905 | match () { |
874 | () => {} | 906 | () => {} |
875 | } | 907 | } |
876 | } | 908 | } |
877 | "; | 909 | ", |
878 | 910 | ); | |
879 | check_no_diagnostic(content); | ||
880 | } | 911 | } |
881 | 912 | ||
882 | #[test] | 913 | #[test] |
883 | fn tuple_of_empty_tuple_no_arms() { | 914 | fn tuple_of_empty_tuple_no_arms() { |
884 | let content = r" | 915 | check_diagnostic( |
916 | r" | ||
885 | fn test_fn() { | 917 | fn test_fn() { |
886 | match (()) { | 918 | match (()) { |
887 | } | 919 | } |
888 | } | 920 | } |
889 | "; | 921 | ", |
890 | 922 | ); | |
891 | check_diagnostic(content); | ||
892 | } | 923 | } |
893 | 924 | ||
894 | #[test] | 925 | #[test] |
895 | fn tuple_of_empty_tuple_no_diagnostic() { | 926 | fn tuple_of_empty_tuple_no_diagnostic() { |
896 | let content = r" | 927 | check_no_diagnostic( |
928 | r" | ||
897 | fn test_fn() { | 929 | fn test_fn() { |
898 | match (()) { | 930 | match (()) { |
899 | (()) => {} | 931 | (()) => {} |
900 | } | 932 | } |
901 | } | 933 | } |
902 | "; | 934 | ", |
903 | 935 | ); | |
904 | check_no_diagnostic(content); | ||
905 | } | 936 | } |
906 | 937 | ||
907 | #[test] | 938 | #[test] |
908 | fn tuple_of_two_empty_tuple_no_arms() { | 939 | fn tuple_of_two_empty_tuple_no_arms() { |
909 | let content = r" | 940 | check_diagnostic( |
941 | r" | ||
910 | fn test_fn() { | 942 | fn test_fn() { |
911 | match ((), ()) { | 943 | match ((), ()) { |
912 | } | 944 | } |
913 | } | 945 | } |
914 | "; | 946 | ", |
915 | 947 | ); | |
916 | check_diagnostic(content); | ||
917 | } | 948 | } |
918 | 949 | ||
919 | #[test] | 950 | #[test] |
920 | fn tuple_of_two_empty_tuple_no_diagnostic() { | 951 | fn tuple_of_two_empty_tuple_no_diagnostic() { |
921 | let content = r" | 952 | check_no_diagnostic( |
953 | r" | ||
922 | fn test_fn() { | 954 | fn test_fn() { |
923 | match ((), ()) { | 955 | match ((), ()) { |
924 | ((), ()) => {} | 956 | ((), ()) => {} |
925 | } | 957 | } |
926 | } | 958 | } |
927 | "; | 959 | ", |
928 | 960 | ); | |
929 | check_no_diagnostic(content); | ||
930 | } | 961 | } |
931 | 962 | ||
932 | #[test] | 963 | #[test] |
933 | fn bool_no_arms() { | 964 | fn bool_no_arms() { |
934 | let content = r" | 965 | check_diagnostic( |
966 | r" | ||
935 | fn test_fn() { | 967 | fn test_fn() { |
936 | match false { | 968 | match false { |
937 | } | 969 | } |
938 | } | 970 | } |
939 | "; | 971 | ", |
940 | 972 | ); | |
941 | check_diagnostic(content); | ||
942 | } | 973 | } |
943 | 974 | ||
944 | #[test] | 975 | #[test] |
945 | fn bool_missing_arm() { | 976 | fn bool_missing_arm() { |
946 | let content = r" | 977 | check_diagnostic( |
978 | r" | ||
947 | fn test_fn() { | 979 | fn test_fn() { |
948 | match false { | 980 | match false { |
949 | true => {} | 981 | true => {} |
950 | } | 982 | } |
951 | } | 983 | } |
952 | "; | 984 | ", |
953 | 985 | ); | |
954 | check_diagnostic(content); | ||
955 | } | 986 | } |
956 | 987 | ||
957 | #[test] | 988 | #[test] |
958 | fn bool_no_diagnostic() { | 989 | fn bool_no_diagnostic() { |
959 | let content = r" | 990 | check_no_diagnostic( |
991 | r" | ||
960 | fn test_fn() { | 992 | fn test_fn() { |
961 | match false { | 993 | match false { |
962 | true => {} | 994 | true => {} |
963 | false => {} | 995 | false => {} |
964 | } | 996 | } |
965 | } | 997 | } |
966 | "; | 998 | ", |
967 | 999 | ); | |
968 | check_no_diagnostic(content); | ||
969 | } | 1000 | } |
970 | 1001 | ||
971 | #[test] | 1002 | #[test] |
972 | fn tuple_of_bools_no_arms() { | 1003 | fn tuple_of_bools_no_arms() { |
973 | let content = r" | 1004 | check_diagnostic( |
1005 | r" | ||
974 | fn test_fn() { | 1006 | fn test_fn() { |
975 | match (false, true) { | 1007 | match (false, true) { |
976 | } | 1008 | } |
977 | } | 1009 | } |
978 | "; | 1010 | ", |
979 | 1011 | ); | |
980 | check_diagnostic(content); | ||
981 | } | 1012 | } |
982 | 1013 | ||
983 | #[test] | 1014 | #[test] |
984 | fn tuple_of_bools_missing_arms() { | 1015 | fn tuple_of_bools_missing_arms() { |
985 | let content = r" | 1016 | check_diagnostic( |
1017 | r" | ||
986 | fn test_fn() { | 1018 | fn test_fn() { |
987 | match (false, true) { | 1019 | match (false, true) { |
988 | (true, true) => {}, | 1020 | (true, true) => {}, |
989 | } | 1021 | } |
990 | } | 1022 | } |
991 | "; | 1023 | ", |
992 | 1024 | ); | |
993 | check_diagnostic(content); | ||
994 | } | 1025 | } |
995 | 1026 | ||
996 | #[test] | 1027 | #[test] |
997 | fn tuple_of_bools_missing_arm() { | 1028 | fn tuple_of_bools_missing_arm() { |
998 | let content = r" | 1029 | check_diagnostic( |
1030 | r" | ||
999 | fn test_fn() { | 1031 | fn test_fn() { |
1000 | match (false, true) { | 1032 | match (false, true) { |
1001 | (false, true) => {}, | 1033 | (false, true) => {}, |
@@ -1003,14 +1035,14 @@ mod tests { | |||
1003 | (true, false) => {}, | 1035 | (true, false) => {}, |
1004 | } | 1036 | } |
1005 | } | 1037 | } |
1006 | "; | 1038 | ", |
1007 | 1039 | ); | |
1008 | check_diagnostic(content); | ||
1009 | } | 1040 | } |
1010 | 1041 | ||
1011 | #[test] | 1042 | #[test] |
1012 | fn tuple_of_bools_with_wilds() { | 1043 | fn tuple_of_bools_with_wilds() { |
1013 | let content = r" | 1044 | check_no_diagnostic( |
1045 | r" | ||
1014 | fn test_fn() { | 1046 | fn test_fn() { |
1015 | match (false, true) { | 1047 | match (false, true) { |
1016 | (false, _) => {}, | 1048 | (false, _) => {}, |
@@ -1018,14 +1050,14 @@ mod tests { | |||
1018 | (_, true) => {}, | 1050 | (_, true) => {}, |
1019 | } | 1051 | } |
1020 | } | 1052 | } |
1021 | "; | 1053 | ", |
1022 | 1054 | ); | |
1023 | check_no_diagnostic(content); | ||
1024 | } | 1055 | } |
1025 | 1056 | ||
1026 | #[test] | 1057 | #[test] |
1027 | fn tuple_of_bools_no_diagnostic() { | 1058 | fn tuple_of_bools_no_diagnostic() { |
1028 | let content = r" | 1059 | check_no_diagnostic( |
1060 | r" | ||
1029 | fn test_fn() { | 1061 | fn test_fn() { |
1030 | match (false, true) { | 1062 | match (false, true) { |
1031 | (true, true) => {}, | 1063 | (true, true) => {}, |
@@ -1034,27 +1066,27 @@ mod tests { | |||
1034 | (false, false) => {}, | 1066 | (false, false) => {}, |
1035 | } | 1067 | } |
1036 | } | 1068 | } |
1037 | "; | 1069 | ", |
1038 | 1070 | ); | |
1039 | check_no_diagnostic(content); | ||
1040 | } | 1071 | } |
1041 | 1072 | ||
1042 | #[test] | 1073 | #[test] |
1043 | fn tuple_of_bools_binding_missing_arms() { | 1074 | fn tuple_of_bools_binding_missing_arms() { |
1044 | let content = r" | 1075 | check_diagnostic( |
1076 | r" | ||
1045 | fn test_fn() { | 1077 | fn test_fn() { |
1046 | match (false, true) { | 1078 | match (false, true) { |
1047 | (true, _x) => {}, | 1079 | (true, _x) => {}, |
1048 | } | 1080 | } |
1049 | } | 1081 | } |
1050 | "; | 1082 | ", |
1051 | 1083 | ); | |
1052 | check_diagnostic(content); | ||
1053 | } | 1084 | } |
1054 | 1085 | ||
1055 | #[test] | 1086 | #[test] |
1056 | fn tuple_of_bools_binding_no_diagnostic() { | 1087 | fn tuple_of_bools_binding_no_diagnostic() { |
1057 | let content = r" | 1088 | check_no_diagnostic( |
1089 | r" | ||
1058 | fn test_fn() { | 1090 | fn test_fn() { |
1059 | match (false, true) { | 1091 | match (false, true) { |
1060 | (true, _x) => {}, | 1092 | (true, _x) => {}, |
@@ -1062,80 +1094,80 @@ mod tests { | |||
1062 | (false, false) => {}, | 1094 | (false, false) => {}, |
1063 | } | 1095 | } |
1064 | } | 1096 | } |
1065 | "; | 1097 | ", |
1066 | 1098 | ); | |
1067 | check_no_diagnostic(content); | ||
1068 | } | 1099 | } |
1069 | 1100 | ||
1070 | #[test] | 1101 | #[test] |
1071 | fn tuple_of_bools_with_ellipsis_at_end_no_diagnostic() { | 1102 | fn tuple_of_bools_with_ellipsis_at_end_no_diagnostic() { |
1072 | let content = r" | 1103 | check_no_diagnostic( |
1104 | r" | ||
1073 | fn test_fn() { | 1105 | fn test_fn() { |
1074 | match (false, true, false) { | 1106 | match (false, true, false) { |
1075 | (false, ..) => {}, | 1107 | (false, ..) => {}, |
1076 | (true, ..) => {}, | 1108 | (true, ..) => {}, |
1077 | } | 1109 | } |
1078 | } | 1110 | } |
1079 | "; | 1111 | ", |
1080 | 1112 | ); | |
1081 | check_no_diagnostic(content); | ||
1082 | } | 1113 | } |
1083 | 1114 | ||
1084 | #[test] | 1115 | #[test] |
1085 | fn tuple_of_bools_with_ellipsis_at_beginning_no_diagnostic() { | 1116 | fn tuple_of_bools_with_ellipsis_at_beginning_no_diagnostic() { |
1086 | let content = r" | 1117 | check_no_diagnostic( |
1118 | r" | ||
1087 | fn test_fn() { | 1119 | fn test_fn() { |
1088 | match (false, true, false) { | 1120 | match (false, true, false) { |
1089 | (.., false) => {}, | 1121 | (.., false) => {}, |
1090 | (.., true) => {}, | 1122 | (.., true) => {}, |
1091 | } | 1123 | } |
1092 | } | 1124 | } |
1093 | "; | 1125 | ", |
1094 | 1126 | ); | |
1095 | check_no_diagnostic(content); | ||
1096 | } | 1127 | } |
1097 | 1128 | ||
1098 | #[test] | 1129 | #[test] |
1099 | fn tuple_of_bools_with_ellipsis_no_diagnostic() { | 1130 | fn tuple_of_bools_with_ellipsis_no_diagnostic() { |
1100 | let content = r" | 1131 | check_no_diagnostic( |
1132 | r" | ||
1101 | fn test_fn() { | 1133 | fn test_fn() { |
1102 | match (false, true, false) { | 1134 | match (false, true, false) { |
1103 | (..) => {}, | 1135 | (..) => {}, |
1104 | } | 1136 | } |
1105 | } | 1137 | } |
1106 | "; | 1138 | ", |
1107 | 1139 | ); | |
1108 | check_no_diagnostic(content); | ||
1109 | } | 1140 | } |
1110 | 1141 | ||
1111 | #[test] | 1142 | #[test] |
1112 | fn tuple_of_tuple_and_bools_no_arms() { | 1143 | fn tuple_of_tuple_and_bools_no_arms() { |
1113 | let content = r" | 1144 | check_diagnostic( |
1145 | r" | ||
1114 | fn test_fn() { | 1146 | fn test_fn() { |
1115 | match (false, ((), false)) { | 1147 | match (false, ((), false)) { |
1116 | } | 1148 | } |
1117 | } | 1149 | } |
1118 | "; | 1150 | ", |
1119 | 1151 | ); | |
1120 | check_diagnostic(content); | ||
1121 | } | 1152 | } |
1122 | 1153 | ||
1123 | #[test] | 1154 | #[test] |
1124 | fn tuple_of_tuple_and_bools_missing_arms() { | 1155 | fn tuple_of_tuple_and_bools_missing_arms() { |
1125 | let content = r" | 1156 | check_diagnostic( |
1157 | r" | ||
1126 | fn test_fn() { | 1158 | fn test_fn() { |
1127 | match (false, ((), false)) { | 1159 | match (false, ((), false)) { |
1128 | (true, ((), true)) => {}, | 1160 | (true, ((), true)) => {}, |
1129 | } | 1161 | } |
1130 | } | 1162 | } |
1131 | "; | 1163 | ", |
1132 | 1164 | ); | |
1133 | check_diagnostic(content); | ||
1134 | } | 1165 | } |
1135 | 1166 | ||
1136 | #[test] | 1167 | #[test] |
1137 | fn tuple_of_tuple_and_bools_no_diagnostic() { | 1168 | fn tuple_of_tuple_and_bools_no_diagnostic() { |
1138 | let content = r" | 1169 | check_no_diagnostic( |
1170 | r" | ||
1139 | fn test_fn() { | 1171 | fn test_fn() { |
1140 | match (false, ((), false)) { | 1172 | match (false, ((), false)) { |
1141 | (true, ((), true)) => {}, | 1173 | (true, ((), true)) => {}, |
@@ -1144,27 +1176,27 @@ mod tests { | |||
1144 | (false, ((), false)) => {}, | 1176 | (false, ((), false)) => {}, |
1145 | } | 1177 | } |
1146 | } | 1178 | } |
1147 | "; | 1179 | ", |
1148 | 1180 | ); | |
1149 | check_no_diagnostic(content); | ||
1150 | } | 1181 | } |
1151 | 1182 | ||
1152 | #[test] | 1183 | #[test] |
1153 | fn tuple_of_tuple_and_bools_wildcard_missing_arms() { | 1184 | fn tuple_of_tuple_and_bools_wildcard_missing_arms() { |
1154 | let content = r" | 1185 | check_diagnostic( |
1186 | r" | ||
1155 | fn test_fn() { | 1187 | fn test_fn() { |
1156 | match (false, ((), false)) { | 1188 | match (false, ((), false)) { |
1157 | (true, _) => {}, | 1189 | (true, _) => {}, |
1158 | } | 1190 | } |
1159 | } | 1191 | } |
1160 | "; | 1192 | ", |
1161 | 1193 | ); | |
1162 | check_diagnostic(content); | ||
1163 | } | 1194 | } |
1164 | 1195 | ||
1165 | #[test] | 1196 | #[test] |
1166 | fn tuple_of_tuple_and_bools_wildcard_no_diagnostic() { | 1197 | fn tuple_of_tuple_and_bools_wildcard_no_diagnostic() { |
1167 | let content = r" | 1198 | check_no_diagnostic( |
1199 | r" | ||
1168 | fn test_fn() { | 1200 | fn test_fn() { |
1169 | match (false, ((), false)) { | 1201 | match (false, ((), false)) { |
1170 | (true, ((), true)) => {}, | 1202 | (true, ((), true)) => {}, |
@@ -1172,14 +1204,14 @@ mod tests { | |||
1172 | (false, _) => {}, | 1204 | (false, _) => {}, |
1173 | } | 1205 | } |
1174 | } | 1206 | } |
1175 | "; | 1207 | ", |
1176 | 1208 | ); | |
1177 | check_no_diagnostic(content); | ||
1178 | } | 1209 | } |
1179 | 1210 | ||
1180 | #[test] | 1211 | #[test] |
1181 | fn enum_no_arms() { | 1212 | fn enum_no_arms() { |
1182 | let content = r" | 1213 | check_diagnostic( |
1214 | r" | ||
1183 | enum Either { | 1215 | enum Either { |
1184 | A, | 1216 | A, |
1185 | B, | 1217 | B, |
@@ -1188,14 +1220,14 @@ mod tests { | |||
1188 | match Either::A { | 1220 | match Either::A { |
1189 | } | 1221 | } |
1190 | } | 1222 | } |
1191 | "; | 1223 | ", |
1192 | 1224 | ); | |
1193 | check_diagnostic(content); | ||
1194 | } | 1225 | } |
1195 | 1226 | ||
1196 | #[test] | 1227 | #[test] |
1197 | fn enum_missing_arms() { | 1228 | fn enum_missing_arms() { |
1198 | let content = r" | 1229 | check_diagnostic( |
1230 | r" | ||
1199 | enum Either { | 1231 | enum Either { |
1200 | A, | 1232 | A, |
1201 | B, | 1233 | B, |
@@ -1205,14 +1237,14 @@ mod tests { | |||
1205 | Either::A => {}, | 1237 | Either::A => {}, |
1206 | } | 1238 | } |
1207 | } | 1239 | } |
1208 | "; | 1240 | ", |
1209 | 1241 | ); | |
1210 | check_diagnostic(content); | ||
1211 | } | 1242 | } |
1212 | 1243 | ||
1213 | #[test] | 1244 | #[test] |
1214 | fn enum_no_diagnostic() { | 1245 | fn enum_no_diagnostic() { |
1215 | let content = r" | 1246 | check_no_diagnostic( |
1247 | r" | ||
1216 | enum Either { | 1248 | enum Either { |
1217 | A, | 1249 | A, |
1218 | B, | 1250 | B, |
@@ -1223,14 +1255,14 @@ mod tests { | |||
1223 | Either::B => {}, | 1255 | Either::B => {}, |
1224 | } | 1256 | } |
1225 | } | 1257 | } |
1226 | "; | 1258 | ", |
1227 | 1259 | ); | |
1228 | check_no_diagnostic(content); | ||
1229 | } | 1260 | } |
1230 | 1261 | ||
1231 | #[test] | 1262 | #[test] |
1232 | fn enum_ref_missing_arms() { | 1263 | fn enum_ref_missing_arms() { |
1233 | let content = r" | 1264 | check_diagnostic( |
1265 | r" | ||
1234 | enum Either { | 1266 | enum Either { |
1235 | A, | 1267 | A, |
1236 | B, | 1268 | B, |
@@ -1240,14 +1272,14 @@ mod tests { | |||
1240 | Either::A => {}, | 1272 | Either::A => {}, |
1241 | } | 1273 | } |
1242 | } | 1274 | } |
1243 | "; | 1275 | ", |
1244 | 1276 | ); | |
1245 | check_diagnostic(content); | ||
1246 | } | 1277 | } |
1247 | 1278 | ||
1248 | #[test] | 1279 | #[test] |
1249 | fn enum_ref_no_diagnostic() { | 1280 | fn enum_ref_no_diagnostic() { |
1250 | let content = r" | 1281 | check_no_diagnostic( |
1282 | r" | ||
1251 | enum Either { | 1283 | enum Either { |
1252 | A, | 1284 | A, |
1253 | B, | 1285 | B, |
@@ -1258,14 +1290,14 @@ mod tests { | |||
1258 | Either::B => {}, | 1290 | Either::B => {}, |
1259 | } | 1291 | } |
1260 | } | 1292 | } |
1261 | "; | 1293 | ", |
1262 | 1294 | ); | |
1263 | check_no_diagnostic(content); | ||
1264 | } | 1295 | } |
1265 | 1296 | ||
1266 | #[test] | 1297 | #[test] |
1267 | fn enum_containing_bool_no_arms() { | 1298 | fn enum_containing_bool_no_arms() { |
1268 | let content = r" | 1299 | check_diagnostic( |
1300 | r" | ||
1269 | enum Either { | 1301 | enum Either { |
1270 | A(bool), | 1302 | A(bool), |
1271 | B, | 1303 | B, |
@@ -1274,14 +1306,14 @@ mod tests { | |||
1274 | match Either::B { | 1306 | match Either::B { |
1275 | } | 1307 | } |
1276 | } | 1308 | } |
1277 | "; | 1309 | ", |
1278 | 1310 | ); | |
1279 | check_diagnostic(content); | ||
1280 | } | 1311 | } |
1281 | 1312 | ||
1282 | #[test] | 1313 | #[test] |
1283 | fn enum_containing_bool_missing_arms() { | 1314 | fn enum_containing_bool_missing_arms() { |
1284 | let content = r" | 1315 | check_diagnostic( |
1316 | r" | ||
1285 | enum Either { | 1317 | enum Either { |
1286 | A(bool), | 1318 | A(bool), |
1287 | B, | 1319 | B, |
@@ -1292,14 +1324,14 @@ mod tests { | |||
1292 | Either::B => (), | 1324 | Either::B => (), |
1293 | } | 1325 | } |
1294 | } | 1326 | } |
1295 | "; | 1327 | ", |
1296 | 1328 | ); | |
1297 | check_diagnostic(content); | ||
1298 | } | 1329 | } |
1299 | 1330 | ||
1300 | #[test] | 1331 | #[test] |
1301 | fn enum_containing_bool_no_diagnostic() { | 1332 | fn enum_containing_bool_no_diagnostic() { |
1302 | let content = r" | 1333 | check_no_diagnostic( |
1334 | r" | ||
1303 | enum Either { | 1335 | enum Either { |
1304 | A(bool), | 1336 | A(bool), |
1305 | B, | 1337 | B, |
@@ -1311,14 +1343,14 @@ mod tests { | |||
1311 | Either::B => (), | 1343 | Either::B => (), |
1312 | } | 1344 | } |
1313 | } | 1345 | } |
1314 | "; | 1346 | ", |
1315 | 1347 | ); | |
1316 | check_no_diagnostic(content); | ||
1317 | } | 1348 | } |
1318 | 1349 | ||
1319 | #[test] | 1350 | #[test] |
1320 | fn enum_containing_bool_with_wild_no_diagnostic() { | 1351 | fn enum_containing_bool_with_wild_no_diagnostic() { |
1321 | let content = r" | 1352 | check_no_diagnostic( |
1353 | r" | ||
1322 | enum Either { | 1354 | enum Either { |
1323 | A(bool), | 1355 | A(bool), |
1324 | B, | 1356 | B, |
@@ -1329,14 +1361,14 @@ mod tests { | |||
1329 | _ => (), | 1361 | _ => (), |
1330 | } | 1362 | } |
1331 | } | 1363 | } |
1332 | "; | 1364 | ", |
1333 | 1365 | ); | |
1334 | check_no_diagnostic(content); | ||
1335 | } | 1366 | } |
1336 | 1367 | ||
1337 | #[test] | 1368 | #[test] |
1338 | fn enum_containing_bool_with_wild_2_no_diagnostic() { | 1369 | fn enum_containing_bool_with_wild_2_no_diagnostic() { |
1339 | let content = r" | 1370 | check_no_diagnostic( |
1371 | r" | ||
1340 | enum Either { | 1372 | enum Either { |
1341 | A(bool), | 1373 | A(bool), |
1342 | B, | 1374 | B, |
@@ -1347,14 +1379,14 @@ mod tests { | |||
1347 | Either::B => (), | 1379 | Either::B => (), |
1348 | } | 1380 | } |
1349 | } | 1381 | } |
1350 | "; | 1382 | ", |
1351 | 1383 | ); | |
1352 | check_no_diagnostic(content); | ||
1353 | } | 1384 | } |
1354 | 1385 | ||
1355 | #[test] | 1386 | #[test] |
1356 | fn enum_different_sizes_missing_arms() { | 1387 | fn enum_different_sizes_missing_arms() { |
1357 | let content = r" | 1388 | check_diagnostic( |
1389 | r" | ||
1358 | enum Either { | 1390 | enum Either { |
1359 | A(bool), | 1391 | A(bool), |
1360 | B(bool, bool), | 1392 | B(bool, bool), |
@@ -1365,14 +1397,14 @@ mod tests { | |||
1365 | Either::B(false, _) => (), | 1397 | Either::B(false, _) => (), |
1366 | } | 1398 | } |
1367 | } | 1399 | } |
1368 | "; | 1400 | ", |
1369 | 1401 | ); | |
1370 | check_diagnostic(content); | ||
1371 | } | 1402 | } |
1372 | 1403 | ||
1373 | #[test] | 1404 | #[test] |
1374 | fn enum_different_sizes_no_diagnostic() { | 1405 | fn enum_different_sizes_no_diagnostic() { |
1375 | let content = r" | 1406 | check_no_diagnostic( |
1407 | r" | ||
1376 | enum Either { | 1408 | enum Either { |
1377 | A(bool), | 1409 | A(bool), |
1378 | B(bool, bool), | 1410 | B(bool, bool), |
@@ -1384,14 +1416,14 @@ mod tests { | |||
1384 | Either::B(false, _) => (), | 1416 | Either::B(false, _) => (), |
1385 | } | 1417 | } |
1386 | } | 1418 | } |
1387 | "; | 1419 | ", |
1388 | 1420 | ); | |
1389 | check_no_diagnostic(content); | ||
1390 | } | 1421 | } |
1391 | 1422 | ||
1392 | #[test] | 1423 | #[test] |
1393 | fn or_no_diagnostic() { | 1424 | fn or_no_diagnostic() { |
1394 | let content = r" | 1425 | check_no_diagnostic( |
1426 | r" | ||
1395 | enum Either { | 1427 | enum Either { |
1396 | A(bool), | 1428 | A(bool), |
1397 | B(bool, bool), | 1429 | B(bool, bool), |
@@ -1403,14 +1435,14 @@ mod tests { | |||
1403 | Either::B(false, _) => (), | 1435 | Either::B(false, _) => (), |
1404 | } | 1436 | } |
1405 | } | 1437 | } |
1406 | "; | 1438 | ", |
1407 | 1439 | ); | |
1408 | check_no_diagnostic(content); | ||
1409 | } | 1440 | } |
1410 | 1441 | ||
1411 | #[test] | 1442 | #[test] |
1412 | fn tuple_of_enum_no_diagnostic() { | 1443 | fn tuple_of_enum_no_diagnostic() { |
1413 | let content = r" | 1444 | check_no_diagnostic( |
1445 | r" | ||
1414 | enum Either { | 1446 | enum Either { |
1415 | A(bool), | 1447 | A(bool), |
1416 | B(bool, bool), | 1448 | B(bool, bool), |
@@ -1427,14 +1459,16 @@ mod tests { | |||
1427 | (Either::B(_, _), Either2::D) => (), | 1459 | (Either::B(_, _), Either2::D) => (), |
1428 | } | 1460 | } |
1429 | } | 1461 | } |
1430 | "; | 1462 | ", |
1431 | 1463 | ); | |
1432 | check_no_diagnostic(content); | ||
1433 | } | 1464 | } |
1434 | 1465 | ||
1435 | #[test] | 1466 | #[test] |
1436 | fn mismatched_types() { | 1467 | fn mismatched_types() { |
1437 | let content = r" | 1468 | // Match statements with arms that don't match the |
1469 | // expression pattern do not fire this diagnostic. | ||
1470 | check_no_diagnostic( | ||
1471 | r" | ||
1438 | enum Either { | 1472 | enum Either { |
1439 | A, | 1473 | A, |
1440 | B, | 1474 | B, |
@@ -1449,47 +1483,47 @@ mod tests { | |||
1449 | Either2::D => (), | 1483 | Either2::D => (), |
1450 | } | 1484 | } |
1451 | } | 1485 | } |
1452 | "; | 1486 | ", |
1453 | 1487 | ); | |
1454 | // Match statements with arms that don't match the | ||
1455 | // expression pattern do not fire this diagnostic. | ||
1456 | check_no_diagnostic(content); | ||
1457 | } | 1488 | } |
1458 | 1489 | ||
1459 | #[test] | 1490 | #[test] |
1460 | fn mismatched_types_with_different_arity() { | 1491 | fn mismatched_types_with_different_arity() { |
1461 | let content = r" | 1492 | // Match statements with arms that don't match the |
1493 | // expression pattern do not fire this diagnostic. | ||
1494 | check_no_diagnostic( | ||
1495 | r" | ||
1462 | fn test_fn() { | 1496 | fn test_fn() { |
1463 | match (true, false) { | 1497 | match (true, false) { |
1464 | (true, false, true) => (), | 1498 | (true, false, true) => (), |
1465 | (true) => (), | 1499 | (true) => (), |
1466 | } | 1500 | } |
1467 | } | 1501 | } |
1468 | "; | 1502 | ", |
1469 | 1503 | ); | |
1470 | // Match statements with arms that don't match the | ||
1471 | // expression pattern do not fire this diagnostic. | ||
1472 | check_no_diagnostic(content); | ||
1473 | } | 1504 | } |
1474 | 1505 | ||
1475 | #[test] | 1506 | #[test] |
1476 | fn malformed_match_arm_tuple_missing_pattern() { | 1507 | fn malformed_match_arm_tuple_missing_pattern() { |
1477 | let content = r" | 1508 | // Match statements with arms that don't match the |
1509 | // expression pattern do not fire this diagnostic. | ||
1510 | check_no_diagnostic( | ||
1511 | r" | ||
1478 | fn test_fn() { | 1512 | fn test_fn() { |
1479 | match (0) { | 1513 | match (0) { |
1480 | () => (), | 1514 | () => (), |
1481 | } | 1515 | } |
1482 | } | 1516 | } |
1483 | "; | 1517 | ", |
1484 | 1518 | ); | |
1485 | // Match statements with arms that don't match the | ||
1486 | // expression pattern do not fire this diagnostic. | ||
1487 | check_no_diagnostic(content); | ||
1488 | } | 1519 | } |
1489 | 1520 | ||
1490 | #[test] | 1521 | #[test] |
1491 | fn malformed_match_arm_tuple_enum_missing_pattern() { | 1522 | fn malformed_match_arm_tuple_enum_missing_pattern() { |
1492 | let content = r" | 1523 | // We are testing to be sure we don't panic here when the match |
1524 | // arm `Either::B` is missing its pattern. | ||
1525 | check_no_diagnostic( | ||
1526 | r" | ||
1493 | enum Either { | 1527 | enum Either { |
1494 | A, | 1528 | A, |
1495 | B(u32), | 1529 | B(u32), |
@@ -1500,32 +1534,30 @@ mod tests { | |||
1500 | Either::B() => (), | 1534 | Either::B() => (), |
1501 | } | 1535 | } |
1502 | } | 1536 | } |
1503 | "; | 1537 | ", |
1504 | 1538 | ); | |
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 | } | 1539 | } |
1509 | 1540 | ||
1510 | #[test] | 1541 | #[test] |
1511 | fn enum_not_in_scope() { | 1542 | fn enum_not_in_scope() { |
1512 | let content = r" | 1543 | // The enum is not in scope so we don't perform exhaustiveness |
1544 | // checking, but we want to be sure we don't panic here (and | ||
1545 | // we don't create a diagnostic). | ||
1546 | check_no_diagnostic( | ||
1547 | r" | ||
1513 | fn test_fn() { | 1548 | fn test_fn() { |
1514 | match Foo::Bar { | 1549 | match Foo::Bar { |
1515 | Foo::Baz => (), | 1550 | Foo::Baz => (), |
1516 | } | 1551 | } |
1517 | } | 1552 | } |
1518 | "; | 1553 | ", |
1519 | 1554 | ); | |
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 | } | 1555 | } |
1525 | 1556 | ||
1526 | #[test] | 1557 | #[test] |
1527 | fn expr_diverges() { | 1558 | fn expr_diverges() { |
1528 | let content = r" | 1559 | check_no_diagnostic( |
1560 | r" | ||
1529 | enum Either { | 1561 | enum Either { |
1530 | A, | 1562 | A, |
1531 | B, | 1563 | B, |
@@ -1536,14 +1568,14 @@ mod tests { | |||
1536 | Either::B => (), | 1568 | Either::B => (), |
1537 | } | 1569 | } |
1538 | } | 1570 | } |
1539 | "; | 1571 | ", |
1540 | 1572 | ); | |
1541 | check_no_diagnostic(content); | ||
1542 | } | 1573 | } |
1543 | 1574 | ||
1544 | #[test] | 1575 | #[test] |
1545 | fn expr_loop_with_break() { | 1576 | fn expr_loop_with_break() { |
1546 | let content = r" | 1577 | check_no_diagnostic( |
1578 | r" | ||
1547 | enum Either { | 1579 | enum Either { |
1548 | A, | 1580 | A, |
1549 | B, | 1581 | B, |
@@ -1554,14 +1586,14 @@ mod tests { | |||
1554 | Either::B => (), | 1586 | Either::B => (), |
1555 | } | 1587 | } |
1556 | } | 1588 | } |
1557 | "; | 1589 | ", |
1558 | 1590 | ); | |
1559 | check_no_diagnostic(content); | ||
1560 | } | 1591 | } |
1561 | 1592 | ||
1562 | #[test] | 1593 | #[test] |
1563 | fn expr_partially_diverges() { | 1594 | fn expr_partially_diverges() { |
1564 | let content = r" | 1595 | check_no_diagnostic( |
1596 | r" | ||
1565 | enum Either<T> { | 1597 | enum Either<T> { |
1566 | A(T), | 1598 | A(T), |
1567 | B, | 1599 | B, |
@@ -1575,14 +1607,14 @@ mod tests { | |||
1575 | Either::B => 0, | 1607 | Either::B => 0, |
1576 | } | 1608 | } |
1577 | } | 1609 | } |
1578 | "; | 1610 | ", |
1579 | 1611 | ); | |
1580 | check_no_diagnostic(content); | ||
1581 | } | 1612 | } |
1582 | 1613 | ||
1583 | #[test] | 1614 | #[test] |
1584 | fn enum_record_no_arms() { | 1615 | fn enum_record_no_arms() { |
1585 | let content = r" | 1616 | check_diagnostic( |
1617 | r" | ||
1586 | enum Either { | 1618 | enum Either { |
1587 | A { foo: bool }, | 1619 | A { foo: bool }, |
1588 | B, | 1620 | B, |
@@ -1592,14 +1624,14 @@ mod tests { | |||
1592 | match a { | 1624 | match a { |
1593 | } | 1625 | } |
1594 | } | 1626 | } |
1595 | "; | 1627 | ", |
1596 | 1628 | ); | |
1597 | check_diagnostic(content); | ||
1598 | } | 1629 | } |
1599 | 1630 | ||
1600 | #[test] | 1631 | #[test] |
1601 | fn enum_record_missing_arms() { | 1632 | fn enum_record_missing_arms() { |
1602 | let content = r" | 1633 | check_diagnostic( |
1634 | r" | ||
1603 | enum Either { | 1635 | enum Either { |
1604 | A { foo: bool }, | 1636 | A { foo: bool }, |
1605 | B, | 1637 | B, |
@@ -1610,14 +1642,14 @@ mod tests { | |||
1610 | Either::A { foo: true } => (), | 1642 | Either::A { foo: true } => (), |
1611 | } | 1643 | } |
1612 | } | 1644 | } |
1613 | "; | 1645 | ", |
1614 | 1646 | ); | |
1615 | check_diagnostic(content); | ||
1616 | } | 1647 | } |
1617 | 1648 | ||
1618 | #[test] | 1649 | #[test] |
1619 | fn enum_record_no_diagnostic() { | 1650 | fn enum_record_no_diagnostic() { |
1620 | let content = r" | 1651 | check_no_diagnostic( |
1652 | r" | ||
1621 | enum Either { | 1653 | enum Either { |
1622 | A { foo: bool }, | 1654 | A { foo: bool }, |
1623 | B, | 1655 | B, |
@@ -1630,14 +1662,17 @@ mod tests { | |||
1630 | Either::B => (), | 1662 | Either::B => (), |
1631 | } | 1663 | } |
1632 | } | 1664 | } |
1633 | "; | 1665 | ", |
1634 | 1666 | ); | |
1635 | check_no_diagnostic(content); | ||
1636 | } | 1667 | } |
1637 | 1668 | ||
1638 | #[test] | 1669 | #[test] |
1639 | fn enum_record_missing_field_no_diagnostic() { | 1670 | fn enum_record_missing_field_no_diagnostic() { |
1640 | let content = r" | 1671 | // When `Either::A` is missing a struct member, we don't want |
1672 | // to fire the missing match arm diagnostic. This should fire | ||
1673 | // some other diagnostic. | ||
1674 | check_no_diagnostic( | ||
1675 | r" | ||
1641 | enum Either { | 1676 | enum Either { |
1642 | A { foo: bool }, | 1677 | A { foo: bool }, |
1643 | B, | 1678 | B, |
@@ -1649,17 +1684,16 @@ mod tests { | |||
1649 | Either::B => (), | 1684 | Either::B => (), |
1650 | } | 1685 | } |
1651 | } | 1686 | } |
1652 | "; | 1687 | ", |
1653 | 1688 | ); | |
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 | } | 1689 | } |
1659 | 1690 | ||
1660 | #[test] | 1691 | #[test] |
1661 | fn enum_record_missing_field_missing_match_arm() { | 1692 | fn enum_record_missing_field_missing_match_arm() { |
1662 | let content = r" | 1693 | // Even though `Either::A` is missing fields, we still want to fire |
1694 | // the missing arm diagnostic here, since we know `Either::B` is missing. | ||
1695 | check_diagnostic( | ||
1696 | r" | ||
1663 | enum Either { | 1697 | enum Either { |
1664 | A { foo: bool }, | 1698 | A { foo: bool }, |
1665 | B, | 1699 | B, |
@@ -1670,16 +1704,14 @@ mod tests { | |||
1670 | Either::A { } => (), | 1704 | Either::A { } => (), |
1671 | } | 1705 | } |
1672 | } | 1706 | } |
1673 | "; | 1707 | ", |
1674 | 1708 | ); | |
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 | } | 1709 | } |
1679 | 1710 | ||
1680 | #[test] | 1711 | #[test] |
1681 | fn enum_record_no_diagnostic_wild() { | 1712 | fn enum_record_no_diagnostic_wild() { |
1682 | let content = r" | 1713 | check_no_diagnostic( |
1714 | r" | ||
1683 | enum Either { | 1715 | enum Either { |
1684 | A { foo: bool }, | 1716 | A { foo: bool }, |
1685 | B, | 1717 | B, |
@@ -1691,14 +1723,14 @@ mod tests { | |||
1691 | Either::B => (), | 1723 | Either::B => (), |
1692 | } | 1724 | } |
1693 | } | 1725 | } |
1694 | "; | 1726 | ", |
1695 | 1727 | ); | |
1696 | check_no_diagnostic(content); | ||
1697 | } | 1728 | } |
1698 | 1729 | ||
1699 | #[test] | 1730 | #[test] |
1700 | fn enum_record_fields_out_of_order_missing_arm() { | 1731 | fn enum_record_fields_out_of_order_missing_arm() { |
1701 | let content = r" | 1732 | check_diagnostic( |
1733 | r" | ||
1702 | enum Either { | 1734 | enum Either { |
1703 | A { foo: bool, bar: () }, | 1735 | A { foo: bool, bar: () }, |
1704 | B, | 1736 | B, |
@@ -1710,14 +1742,14 @@ mod tests { | |||
1710 | Either::A { foo: true, bar: () } => (), | 1742 | Either::A { foo: true, bar: () } => (), |
1711 | } | 1743 | } |
1712 | } | 1744 | } |
1713 | "; | 1745 | ", |
1714 | 1746 | ); | |
1715 | check_diagnostic(content); | ||
1716 | } | 1747 | } |
1717 | 1748 | ||
1718 | #[test] | 1749 | #[test] |
1719 | fn enum_record_fields_out_of_order_no_diagnostic() { | 1750 | fn enum_record_fields_out_of_order_no_diagnostic() { |
1720 | let content = r" | 1751 | check_no_diagnostic( |
1752 | r" | ||
1721 | enum Either { | 1753 | enum Either { |
1722 | A { foo: bool, bar: () }, | 1754 | A { foo: bool, bar: () }, |
1723 | B, | 1755 | B, |
@@ -1730,89 +1762,89 @@ mod tests { | |||
1730 | Either::B => (), | 1762 | Either::B => (), |
1731 | } | 1763 | } |
1732 | } | 1764 | } |
1733 | "; | 1765 | ", |
1734 | 1766 | ); | |
1735 | check_no_diagnostic(content); | ||
1736 | } | 1767 | } |
1737 | 1768 | ||
1738 | #[test] | 1769 | #[test] |
1739 | fn enum_record_ellipsis_missing_arm() { | 1770 | fn enum_record_ellipsis_missing_arm() { |
1740 | let content = r" | 1771 | check_diagnostic( |
1741 | enum Either { | 1772 | r" |
1742 | A { foo: bool, bar: bool }, | 1773 | enum Either { |
1743 | B, | 1774 | A { foo: bool, bar: bool }, |
1744 | } | 1775 | B, |
1745 | fn test_fn() { | 1776 | } |
1746 | match Either::B { | 1777 | fn test_fn() { |
1747 | Either::A { foo: true, .. } => (), | 1778 | match Either::B { |
1748 | Either::B => (), | 1779 | Either::A { foo: true, .. } => (), |
1749 | } | 1780 | Either::B => (), |
1750 | } | 1781 | } |
1751 | "; | 1782 | } |
1752 | 1783 | ", | |
1753 | check_diagnostic(content); | 1784 | ); |
1754 | } | 1785 | } |
1755 | 1786 | ||
1756 | #[test] | 1787 | #[test] |
1757 | fn enum_record_ellipsis_no_diagnostic() { | 1788 | fn enum_record_ellipsis_no_diagnostic() { |
1758 | let content = r" | 1789 | check_no_diagnostic( |
1759 | enum Either { | 1790 | r" |
1760 | A { foo: bool, bar: bool }, | 1791 | enum Either { |
1761 | B, | 1792 | A { foo: bool, bar: bool }, |
1762 | } | 1793 | B, |
1763 | fn test_fn() { | 1794 | } |
1764 | let a = Either::A { foo: true }; | 1795 | fn test_fn() { |
1765 | match a { | 1796 | let a = Either::A { foo: true }; |
1766 | Either::A { foo: true, .. } => (), | 1797 | match a { |
1767 | Either::A { foo: false, .. } => (), | 1798 | Either::A { foo: true, .. } => (), |
1768 | Either::B => (), | 1799 | Either::A { foo: false, .. } => (), |
1769 | } | 1800 | Either::B => (), |
1770 | } | 1801 | } |
1771 | "; | 1802 | } |
1772 | 1803 | ", | |
1773 | check_no_diagnostic(content); | 1804 | ); |
1774 | } | 1805 | } |
1775 | 1806 | ||
1776 | #[test] | 1807 | #[test] |
1777 | fn enum_record_ellipsis_all_fields_missing_arm() { | 1808 | fn enum_record_ellipsis_all_fields_missing_arm() { |
1778 | let content = r" | 1809 | check_diagnostic( |
1779 | enum Either { | 1810 | r" |
1780 | A { foo: bool, bar: bool }, | 1811 | enum Either { |
1781 | B, | 1812 | A { foo: bool, bar: bool }, |
1782 | } | 1813 | B, |
1783 | fn test_fn() { | 1814 | } |
1784 | let a = Either::B; | 1815 | fn test_fn() { |
1785 | match a { | 1816 | let a = Either::B; |
1786 | Either::A { .. } => (), | 1817 | match a { |
1787 | } | 1818 | Either::A { .. } => (), |
1788 | } | 1819 | } |
1789 | "; | 1820 | } |
1790 | 1821 | ", | |
1791 | check_diagnostic(content); | 1822 | ); |
1792 | } | 1823 | } |
1793 | 1824 | ||
1794 | #[test] | 1825 | #[test] |
1795 | fn enum_record_ellipsis_all_fields_no_diagnostic() { | 1826 | fn enum_record_ellipsis_all_fields_no_diagnostic() { |
1796 | let content = r" | 1827 | check_no_diagnostic( |
1797 | enum Either { | 1828 | r" |
1798 | A { foo: bool, bar: bool }, | 1829 | enum Either { |
1799 | B, | 1830 | A { foo: bool, bar: bool }, |
1800 | } | 1831 | B, |
1801 | fn test_fn() { | 1832 | } |
1802 | let a = Either::B; | 1833 | fn test_fn() { |
1803 | match a { | 1834 | let a = Either::B; |
1804 | Either::A { .. } => (), | 1835 | match a { |
1805 | Either::B => (), | 1836 | Either::A { .. } => (), |
1806 | } | 1837 | Either::B => (), |
1807 | } | 1838 | } |
1808 | "; | 1839 | } |
1809 | 1840 | ", | |
1810 | check_no_diagnostic(content); | 1841 | ); |
1811 | } | 1842 | } |
1812 | 1843 | ||
1813 | #[test] | 1844 | #[test] |
1814 | fn enum_tuple_partial_ellipsis_no_diagnostic() { | 1845 | fn enum_tuple_partial_ellipsis_no_diagnostic() { |
1815 | let content = r" | 1846 | check_no_diagnostic( |
1847 | r" | ||
1816 | enum Either { | 1848 | enum Either { |
1817 | A(bool, bool, bool, bool), | 1849 | A(bool, bool, bool, bool), |
1818 | B, | 1850 | B, |
@@ -1826,14 +1858,14 @@ mod tests { | |||
1826 | Either::B => {}, | 1858 | Either::B => {}, |
1827 | } | 1859 | } |
1828 | } | 1860 | } |
1829 | "; | 1861 | ", |
1830 | 1862 | ); | |
1831 | check_no_diagnostic(content); | ||
1832 | } | 1863 | } |
1833 | 1864 | ||
1834 | #[test] | 1865 | #[test] |
1835 | fn enum_tuple_partial_ellipsis_2_no_diagnostic() { | 1866 | fn enum_tuple_partial_ellipsis_2_no_diagnostic() { |
1836 | let content = r" | 1867 | check_no_diagnostic( |
1868 | r" | ||
1837 | enum Either { | 1869 | enum Either { |
1838 | A(bool, bool, bool, bool), | 1870 | A(bool, bool, bool, bool), |
1839 | B, | 1871 | B, |
@@ -1847,14 +1879,14 @@ mod tests { | |||
1847 | Either::B => {}, | 1879 | Either::B => {}, |
1848 | } | 1880 | } |
1849 | } | 1881 | } |
1850 | "; | 1882 | ", |
1851 | 1883 | ); | |
1852 | check_no_diagnostic(content); | ||
1853 | } | 1884 | } |
1854 | 1885 | ||
1855 | #[test] | 1886 | #[test] |
1856 | fn enum_tuple_partial_ellipsis_missing_arm() { | 1887 | fn enum_tuple_partial_ellipsis_missing_arm() { |
1857 | let content = r" | 1888 | check_diagnostic( |
1889 | r" | ||
1858 | enum Either { | 1890 | enum Either { |
1859 | A(bool, bool, bool, bool), | 1891 | A(bool, bool, bool, bool), |
1860 | B, | 1892 | B, |
@@ -1867,14 +1899,14 @@ mod tests { | |||
1867 | Either::B => {}, | 1899 | Either::B => {}, |
1868 | } | 1900 | } |
1869 | } | 1901 | } |
1870 | "; | 1902 | ", |
1871 | 1903 | ); | |
1872 | check_diagnostic(content); | ||
1873 | } | 1904 | } |
1874 | 1905 | ||
1875 | #[test] | 1906 | #[test] |
1876 | fn enum_tuple_partial_ellipsis_2_missing_arm() { | 1907 | fn enum_tuple_partial_ellipsis_2_missing_arm() { |
1877 | let content = r" | 1908 | check_diagnostic( |
1909 | r" | ||
1878 | enum Either { | 1910 | enum Either { |
1879 | A(bool, bool, bool, bool), | 1911 | A(bool, bool, bool, bool), |
1880 | B, | 1912 | B, |
@@ -1887,14 +1919,14 @@ mod tests { | |||
1887 | Either::B => {}, | 1919 | Either::B => {}, |
1888 | } | 1920 | } |
1889 | } | 1921 | } |
1890 | "; | 1922 | ", |
1891 | 1923 | ); | |
1892 | check_diagnostic(content); | ||
1893 | } | 1924 | } |
1894 | 1925 | ||
1895 | #[test] | 1926 | #[test] |
1896 | fn enum_tuple_ellipsis_no_diagnostic() { | 1927 | fn enum_tuple_ellipsis_no_diagnostic() { |
1897 | let content = r" | 1928 | check_no_diagnostic( |
1929 | r" | ||
1898 | enum Either { | 1930 | enum Either { |
1899 | A(bool, bool, bool, bool), | 1931 | A(bool, bool, bool, bool), |
1900 | B, | 1932 | B, |
@@ -1905,51 +1937,51 @@ mod tests { | |||
1905 | Either::B => {}, | 1937 | Either::B => {}, |
1906 | } | 1938 | } |
1907 | } | 1939 | } |
1908 | "; | 1940 | ", |
1909 | 1941 | ); | |
1910 | check_no_diagnostic(content); | ||
1911 | } | 1942 | } |
1912 | 1943 | ||
1913 | #[test] | 1944 | #[test] |
1914 | fn enum_never() { | 1945 | fn enum_never() { |
1915 | let content = r" | 1946 | check_no_diagnostic( |
1947 | r" | ||
1916 | enum Never {} | 1948 | enum Never {} |
1917 | 1949 | ||
1918 | fn test_fn(never: Never) { | 1950 | fn test_fn(never: Never) { |
1919 | match never {} | 1951 | match never {} |
1920 | } | 1952 | } |
1921 | "; | 1953 | ", |
1922 | 1954 | ); | |
1923 | check_no_diagnostic(content); | ||
1924 | } | 1955 | } |
1925 | 1956 | ||
1926 | #[test] | 1957 | #[test] |
1927 | fn type_never() { | 1958 | fn type_never() { |
1928 | let content = r" | 1959 | check_no_diagnostic( |
1960 | r" | ||
1929 | fn test_fn(never: !) { | 1961 | fn test_fn(never: !) { |
1930 | match never {} | 1962 | match never {} |
1931 | } | 1963 | } |
1932 | "; | 1964 | ", |
1933 | 1965 | ); | |
1934 | check_no_diagnostic(content); | ||
1935 | } | 1966 | } |
1936 | 1967 | ||
1937 | #[test] | 1968 | #[test] |
1938 | fn enum_never_ref() { | 1969 | fn enum_never_ref() { |
1939 | let content = r" | 1970 | check_no_diagnostic( |
1971 | r" | ||
1940 | enum Never {} | 1972 | enum Never {} |
1941 | 1973 | ||
1942 | fn test_fn(never: &Never) { | 1974 | fn test_fn(never: &Never) { |
1943 | match never {} | 1975 | match never {} |
1944 | } | 1976 | } |
1945 | "; | 1977 | ", |
1946 | 1978 | ); | |
1947 | check_no_diagnostic(content); | ||
1948 | } | 1979 | } |
1949 | 1980 | ||
1950 | #[test] | 1981 | #[test] |
1951 | fn expr_diverges_missing_arm() { | 1982 | fn expr_diverges_missing_arm() { |
1952 | let content = r" | 1983 | check_no_diagnostic( |
1984 | r" | ||
1953 | enum Either { | 1985 | enum Either { |
1954 | A, | 1986 | A, |
1955 | B, | 1987 | B, |
@@ -1959,9 +1991,49 @@ mod tests { | |||
1959 | Either::A => (), | 1991 | Either::A => (), |
1960 | } | 1992 | } |
1961 | } | 1993 | } |
1962 | "; | 1994 | ", |
1995 | ); | ||
1996 | } | ||
1997 | |||
1998 | #[test] | ||
1999 | fn or_pattern_panic() { | ||
2000 | check_no_diagnostic( | ||
2001 | r" | ||
2002 | pub enum Category { | ||
2003 | Infinity, | ||
2004 | Zero, | ||
2005 | } | ||
2006 | |||
2007 | fn panic(a: Category, b: Category) { | ||
2008 | match (a, b) { | ||
2009 | (Category::Zero | Category::Infinity, _) => {} | ||
2010 | (_, Category::Zero | Category::Infinity) => {} | ||
2011 | } | ||
2012 | } | ||
2013 | ", | ||
2014 | ); | ||
2015 | } | ||
1963 | 2016 | ||
1964 | check_no_diagnostic(content); | 2017 | #[test] |
2018 | fn or_pattern_panic_2() { | ||
2019 | // FIXME: This is a false positive, but the code used to cause a panic in the match checker, | ||
2020 | // so this acts as a regression test for that. | ||
2021 | check_diagnostic( | ||
2022 | r" | ||
2023 | pub enum Category { | ||
2024 | Infinity, | ||
2025 | Zero, | ||
2026 | } | ||
2027 | |||
2028 | fn panic(a: Category, b: Category) { | ||
2029 | match (a, b) { | ||
2030 | (Category::Infinity, Category::Infinity) | (Category::Zero, Category::Zero) => {} | ||
2031 | |||
2032 | (Category::Infinity | Category::Zero, _) => {} | ||
2033 | } | ||
2034 | } | ||
2035 | ", | ||
2036 | ); | ||
1965 | } | 2037 | } |
1966 | } | 2038 | } |
1967 | 2039 | ||
@@ -1981,23 +2053,26 @@ mod false_negatives { | |||
1981 | 2053 | ||
1982 | #[test] | 2054 | #[test] |
1983 | fn integers() { | 2055 | fn integers() { |
1984 | let content = r" | 2056 | // This is a false negative. |
2057 | // We don't currently check integer exhaustiveness. | ||
2058 | check_no_diagnostic( | ||
2059 | r" | ||
1985 | fn test_fn() { | 2060 | fn test_fn() { |
1986 | match 5 { | 2061 | match 5 { |
1987 | 10 => (), | 2062 | 10 => (), |
1988 | 11..20 => (), | 2063 | 11..20 => (), |
1989 | } | 2064 | } |
1990 | } | 2065 | } |
1991 | "; | 2066 | ", |
1992 | 2067 | ); | |
1993 | // This is a false negative. | ||
1994 | // We don't currently check integer exhaustiveness. | ||
1995 | check_no_diagnostic(content); | ||
1996 | } | 2068 | } |
1997 | 2069 | ||
1998 | #[test] | 2070 | #[test] |
1999 | fn internal_or() { | 2071 | fn internal_or() { |
2000 | let content = r" | 2072 | // This is a false negative. |
2073 | // We do not currently handle patterns with internal `or`s. | ||
2074 | check_no_diagnostic( | ||
2075 | r" | ||
2001 | fn test_fn() { | 2076 | fn test_fn() { |
2002 | enum Either { | 2077 | enum Either { |
2003 | A(bool), | 2078 | A(bool), |
@@ -2007,16 +2082,18 @@ mod false_negatives { | |||
2007 | Either::A(true | false) => (), | 2082 | Either::A(true | false) => (), |
2008 | } | 2083 | } |
2009 | } | 2084 | } |
2010 | "; | 2085 | ", |
2011 | 2086 | ); | |
2012 | // This is a false negative. | ||
2013 | // We do not currently handle patterns with internal `or`s. | ||
2014 | check_no_diagnostic(content); | ||
2015 | } | 2087 | } |
2016 | 2088 | ||
2017 | #[test] | 2089 | #[test] |
2018 | fn expr_loop_missing_arm() { | 2090 | fn expr_loop_missing_arm() { |
2019 | let content = r" | 2091 | // This is a false negative. |
2092 | // We currently infer the type of `loop { break Foo::A }` to `!`, which | ||
2093 | // causes us to skip the diagnostic since `Either::A` doesn't type check | ||
2094 | // with `!`. | ||
2095 | check_diagnostic( | ||
2096 | r" | ||
2020 | enum Either { | 2097 | enum Either { |
2021 | A, | 2098 | A, |
2022 | B, | 2099 | B, |
@@ -2026,48 +2103,46 @@ mod false_negatives { | |||
2026 | Either::A => (), | 2103 | Either::A => (), |
2027 | } | 2104 | } |
2028 | } | 2105 | } |
2029 | "; | 2106 | ", |
2030 | 2107 | ); | |
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 | } | 2108 | } |
2037 | 2109 | ||
2038 | #[test] | 2110 | #[test] |
2039 | fn tuple_of_bools_with_ellipsis_at_end_missing_arm() { | 2111 | fn tuple_of_bools_with_ellipsis_at_end_missing_arm() { |
2040 | let content = r" | 2112 | // This is a false negative. |
2113 | // We don't currently handle tuple patterns with ellipsis. | ||
2114 | check_no_diagnostic( | ||
2115 | r" | ||
2041 | fn test_fn() { | 2116 | fn test_fn() { |
2042 | match (false, true, false) { | 2117 | match (false, true, false) { |
2043 | (false, ..) => {}, | 2118 | (false, ..) => {}, |
2044 | } | 2119 | } |
2045 | } | 2120 | } |
2046 | "; | 2121 | ", |
2047 | 2122 | ); | |
2048 | // This is a false negative. | ||
2049 | // We don't currently handle tuple patterns with ellipsis. | ||
2050 | check_no_diagnostic(content); | ||
2051 | } | 2123 | } |
2052 | 2124 | ||
2053 | #[test] | 2125 | #[test] |
2054 | fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() { | 2126 | fn tuple_of_bools_with_ellipsis_at_beginning_missing_arm() { |
2055 | let content = r" | 2127 | // This is a false negative. |
2128 | // We don't currently handle tuple patterns with ellipsis. | ||
2129 | check_no_diagnostic( | ||
2130 | r" | ||
2056 | fn test_fn() { | 2131 | fn test_fn() { |
2057 | match (false, true, false) { | 2132 | match (false, true, false) { |
2058 | (.., false) => {}, | 2133 | (.., false) => {}, |
2059 | } | 2134 | } |
2060 | } | 2135 | } |
2061 | "; | 2136 | ", |
2062 | 2137 | ); | |
2063 | // This is a false negative. | ||
2064 | // We don't currently handle tuple patterns with ellipsis. | ||
2065 | check_no_diagnostic(content); | ||
2066 | } | 2138 | } |
2067 | 2139 | ||
2068 | #[test] | 2140 | #[test] |
2069 | fn struct_missing_arm() { | 2141 | fn struct_missing_arm() { |
2070 | let content = r" | 2142 | // This is a false negative. |
2143 | // We don't currently handle structs. | ||
2144 | check_no_diagnostic( | ||
2145 | r" | ||
2071 | struct Foo { | 2146 | struct Foo { |
2072 | a: bool, | 2147 | a: bool, |
2073 | } | 2148 | } |
@@ -2076,10 +2151,7 @@ mod false_negatives { | |||
2076 | Foo { a: true } => {}, | 2151 | Foo { a: true } => {}, |
2077 | } | 2152 | } |
2078 | } | 2153 | } |
2079 | "; | 2154 | ", |
2080 | 2155 | ); | |
2081 | // This is a false negative. | ||
2082 | // We don't currently handle structs. | ||
2083 | check_no_diagnostic(content); | ||
2084 | } | 2156 | } |
2085 | } | 2157 | } |
diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index 2b9372b4b..f22232324 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs | |||
@@ -73,6 +73,7 @@ pub use lower::{ | |||
73 | pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; | 73 | pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; |
74 | 74 | ||
75 | pub use chalk_ir::{BoundVar, DebruijnIndex}; | 75 | pub use chalk_ir::{BoundVar, DebruijnIndex}; |
76 | use itertools::Itertools; | ||
76 | 77 | ||
77 | /// A type constructor or type name: this might be something like the primitive | 78 | /// A type constructor or type name: this might be something like the primitive |
78 | /// type `bool`, a struct like `Vec`, or things like function pointers or | 79 | /// type `bool`, a struct like `Vec`, or things like function pointers or |
@@ -815,6 +816,11 @@ impl Ty { | |||
815 | } | 816 | } |
816 | } | 817 | } |
817 | 818 | ||
819 | /// If this is a `dyn Trait`, returns that trait. | ||
820 | pub fn dyn_trait(&self) -> Option<TraitId> { | ||
821 | self.dyn_trait_ref().map(|it| it.trait_) | ||
822 | } | ||
823 | |||
818 | fn builtin_deref(&self) -> Option<Ty> { | 824 | fn builtin_deref(&self) -> Option<Ty> { |
819 | match self { | 825 | match self { |
820 | Ty::Apply(a_ty) => match a_ty.ctor { | 826 | Ty::Apply(a_ty) => match a_ty.ctor { |
@@ -867,13 +873,56 @@ impl Ty { | |||
867 | } | 873 | } |
868 | } | 874 | } |
869 | 875 | ||
870 | /// If this is a `dyn Trait`, returns that trait. | 876 | pub fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option<Vec<GenericPredicate>> { |
871 | pub fn dyn_trait(&self) -> Option<TraitId> { | ||
872 | match self { | 877 | match self { |
873 | Ty::Dyn(predicates) => predicates.iter().find_map(|pred| match pred { | 878 | Ty::Opaque(opaque_ty) => { |
874 | GenericPredicate::Implemented(tr) => Some(tr.trait_), | 879 | let predicates = match opaque_ty.opaque_ty_id { |
875 | _ => None, | 880 | OpaqueTyId::ReturnTypeImplTrait(func, idx) => { |
876 | }), | 881 | db.return_type_impl_traits(func).map(|it| { |
882 | let data = (*it) | ||
883 | .as_ref() | ||
884 | .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); | ||
885 | data.clone().subst(&opaque_ty.parameters) | ||
886 | }) | ||
887 | } | ||
888 | }; | ||
889 | |||
890 | predicates.map(|it| it.value) | ||
891 | } | ||
892 | Ty::Placeholder(id) => { | ||
893 | let generic_params = db.generic_params(id.parent); | ||
894 | let param_data = &generic_params.types[id.local_id]; | ||
895 | match param_data.provenance { | ||
896 | hir_def::generics::TypeParamProvenance::ArgumentImplTrait => { | ||
897 | let predicates = db | ||
898 | .generic_predicates_for_param(*id) | ||
899 | .into_iter() | ||
900 | .map(|pred| pred.value.clone()) | ||
901 | .collect_vec(); | ||
902 | |||
903 | Some(predicates) | ||
904 | } | ||
905 | _ => None, | ||
906 | } | ||
907 | } | ||
908 | _ => None, | ||
909 | } | ||
910 | } | ||
911 | |||
912 | pub fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<TraitId> { | ||
913 | match self { | ||
914 | Ty::Apply(ApplicationTy { ctor: TypeCtor::AssociatedType(type_alias_id), .. }) => { | ||
915 | match type_alias_id.lookup(db.upcast()).container { | ||
916 | AssocContainerId::TraitId(trait_id) => Some(trait_id), | ||
917 | _ => None, | ||
918 | } | ||
919 | } | ||
920 | Ty::Projection(projection_ty) => { | ||
921 | match projection_ty.associated_ty.lookup(db.upcast()).container { | ||
922 | AssocContainerId::TraitId(trait_id) => Some(trait_id), | ||
923 | _ => None, | ||
924 | } | ||
925 | } | ||
877 | _ => None, | 926 | _ => None, |
878 | } | 927 | } |
879 | } | 928 | } |
@@ -1057,5 +1106,5 @@ pub struct ReturnTypeImplTraits { | |||
1057 | 1106 | ||
1058 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] | 1107 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] |
1059 | pub(crate) struct ReturnTypeImplTrait { | 1108 | pub(crate) struct ReturnTypeImplTrait { |
1060 | pub(crate) bounds: Binders<Vec<GenericPredicate>>, | 1109 | pub bounds: Binders<Vec<GenericPredicate>>, |
1061 | } | 1110 | } |
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index a721e23c6..e1fcf379d 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -15,6 +15,7 @@ mod complete_unqualified_path; | |||
15 | mod complete_postfix; | 15 | mod complete_postfix; |
16 | mod complete_macro_in_item_position; | 16 | mod complete_macro_in_item_position; |
17 | mod complete_trait_impl; | 17 | mod complete_trait_impl; |
18 | mod patterns; | ||
18 | #[cfg(test)] | 19 | #[cfg(test)] |
19 | mod test_utils; | 20 | mod test_utils; |
20 | 21 | ||
diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs index fd95bc410..b2f621a11 100644 --- a/crates/ra_ide/src/completion/complete_keyword.rs +++ b/crates/ra_ide/src/completion/complete_keyword.rs | |||
@@ -1,11 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | ||
3 | use ra_syntax::{ | 3 | use ra_syntax::ast; |
4 | ast::{self, LoopBodyOwner}, | ||
5 | match_ast, AstNode, | ||
6 | SyntaxKind::*, | ||
7 | SyntaxToken, | ||
8 | }; | ||
9 | 4 | ||
10 | use crate::completion::{ | 5 | use crate::completion::{ |
11 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, | 6 | CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, |
@@ -41,68 +36,122 @@ pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionC | |||
41 | } | 36 | } |
42 | } | 37 | } |
43 | 38 | ||
44 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { | 39 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { |
45 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) | 40 | let has_trait_or_impl_parent = ctx.has_impl_parent || ctx.has_trait_parent; |
46 | .kind(CompletionItemKind::Keyword); | 41 | if ctx.trait_as_prev_sibling || ctx.impl_as_prev_sibling { |
47 | 42 | add_keyword(ctx, acc, "where", "where "); | |
48 | match ctx.config.snippet_cap { | 43 | return; |
49 | Some(cap) => res.insert_snippet(cap, snippet), | ||
50 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), | ||
51 | } | 44 | } |
52 | .build() | 45 | if ctx.unsafe_is_prev { |
53 | } | 46 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { |
47 | add_keyword(ctx, acc, "fn", "fn $0() {}") | ||
48 | } | ||
49 | |||
50 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
51 | || ctx.block_expr_parent | ||
52 | { | ||
53 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
54 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
55 | } | ||
54 | 56 | ||
55 | pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { | ||
56 | if !ctx.is_trivial_path { | ||
57 | return; | 57 | return; |
58 | } | 58 | } |
59 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { | ||
60 | add_keyword(ctx, acc, "fn", "fn $0() {}"); | ||
61 | } | ||
62 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
63 | || ctx.block_expr_parent | ||
64 | { | ||
65 | add_keyword(ctx, acc, "use", "use "); | ||
66 | add_keyword(ctx, acc, "impl", "impl $0 {}"); | ||
67 | add_keyword(ctx, acc, "trait", "trait $0 {}"); | ||
68 | } | ||
59 | 69 | ||
60 | let fn_def = match &ctx.function_syntax { | 70 | if ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent { |
61 | Some(it) => it, | 71 | add_keyword(ctx, acc, "enum", "enum $0 {}"); |
62 | None => return, | 72 | add_keyword(ctx, acc, "struct", "struct $0 {}"); |
63 | }; | 73 | add_keyword(ctx, acc, "union", "union $0 {}"); |
64 | acc.add(keyword(ctx, "if", "if $0 {}")); | 74 | } |
65 | acc.add(keyword(ctx, "match", "match $0 {}")); | ||
66 | acc.add(keyword(ctx, "while", "while $0 {}")); | ||
67 | acc.add(keyword(ctx, "loop", "loop {$0}")); | ||
68 | 75 | ||
76 | if ctx.block_expr_parent || ctx.is_match_arm { | ||
77 | add_keyword(ctx, acc, "match", "match $0 {}"); | ||
78 | add_keyword(ctx, acc, "loop", "loop {$0}"); | ||
79 | } | ||
80 | if ctx.block_expr_parent { | ||
81 | add_keyword(ctx, acc, "while", "while $0 {}"); | ||
82 | } | ||
83 | if ctx.if_is_prev || ctx.block_expr_parent { | ||
84 | add_keyword(ctx, acc, "let", "let "); | ||
85 | } | ||
86 | if ctx.if_is_prev || ctx.block_expr_parent || ctx.is_match_arm { | ||
87 | add_keyword(ctx, acc, "if", "if "); | ||
88 | add_keyword(ctx, acc, "if let", "if let "); | ||
89 | } | ||
69 | if ctx.after_if { | 90 | if ctx.after_if { |
70 | acc.add(keyword(ctx, "else", "else {$0}")); | 91 | add_keyword(ctx, acc, "else", "else {$0}"); |
71 | acc.add(keyword(ctx, "else if", "else if $0 {}")); | 92 | add_keyword(ctx, acc, "else if", "else if $0 {}"); |
93 | } | ||
94 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
95 | || ctx.block_expr_parent | ||
96 | { | ||
97 | add_keyword(ctx, acc, "mod", "mod $0 {}"); | ||
98 | } | ||
99 | if ctx.bind_pat_parent || ctx.ref_pat_parent { | ||
100 | add_keyword(ctx, acc, "mut", "mut "); | ||
72 | } | 101 | } |
73 | if is_in_loop_body(&ctx.token) { | 102 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent { |
103 | add_keyword(ctx, acc, "const", "const "); | ||
104 | add_keyword(ctx, acc, "type", "type "); | ||
105 | } | ||
106 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
107 | || ctx.block_expr_parent | ||
108 | { | ||
109 | add_keyword(ctx, acc, "static", "static "); | ||
110 | }; | ||
111 | if (ctx.has_item_list_or_source_file_parent && !has_trait_or_impl_parent) | ||
112 | || ctx.block_expr_parent | ||
113 | { | ||
114 | add_keyword(ctx, acc, "extern", "extern "); | ||
115 | } | ||
116 | if ctx.has_item_list_or_source_file_parent || ctx.block_expr_parent || ctx.is_match_arm { | ||
117 | add_keyword(ctx, acc, "unsafe", "unsafe "); | ||
118 | } | ||
119 | if ctx.in_loop_body { | ||
74 | if ctx.can_be_stmt { | 120 | if ctx.can_be_stmt { |
75 | acc.add(keyword(ctx, "continue", "continue;")); | 121 | add_keyword(ctx, acc, "continue", "continue;"); |
76 | acc.add(keyword(ctx, "break", "break;")); | 122 | add_keyword(ctx, acc, "break", "break;"); |
77 | } else { | 123 | } else { |
78 | acc.add(keyword(ctx, "continue", "continue")); | 124 | add_keyword(ctx, acc, "continue", "continue"); |
79 | acc.add(keyword(ctx, "break", "break")); | 125 | add_keyword(ctx, acc, "break", "break"); |
80 | } | 126 | } |
81 | } | 127 | } |
128 | if ctx.has_item_list_or_source_file_parent && !ctx.has_trait_parent { | ||
129 | add_keyword(ctx, acc, "pub", "pub ") | ||
130 | } | ||
131 | |||
132 | if !ctx.is_trivial_path { | ||
133 | return; | ||
134 | } | ||
135 | let fn_def = match &ctx.function_syntax { | ||
136 | Some(it) => it, | ||
137 | None => return, | ||
138 | }; | ||
82 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); | 139 | acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); |
83 | } | 140 | } |
84 | 141 | ||
85 | fn is_in_loop_body(leaf: &SyntaxToken) -> bool { | 142 | fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { |
86 | // FIXME move this to CompletionContext and make it handle macros | 143 | let res = CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) |
87 | for node in leaf.parent().ancestors() { | 144 | .kind(CompletionItemKind::Keyword); |
88 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | 145 | |
89 | break; | 146 | match ctx.config.snippet_cap { |
90 | } | 147 | Some(cap) => res.insert_snippet(cap, snippet), |
91 | let loop_body = match_ast! { | 148 | _ => res.insert_text(if snippet.contains('$') { kw } else { snippet }), |
92 | match node { | ||
93 | ast::ForExpr(it) => it.loop_body(), | ||
94 | ast::WhileExpr(it) => it.loop_body(), | ||
95 | ast::LoopExpr(it) => it.loop_body(), | ||
96 | _ => None, | ||
97 | } | ||
98 | }; | ||
99 | if let Some(body) = loop_body { | ||
100 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
101 | return true; | ||
102 | } | ||
103 | } | ||
104 | } | 149 | } |
105 | false | 150 | .build() |
151 | } | ||
152 | |||
153 | fn add_keyword(ctx: &CompletionContext, acc: &mut Completions, kw: &str, snippet: &str) { | ||
154 | acc.add(keyword(ctx, kw, snippet)); | ||
106 | } | 155 | } |
107 | 156 | ||
108 | fn complete_return( | 157 | fn complete_return( |
@@ -121,327 +170,156 @@ fn complete_return( | |||
121 | 170 | ||
122 | #[cfg(test)] | 171 | #[cfg(test)] |
123 | mod tests { | 172 | mod tests { |
124 | use crate::completion::{test_utils::do_completion, CompletionItem, CompletionKind}; | 173 | use crate::completion::{test_utils::completion_list, CompletionKind}; |
125 | use insta::assert_debug_snapshot; | 174 | use insta::assert_snapshot; |
126 | 175 | ||
127 | fn do_keyword_completion(code: &str) -> Vec<CompletionItem> { | 176 | fn get_keyword_completions(code: &str) -> String { |
128 | do_completion(code, CompletionKind::Keyword) | 177 | completion_list(code, CompletionKind::Keyword) |
129 | } | 178 | } |
130 | 179 | ||
131 | #[test] | 180 | #[test] |
132 | fn completes_keywords_in_use_stmt() { | 181 | fn test_keywords_in_use_stmt() { |
133 | assert_debug_snapshot!( | 182 | assert_snapshot!( |
134 | do_keyword_completion( | 183 | get_keyword_completions(r"use <|>"), |
135 | r" | ||
136 | use <|> | ||
137 | ", | ||
138 | ), | ||
139 | @r###" | 184 | @r###" |
140 | [ | 185 | kw crate |
141 | CompletionItem { | 186 | kw self |
142 | label: "crate", | 187 | kw super |
143 | source_range: 21..21, | ||
144 | delete: 21..21, | ||
145 | insert: "crate::", | ||
146 | kind: Keyword, | ||
147 | }, | ||
148 | CompletionItem { | ||
149 | label: "self", | ||
150 | source_range: 21..21, | ||
151 | delete: 21..21, | ||
152 | insert: "self", | ||
153 | kind: Keyword, | ||
154 | }, | ||
155 | CompletionItem { | ||
156 | label: "super", | ||
157 | source_range: 21..21, | ||
158 | delete: 21..21, | ||
159 | insert: "super::", | ||
160 | kind: Keyword, | ||
161 | }, | ||
162 | ] | ||
163 | "### | 188 | "### |
164 | ); | 189 | ); |
165 | 190 | ||
166 | assert_debug_snapshot!( | 191 | assert_snapshot!( |
167 | do_keyword_completion( | 192 | get_keyword_completions(r"use a::<|>"), |
168 | r" | ||
169 | use a::<|> | ||
170 | ", | ||
171 | ), | ||
172 | @r###" | 193 | @r###" |
173 | [ | 194 | kw self |
174 | CompletionItem { | 195 | kw super |
175 | label: "self", | ||
176 | source_range: 24..24, | ||
177 | delete: 24..24, | ||
178 | insert: "self", | ||
179 | kind: Keyword, | ||
180 | }, | ||
181 | CompletionItem { | ||
182 | label: "super", | ||
183 | source_range: 24..24, | ||
184 | delete: 24..24, | ||
185 | insert: "super::", | ||
186 | kind: Keyword, | ||
187 | }, | ||
188 | ] | ||
189 | "### | 196 | "### |
190 | ); | 197 | ); |
191 | 198 | ||
192 | assert_debug_snapshot!( | 199 | assert_snapshot!( |
193 | do_keyword_completion( | 200 | get_keyword_completions(r"use a::{b, <|>}"), |
194 | r" | ||
195 | use a::{b, <|>} | ||
196 | ", | ||
197 | ), | ||
198 | @r###" | 201 | @r###" |
199 | [ | 202 | kw self |
200 | CompletionItem { | 203 | kw super |
201 | label: "self", | ||
202 | source_range: 28..28, | ||
203 | delete: 28..28, | ||
204 | insert: "self", | ||
205 | kind: Keyword, | ||
206 | }, | ||
207 | CompletionItem { | ||
208 | label: "super", | ||
209 | source_range: 28..28, | ||
210 | delete: 28..28, | ||
211 | insert: "super::", | ||
212 | kind: Keyword, | ||
213 | }, | ||
214 | ] | ||
215 | "### | 204 | "### |
216 | ); | 205 | ); |
217 | } | 206 | } |
218 | 207 | ||
219 | #[test] | 208 | #[test] |
220 | fn completes_various_keywords_in_function() { | 209 | fn test_keywords_at_source_file_level() { |
221 | assert_debug_snapshot!( | 210 | assert_snapshot!( |
222 | do_keyword_completion( | 211 | get_keyword_completions(r"m<|>"), |
223 | r" | ||
224 | fn quux() { | ||
225 | <|> | ||
226 | } | ||
227 | ", | ||
228 | ), | ||
229 | @r###" | 212 | @r###" |
230 | [ | 213 | kw const |
231 | CompletionItem { | 214 | kw enum |
232 | label: "if", | 215 | kw extern |
233 | source_range: 49..49, | 216 | kw fn |
234 | delete: 49..49, | 217 | kw impl |
235 | insert: "if $0 {}", | 218 | kw mod |
236 | kind: Keyword, | 219 | kw pub |
237 | }, | 220 | kw static |
238 | CompletionItem { | 221 | kw struct |
239 | label: "loop", | 222 | kw trait |
240 | source_range: 49..49, | 223 | kw type |
241 | delete: 49..49, | 224 | kw union |
242 | insert: "loop {$0}", | 225 | kw unsafe |
243 | kind: Keyword, | 226 | kw use |
244 | }, | ||
245 | CompletionItem { | ||
246 | label: "match", | ||
247 | source_range: 49..49, | ||
248 | delete: 49..49, | ||
249 | insert: "match $0 {}", | ||
250 | kind: Keyword, | ||
251 | }, | ||
252 | CompletionItem { | ||
253 | label: "return", | ||
254 | source_range: 49..49, | ||
255 | delete: 49..49, | ||
256 | insert: "return;", | ||
257 | kind: Keyword, | ||
258 | }, | ||
259 | CompletionItem { | ||
260 | label: "while", | ||
261 | source_range: 49..49, | ||
262 | delete: 49..49, | ||
263 | insert: "while $0 {}", | ||
264 | kind: Keyword, | ||
265 | }, | ||
266 | ] | ||
267 | "### | 227 | "### |
268 | ); | 228 | ); |
269 | } | 229 | } |
270 | 230 | ||
271 | #[test] | 231 | #[test] |
272 | fn completes_else_after_if() { | 232 | fn test_keywords_in_function() { |
273 | assert_debug_snapshot!( | 233 | assert_snapshot!( |
274 | do_keyword_completion( | 234 | get_keyword_completions(r"fn quux() { <|> }"), |
275 | r" | ||
276 | fn quux() { | ||
277 | if true { | ||
278 | () | ||
279 | } <|> | ||
280 | } | ||
281 | ", | ||
282 | ), | ||
283 | @r###" | 235 | @r###" |
284 | [ | 236 | kw const |
285 | CompletionItem { | 237 | kw extern |
286 | label: "else", | 238 | kw fn |
287 | source_range: 108..108, | 239 | kw if |
288 | delete: 108..108, | 240 | kw if let |
289 | insert: "else {$0}", | 241 | kw impl |
290 | kind: Keyword, | 242 | kw let |
291 | }, | 243 | kw loop |
292 | CompletionItem { | 244 | kw match |
293 | label: "else if", | 245 | kw mod |
294 | source_range: 108..108, | 246 | kw return |
295 | delete: 108..108, | 247 | kw static |
296 | insert: "else if $0 {}", | 248 | kw trait |
297 | kind: Keyword, | 249 | kw type |
298 | }, | 250 | kw unsafe |
299 | CompletionItem { | 251 | kw use |
300 | label: "if", | 252 | kw while |
301 | source_range: 108..108, | ||
302 | delete: 108..108, | ||
303 | insert: "if $0 {}", | ||
304 | kind: Keyword, | ||
305 | }, | ||
306 | CompletionItem { | ||
307 | label: "loop", | ||
308 | source_range: 108..108, | ||
309 | delete: 108..108, | ||
310 | insert: "loop {$0}", | ||
311 | kind: Keyword, | ||
312 | }, | ||
313 | CompletionItem { | ||
314 | label: "match", | ||
315 | source_range: 108..108, | ||
316 | delete: 108..108, | ||
317 | insert: "match $0 {}", | ||
318 | kind: Keyword, | ||
319 | }, | ||
320 | CompletionItem { | ||
321 | label: "return", | ||
322 | source_range: 108..108, | ||
323 | delete: 108..108, | ||
324 | insert: "return;", | ||
325 | kind: Keyword, | ||
326 | }, | ||
327 | CompletionItem { | ||
328 | label: "while", | ||
329 | source_range: 108..108, | ||
330 | delete: 108..108, | ||
331 | insert: "while $0 {}", | ||
332 | kind: Keyword, | ||
333 | }, | ||
334 | ] | ||
335 | "### | 253 | "### |
336 | ); | 254 | ); |
337 | } | 255 | } |
338 | 256 | ||
339 | #[test] | 257 | #[test] |
340 | fn test_completion_return_value() { | 258 | fn test_keywords_inside_block() { |
341 | assert_debug_snapshot!( | 259 | assert_snapshot!( |
342 | do_keyword_completion( | 260 | get_keyword_completions(r"fn quux() { if true { <|> } }"), |
343 | r" | ||
344 | fn quux() -> i32 { | ||
345 | <|> | ||
346 | 92 | ||
347 | } | ||
348 | ", | ||
349 | ), | ||
350 | @r###" | 261 | @r###" |
351 | [ | 262 | kw const |
352 | CompletionItem { | 263 | kw extern |
353 | label: "if", | 264 | kw fn |
354 | source_range: 56..56, | 265 | kw if |
355 | delete: 56..56, | 266 | kw if let |
356 | insert: "if $0 {}", | 267 | kw impl |
357 | kind: Keyword, | 268 | kw let |
358 | }, | 269 | kw loop |
359 | CompletionItem { | 270 | kw match |
360 | label: "loop", | 271 | kw mod |
361 | source_range: 56..56, | 272 | kw return |
362 | delete: 56..56, | 273 | kw static |
363 | insert: "loop {$0}", | 274 | kw trait |
364 | kind: Keyword, | 275 | kw type |
365 | }, | 276 | kw unsafe |
366 | CompletionItem { | 277 | kw use |
367 | label: "match", | 278 | kw while |
368 | source_range: 56..56, | ||
369 | delete: 56..56, | ||
370 | insert: "match $0 {}", | ||
371 | kind: Keyword, | ||
372 | }, | ||
373 | CompletionItem { | ||
374 | label: "return", | ||
375 | source_range: 56..56, | ||
376 | delete: 56..56, | ||
377 | insert: "return $0;", | ||
378 | kind: Keyword, | ||
379 | }, | ||
380 | CompletionItem { | ||
381 | label: "while", | ||
382 | source_range: 56..56, | ||
383 | delete: 56..56, | ||
384 | insert: "while $0 {}", | ||
385 | kind: Keyword, | ||
386 | }, | ||
387 | ] | ||
388 | "### | 279 | "### |
389 | ); | 280 | ); |
390 | assert_debug_snapshot!( | 281 | } |
391 | do_keyword_completion( | 282 | |
283 | #[test] | ||
284 | fn test_keywords_after_if() { | ||
285 | assert_snapshot!( | ||
286 | get_keyword_completions( | ||
392 | r" | 287 | r" |
393 | fn quux() { | 288 | fn quux() { |
394 | <|> | 289 | if true { |
395 | 92 | 290 | () |
291 | } <|> | ||
396 | } | 292 | } |
397 | ", | 293 | ", |
398 | ), | 294 | ), |
399 | @r###" | 295 | @r###" |
400 | [ | 296 | kw const |
401 | CompletionItem { | 297 | kw else |
402 | label: "if", | 298 | kw else if |
403 | source_range: 49..49, | 299 | kw extern |
404 | delete: 49..49, | 300 | kw fn |
405 | insert: "if $0 {}", | 301 | kw if |
406 | kind: Keyword, | 302 | kw if let |
407 | }, | 303 | kw impl |
408 | CompletionItem { | 304 | kw let |
409 | label: "loop", | 305 | kw loop |
410 | source_range: 49..49, | 306 | kw match |
411 | delete: 49..49, | 307 | kw mod |
412 | insert: "loop {$0}", | 308 | kw return |
413 | kind: Keyword, | 309 | kw static |
414 | }, | 310 | kw trait |
415 | CompletionItem { | 311 | kw type |
416 | label: "match", | 312 | kw unsafe |
417 | source_range: 49..49, | 313 | kw use |
418 | delete: 49..49, | 314 | kw while |
419 | insert: "match $0 {}", | ||
420 | kind: Keyword, | ||
421 | }, | ||
422 | CompletionItem { | ||
423 | label: "return", | ||
424 | source_range: 49..49, | ||
425 | delete: 49..49, | ||
426 | insert: "return;", | ||
427 | kind: Keyword, | ||
428 | }, | ||
429 | CompletionItem { | ||
430 | label: "while", | ||
431 | source_range: 49..49, | ||
432 | delete: 49..49, | ||
433 | insert: "while $0 {}", | ||
434 | kind: Keyword, | ||
435 | }, | ||
436 | ] | ||
437 | "### | 315 | "### |
438 | ); | 316 | ); |
439 | } | 317 | } |
440 | 318 | ||
441 | #[test] | 319 | #[test] |
442 | fn dont_add_semi_after_return_if_not_a_statement() { | 320 | fn test_keywords_in_match_arm() { |
443 | assert_debug_snapshot!( | 321 | assert_snapshot!( |
444 | do_keyword_completion( | 322 | get_keyword_completions( |
445 | r" | 323 | r" |
446 | fn quux() -> i32 { | 324 | fn quux() -> i32 { |
447 | match () { | 325 | match () { |
@@ -451,336 +329,130 @@ mod tests { | |||
451 | ", | 329 | ", |
452 | ), | 330 | ), |
453 | @r###" | 331 | @r###" |
454 | [ | 332 | kw if |
455 | CompletionItem { | 333 | kw if let |
456 | label: "if", | 334 | kw loop |
457 | source_range: 97..97, | 335 | kw match |
458 | delete: 97..97, | 336 | kw return |
459 | insert: "if $0 {}", | 337 | kw unsafe |
460 | kind: Keyword, | ||
461 | }, | ||
462 | CompletionItem { | ||
463 | label: "loop", | ||
464 | source_range: 97..97, | ||
465 | delete: 97..97, | ||
466 | insert: "loop {$0}", | ||
467 | kind: Keyword, | ||
468 | }, | ||
469 | CompletionItem { | ||
470 | label: "match", | ||
471 | source_range: 97..97, | ||
472 | delete: 97..97, | ||
473 | insert: "match $0 {}", | ||
474 | kind: Keyword, | ||
475 | }, | ||
476 | CompletionItem { | ||
477 | label: "return", | ||
478 | source_range: 97..97, | ||
479 | delete: 97..97, | ||
480 | insert: "return $0", | ||
481 | kind: Keyword, | ||
482 | }, | ||
483 | CompletionItem { | ||
484 | label: "while", | ||
485 | source_range: 97..97, | ||
486 | delete: 97..97, | ||
487 | insert: "while $0 {}", | ||
488 | kind: Keyword, | ||
489 | }, | ||
490 | ] | ||
491 | "### | 338 | "### |
492 | ); | 339 | ); |
493 | } | 340 | } |
494 | 341 | ||
495 | #[test] | 342 | #[test] |
496 | fn last_return_in_block_has_semi() { | 343 | fn test_keywords_in_trait_def() { |
497 | assert_debug_snapshot!( | 344 | assert_snapshot!( |
498 | do_keyword_completion( | 345 | get_keyword_completions(r"trait My { <|> }"), |
499 | r" | ||
500 | fn quux() -> i32 { | ||
501 | if condition { | ||
502 | <|> | ||
503 | } | ||
504 | } | ||
505 | ", | ||
506 | ), | ||
507 | @r###" | 346 | @r###" |
508 | [ | 347 | kw const |
509 | CompletionItem { | 348 | kw fn |
510 | label: "if", | 349 | kw type |
511 | source_range: 95..95, | 350 | kw unsafe |
512 | delete: 95..95, | ||
513 | insert: "if $0 {}", | ||
514 | kind: Keyword, | ||
515 | }, | ||
516 | CompletionItem { | ||
517 | label: "loop", | ||
518 | source_range: 95..95, | ||
519 | delete: 95..95, | ||
520 | insert: "loop {$0}", | ||
521 | kind: Keyword, | ||
522 | }, | ||
523 | CompletionItem { | ||
524 | label: "match", | ||
525 | source_range: 95..95, | ||
526 | delete: 95..95, | ||
527 | insert: "match $0 {}", | ||
528 | kind: Keyword, | ||
529 | }, | ||
530 | CompletionItem { | ||
531 | label: "return", | ||
532 | source_range: 95..95, | ||
533 | delete: 95..95, | ||
534 | insert: "return $0;", | ||
535 | kind: Keyword, | ||
536 | }, | ||
537 | CompletionItem { | ||
538 | label: "while", | ||
539 | source_range: 95..95, | ||
540 | delete: 95..95, | ||
541 | insert: "while $0 {}", | ||
542 | kind: Keyword, | ||
543 | }, | ||
544 | ] | ||
545 | "### | 351 | "### |
546 | ); | 352 | ); |
547 | assert_debug_snapshot!( | 353 | } |
548 | do_keyword_completion( | 354 | |
549 | r" | 355 | #[test] |
550 | fn quux() -> i32 { | 356 | fn test_keywords_in_impl_def() { |
551 | if condition { | 357 | assert_snapshot!( |
552 | <|> | 358 | get_keyword_completions(r"impl My { <|> }"), |
553 | } | ||
554 | let x = 92; | ||
555 | x | ||
556 | } | ||
557 | ", | ||
558 | ), | ||
559 | @r###" | 359 | @r###" |
560 | [ | 360 | kw const |
561 | CompletionItem { | 361 | kw fn |
562 | label: "if", | 362 | kw pub |
563 | source_range: 95..95, | 363 | kw type |
564 | delete: 95..95, | 364 | kw unsafe |
565 | insert: "if $0 {}", | ||
566 | kind: Keyword, | ||
567 | }, | ||
568 | CompletionItem { | ||
569 | label: "loop", | ||
570 | source_range: 95..95, | ||
571 | delete: 95..95, | ||
572 | insert: "loop {$0}", | ||
573 | kind: Keyword, | ||
574 | }, | ||
575 | CompletionItem { | ||
576 | label: "match", | ||
577 | source_range: 95..95, | ||
578 | delete: 95..95, | ||
579 | insert: "match $0 {}", | ||
580 | kind: Keyword, | ||
581 | }, | ||
582 | CompletionItem { | ||
583 | label: "return", | ||
584 | source_range: 95..95, | ||
585 | delete: 95..95, | ||
586 | insert: "return $0;", | ||
587 | kind: Keyword, | ||
588 | }, | ||
589 | CompletionItem { | ||
590 | label: "while", | ||
591 | source_range: 95..95, | ||
592 | delete: 95..95, | ||
593 | insert: "while $0 {}", | ||
594 | kind: Keyword, | ||
595 | }, | ||
596 | ] | ||
597 | "### | 365 | "### |
598 | ); | 366 | ); |
599 | } | 367 | } |
600 | 368 | ||
601 | #[test] | 369 | #[test] |
602 | fn completes_break_and_continue_in_loops() { | 370 | fn test_keywords_in_loop() { |
603 | assert_debug_snapshot!( | 371 | assert_snapshot!( |
604 | do_keyword_completion( | 372 | get_keyword_completions(r"fn my() { loop { <|> } }"), |
605 | r" | ||
606 | fn quux() -> i32 { | ||
607 | loop { <|> } | ||
608 | } | ||
609 | ", | ||
610 | ), | ||
611 | @r###" | 373 | @r###" |
612 | [ | 374 | kw break |
613 | CompletionItem { | 375 | kw const |
614 | label: "break", | 376 | kw continue |
615 | source_range: 63..63, | 377 | kw extern |
616 | delete: 63..63, | 378 | kw fn |
617 | insert: "break;", | 379 | kw if |
618 | kind: Keyword, | 380 | kw if let |
619 | }, | 381 | kw impl |
620 | CompletionItem { | 382 | kw let |
621 | label: "continue", | 383 | kw loop |
622 | source_range: 63..63, | 384 | kw match |
623 | delete: 63..63, | 385 | kw mod |
624 | insert: "continue;", | 386 | kw return |
625 | kind: Keyword, | 387 | kw static |
626 | }, | 388 | kw trait |
627 | CompletionItem { | 389 | kw type |
628 | label: "if", | 390 | kw unsafe |
629 | source_range: 63..63, | 391 | kw use |
630 | delete: 63..63, | 392 | kw while |
631 | insert: "if $0 {}", | ||
632 | kind: Keyword, | ||
633 | }, | ||
634 | CompletionItem { | ||
635 | label: "loop", | ||
636 | source_range: 63..63, | ||
637 | delete: 63..63, | ||
638 | insert: "loop {$0}", | ||
639 | kind: Keyword, | ||
640 | }, | ||
641 | CompletionItem { | ||
642 | label: "match", | ||
643 | source_range: 63..63, | ||
644 | delete: 63..63, | ||
645 | insert: "match $0 {}", | ||
646 | kind: Keyword, | ||
647 | }, | ||
648 | CompletionItem { | ||
649 | label: "return", | ||
650 | source_range: 63..63, | ||
651 | delete: 63..63, | ||
652 | insert: "return $0;", | ||
653 | kind: Keyword, | ||
654 | }, | ||
655 | CompletionItem { | ||
656 | label: "while", | ||
657 | source_range: 63..63, | ||
658 | delete: 63..63, | ||
659 | insert: "while $0 {}", | ||
660 | kind: Keyword, | ||
661 | }, | ||
662 | ] | ||
663 | "### | 393 | "### |
664 | ); | 394 | ); |
395 | } | ||
665 | 396 | ||
666 | // No completion: lambda isolates control flow | 397 | #[test] |
667 | assert_debug_snapshot!( | 398 | fn test_keywords_after_unsafe_in_item_list() { |
668 | do_keyword_completion( | 399 | assert_snapshot!( |
669 | r" | 400 | get_keyword_completions(r"unsafe <|>"), |
670 | fn quux() -> i32 { | ||
671 | loop { || { <|> } } | ||
672 | } | ||
673 | ", | ||
674 | ), | ||
675 | @r###" | 401 | @r###" |
676 | [ | 402 | kw fn |
677 | CompletionItem { | 403 | kw impl |
678 | label: "if", | 404 | kw trait |
679 | source_range: 68..68, | ||
680 | delete: 68..68, | ||
681 | insert: "if $0 {}", | ||
682 | kind: Keyword, | ||
683 | }, | ||
684 | CompletionItem { | ||
685 | label: "loop", | ||
686 | source_range: 68..68, | ||
687 | delete: 68..68, | ||
688 | insert: "loop {$0}", | ||
689 | kind: Keyword, | ||
690 | }, | ||
691 | CompletionItem { | ||
692 | label: "match", | ||
693 | source_range: 68..68, | ||
694 | delete: 68..68, | ||
695 | insert: "match $0 {}", | ||
696 | kind: Keyword, | ||
697 | }, | ||
698 | CompletionItem { | ||
699 | label: "return", | ||
700 | source_range: 68..68, | ||
701 | delete: 68..68, | ||
702 | insert: "return $0;", | ||
703 | kind: Keyword, | ||
704 | }, | ||
705 | CompletionItem { | ||
706 | label: "while", | ||
707 | source_range: 68..68, | ||
708 | delete: 68..68, | ||
709 | insert: "while $0 {}", | ||
710 | kind: Keyword, | ||
711 | }, | ||
712 | ] | ||
713 | "### | 405 | "### |
714 | ); | 406 | ); |
715 | } | 407 | } |
716 | 408 | ||
717 | #[test] | 409 | #[test] |
718 | fn no_semi_after_break_continue_in_expr() { | 410 | fn test_keywords_after_unsafe_in_block_expr() { |
719 | assert_debug_snapshot!( | 411 | assert_snapshot!( |
720 | do_keyword_completion( | 412 | get_keyword_completions(r"fn my_fn() { unsafe <|> }"), |
721 | r" | ||
722 | fn f() { | ||
723 | loop { | ||
724 | match () { | ||
725 | () => br<|> | ||
726 | } | ||
727 | } | ||
728 | } | ||
729 | ", | ||
730 | ), | ||
731 | @r###" | 413 | @r###" |
732 | [ | 414 | kw fn |
733 | CompletionItem { | 415 | kw impl |
734 | label: "break", | 416 | kw trait |
735 | source_range: 122..124, | ||
736 | delete: 122..124, | ||
737 | insert: "break", | ||
738 | kind: Keyword, | ||
739 | }, | ||
740 | CompletionItem { | ||
741 | label: "continue", | ||
742 | source_range: 122..124, | ||
743 | delete: 122..124, | ||
744 | insert: "continue", | ||
745 | kind: Keyword, | ||
746 | }, | ||
747 | CompletionItem { | ||
748 | label: "if", | ||
749 | source_range: 122..124, | ||
750 | delete: 122..124, | ||
751 | insert: "if $0 {}", | ||
752 | kind: Keyword, | ||
753 | }, | ||
754 | CompletionItem { | ||
755 | label: "loop", | ||
756 | source_range: 122..124, | ||
757 | delete: 122..124, | ||
758 | insert: "loop {$0}", | ||
759 | kind: Keyword, | ||
760 | }, | ||
761 | CompletionItem { | ||
762 | label: "match", | ||
763 | source_range: 122..124, | ||
764 | delete: 122..124, | ||
765 | insert: "match $0 {}", | ||
766 | kind: Keyword, | ||
767 | }, | ||
768 | CompletionItem { | ||
769 | label: "return", | ||
770 | source_range: 122..124, | ||
771 | delete: 122..124, | ||
772 | insert: "return", | ||
773 | kind: Keyword, | ||
774 | }, | ||
775 | CompletionItem { | ||
776 | label: "while", | ||
777 | source_range: 122..124, | ||
778 | delete: 122..124, | ||
779 | insert: "while $0 {}", | ||
780 | kind: Keyword, | ||
781 | }, | ||
782 | ] | ||
783 | "### | 417 | "### |
784 | ) | 418 | ); |
419 | } | ||
420 | |||
421 | #[test] | ||
422 | fn test_mut_in_ref_and_in_fn_parameters_list() { | ||
423 | assert_snapshot!( | ||
424 | get_keyword_completions(r"fn my_fn(&<|>) {}"), | ||
425 | @r###" | ||
426 | kw mut | ||
427 | "### | ||
428 | ); | ||
429 | assert_snapshot!( | ||
430 | get_keyword_completions(r"fn my_fn(<|>) {}"), | ||
431 | @r###" | ||
432 | kw mut | ||
433 | "### | ||
434 | ); | ||
435 | assert_snapshot!( | ||
436 | get_keyword_completions(r"fn my_fn() { let &<|> }"), | ||
437 | @r###" | ||
438 | kw mut | ||
439 | "### | ||
440 | ); | ||
441 | } | ||
442 | |||
443 | #[test] | ||
444 | fn test_where_keyword() { | ||
445 | assert_snapshot!( | ||
446 | get_keyword_completions(r"trait A <|>"), | ||
447 | @r###" | ||
448 | kw where | ||
449 | "### | ||
450 | ); | ||
451 | assert_snapshot!( | ||
452 | get_keyword_completions(r"impl A <|>"), | ||
453 | @r###" | ||
454 | kw where | ||
455 | "### | ||
456 | ); | ||
785 | } | 457 | } |
786 | } | 458 | } |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 59b58bf98..b878aeb0a 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -91,7 +91,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
91 | &dot_receiver, | 91 | &dot_receiver, |
92 | "if", | 92 | "if", |
93 | "if expr {}", | 93 | "if expr {}", |
94 | &format!("if {} {{$0}}", receiver_text), | 94 | &format!("if {} {{\n $0\n}}", receiver_text), |
95 | ) | 95 | ) |
96 | .add_to(acc); | 96 | .add_to(acc); |
97 | postfix_snippet( | 97 | postfix_snippet( |
@@ -100,7 +100,7 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | |||
100 | &dot_receiver, | 100 | &dot_receiver, |
101 | "while", | 101 | "while", |
102 | "while expr {}", | 102 | "while expr {}", |
103 | &format!("while {} {{\n$0\n}}", receiver_text), | 103 | &format!("while {} {{\n $0\n}}", receiver_text), |
104 | ) | 104 | ) |
105 | .add_to(acc); | 105 | .add_to(acc); |
106 | } | 106 | } |
@@ -283,7 +283,7 @@ mod tests { | |||
283 | label: "if", | 283 | label: "if", |
284 | source_range: 89..89, | 284 | source_range: 89..89, |
285 | delete: 85..89, | 285 | delete: 85..89, |
286 | insert: "if bar {$0}", | 286 | insert: "if bar {\n $0\n}", |
287 | detail: "if expr {}", | 287 | detail: "if expr {}", |
288 | }, | 288 | }, |
289 | CompletionItem { | 289 | CompletionItem { |
@@ -318,7 +318,7 @@ mod tests { | |||
318 | label: "while", | 318 | label: "while", |
319 | source_range: 89..89, | 319 | source_range: 89..89, |
320 | delete: 85..89, | 320 | delete: 85..89, |
321 | insert: "while bar {\n$0\n}", | 321 | insert: "while bar {\n $0\n}", |
322 | detail: "while expr {}", | 322 | detail: "while expr {}", |
323 | }, | 323 | }, |
324 | ] | 324 | ] |
diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs index c4646b727..560fb19e6 100644 --- a/crates/ra_ide/src/completion/completion_context.rs +++ b/crates/ra_ide/src/completion/completion_context.rs | |||
@@ -5,12 +5,17 @@ use ra_db::SourceDatabase; | |||
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | algo::{find_covering_element, find_node_at_offset}, | 7 | algo::{find_covering_element, find_node_at_offset}, |
8 | ast, match_ast, AstNode, | 8 | ast, match_ast, AstNode, NodeOrToken, |
9 | SyntaxKind::*, | 9 | SyntaxKind::*, |
10 | SyntaxNode, SyntaxToken, TextRange, TextSize, | 10 | SyntaxNode, SyntaxToken, TextRange, TextSize, |
11 | }; | 11 | }; |
12 | use ra_text_edit::Indel; | 12 | use ra_text_edit::Indel; |
13 | 13 | ||
14 | use super::patterns::{ | ||
15 | has_bind_pat_parent, has_block_expr_parent, has_impl_as_prev_sibling, has_impl_parent, | ||
16 | has_item_list_or_source_file_parent, has_ref_parent, has_trait_as_prev_sibling, | ||
17 | has_trait_parent, if_is_prev, is_in_loop_body, is_match_arm, unsafe_is_prev, | ||
18 | }; | ||
14 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; | 19 | use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition}; |
15 | use test_utils::mark; | 20 | use test_utils::mark; |
16 | 21 | ||
@@ -60,6 +65,18 @@ pub(crate) struct CompletionContext<'a> { | |||
60 | pub(super) is_path_type: bool, | 65 | pub(super) is_path_type: bool, |
61 | pub(super) has_type_args: bool, | 66 | pub(super) has_type_args: bool, |
62 | pub(super) attribute_under_caret: Option<ast::Attr>, | 67 | pub(super) attribute_under_caret: Option<ast::Attr>, |
68 | pub(super) unsafe_is_prev: bool, | ||
69 | pub(super) if_is_prev: bool, | ||
70 | pub(super) block_expr_parent: bool, | ||
71 | pub(super) bind_pat_parent: bool, | ||
72 | pub(super) ref_pat_parent: bool, | ||
73 | pub(super) in_loop_body: bool, | ||
74 | pub(super) has_trait_parent: bool, | ||
75 | pub(super) has_impl_parent: bool, | ||
76 | pub(super) trait_as_prev_sibling: bool, | ||
77 | pub(super) impl_as_prev_sibling: bool, | ||
78 | pub(super) is_match_arm: bool, | ||
79 | pub(super) has_item_list_or_source_file_parent: bool, | ||
63 | } | 80 | } |
64 | 81 | ||
65 | impl<'a> CompletionContext<'a> { | 82 | impl<'a> CompletionContext<'a> { |
@@ -118,6 +135,18 @@ impl<'a> CompletionContext<'a> { | |||
118 | has_type_args: false, | 135 | has_type_args: false, |
119 | dot_receiver_is_ambiguous_float_literal: false, | 136 | dot_receiver_is_ambiguous_float_literal: false, |
120 | attribute_under_caret: None, | 137 | attribute_under_caret: None, |
138 | unsafe_is_prev: false, | ||
139 | in_loop_body: false, | ||
140 | ref_pat_parent: false, | ||
141 | bind_pat_parent: false, | ||
142 | block_expr_parent: false, | ||
143 | has_trait_parent: false, | ||
144 | has_impl_parent: false, | ||
145 | trait_as_prev_sibling: false, | ||
146 | impl_as_prev_sibling: false, | ||
147 | if_is_prev: false, | ||
148 | is_match_arm: false, | ||
149 | has_item_list_or_source_file_parent: false, | ||
121 | }; | 150 | }; |
122 | 151 | ||
123 | let mut original_file = original_file.syntax().clone(); | 152 | let mut original_file = original_file.syntax().clone(); |
@@ -159,7 +188,7 @@ impl<'a> CompletionContext<'a> { | |||
159 | break; | 188 | break; |
160 | } | 189 | } |
161 | } | 190 | } |
162 | 191 | ctx.fill_keyword_patterns(&hypothetical_file, offset); | |
163 | ctx.fill(&original_file, hypothetical_file, offset); | 192 | ctx.fill(&original_file, hypothetical_file, offset); |
164 | Some(ctx) | 193 | Some(ctx) |
165 | } | 194 | } |
@@ -188,6 +217,24 @@ impl<'a> CompletionContext<'a> { | |||
188 | self.sema.scope_at_offset(&self.token.parent(), self.offset) | 217 | self.sema.scope_at_offset(&self.token.parent(), self.offset) |
189 | } | 218 | } |
190 | 219 | ||
220 | fn fill_keyword_patterns(&mut self, file_with_fake_ident: &SyntaxNode, offset: TextSize) { | ||
221 | let fake_ident_token = file_with_fake_ident.token_at_offset(offset).right_biased().unwrap(); | ||
222 | let syntax_element = NodeOrToken::Token(fake_ident_token.clone()); | ||
223 | self.block_expr_parent = has_block_expr_parent(syntax_element.clone()); | ||
224 | self.unsafe_is_prev = unsafe_is_prev(syntax_element.clone()); | ||
225 | self.if_is_prev = if_is_prev(syntax_element.clone()); | ||
226 | self.bind_pat_parent = has_bind_pat_parent(syntax_element.clone()); | ||
227 | self.ref_pat_parent = has_ref_parent(syntax_element.clone()); | ||
228 | self.in_loop_body = is_in_loop_body(syntax_element.clone()); | ||
229 | self.has_trait_parent = has_trait_parent(syntax_element.clone()); | ||
230 | self.has_impl_parent = has_impl_parent(syntax_element.clone()); | ||
231 | self.impl_as_prev_sibling = has_impl_as_prev_sibling(syntax_element.clone()); | ||
232 | self.trait_as_prev_sibling = has_trait_as_prev_sibling(syntax_element.clone()); | ||
233 | self.is_match_arm = is_match_arm(syntax_element.clone()); | ||
234 | self.has_item_list_or_source_file_parent = | ||
235 | has_item_list_or_source_file_parent(syntax_element.clone()); | ||
236 | } | ||
237 | |||
191 | fn fill( | 238 | fn fill( |
192 | &mut self, | 239 | &mut self, |
193 | original_file: &SyntaxNode, | 240 | original_file: &SyntaxNode, |
@@ -334,6 +381,7 @@ impl<'a> CompletionContext<'a> { | |||
334 | 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(); |
335 | self.has_type_args = segment.type_arg_list().is_some(); | 382 | self.has_type_args = segment.type_arg_list().is_some(); |
336 | 383 | ||
384 | #[allow(deprecated)] | ||
337 | if let Some(path) = hir::Path::from_ast(path.clone()) { | 385 | if let Some(path) = hir::Path::from_ast(path.clone()) { |
338 | if let Some(path_prefix) = path.qualifier() { | 386 | if let Some(path_prefix) = path.qualifier() { |
339 | self.path_prefix = Some(path_prefix); | 387 | self.path_prefix = Some(path_prefix); |
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs index cfb7c1e38..98348b349 100644 --- a/crates/ra_ide/src/completion/completion_item.rs +++ b/crates/ra_ide/src/completion/completion_item.rs | |||
@@ -125,6 +125,32 @@ pub enum CompletionItemKind { | |||
125 | Attribute, | 125 | Attribute, |
126 | } | 126 | } |
127 | 127 | ||
128 | impl CompletionItemKind { | ||
129 | #[cfg(test)] | ||
130 | pub(crate) fn tag(&self) -> &'static str { | ||
131 | match self { | ||
132 | CompletionItemKind::Snippet => "sn", | ||
133 | CompletionItemKind::Keyword => "kw", | ||
134 | CompletionItemKind::Module => "md", | ||
135 | CompletionItemKind::Function => "fn", | ||
136 | CompletionItemKind::BuiltinType => "bt", | ||
137 | CompletionItemKind::Struct => "st", | ||
138 | CompletionItemKind::Enum => "en", | ||
139 | CompletionItemKind::EnumVariant => "ev", | ||
140 | CompletionItemKind::Binding => "bn", | ||
141 | CompletionItemKind::Field => "fd", | ||
142 | CompletionItemKind::Static => "sc", | ||
143 | CompletionItemKind::Const => "ct", | ||
144 | CompletionItemKind::Trait => "tt", | ||
145 | CompletionItemKind::TypeAlias => "ta", | ||
146 | CompletionItemKind::Method => "me", | ||
147 | CompletionItemKind::TypeParam => "tp", | ||
148 | CompletionItemKind::Macro => "ma", | ||
149 | CompletionItemKind::Attribute => "at", | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
128 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] | 154 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] |
129 | pub(crate) enum CompletionKind { | 155 | pub(crate) enum CompletionKind { |
130 | /// Parser-based keyword completion. | 156 | /// Parser-based keyword completion. |
diff --git a/crates/ra_ide/src/completion/patterns.rs b/crates/ra_ide/src/completion/patterns.rs new file mode 100644 index 000000000..b2fe13280 --- /dev/null +++ b/crates/ra_ide/src/completion/patterns.rs | |||
@@ -0,0 +1,194 @@ | |||
1 | //! Patterns telling us certain facts about current syntax element, they are used in completion context | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::non_trivia_sibling, | ||
5 | ast::{self, LoopBodyOwner}, | ||
6 | match_ast, AstNode, Direction, NodeOrToken, SyntaxElement, | ||
7 | SyntaxKind::*, | ||
8 | SyntaxNode, SyntaxToken, | ||
9 | }; | ||
10 | |||
11 | #[cfg(test)] | ||
12 | use crate::completion::test_utils::check_pattern_is_applicable; | ||
13 | |||
14 | pub(crate) fn has_trait_parent(element: SyntaxElement) -> bool { | ||
15 | not_same_range_ancestor(element) | ||
16 | .filter(|it| it.kind() == ITEM_LIST) | ||
17 | .and_then(|it| it.parent()) | ||
18 | .filter(|it| it.kind() == TRAIT_DEF) | ||
19 | .is_some() | ||
20 | } | ||
21 | #[test] | ||
22 | fn test_has_trait_parent() { | ||
23 | check_pattern_is_applicable(r"trait A { f<|> }", has_trait_parent); | ||
24 | } | ||
25 | |||
26 | pub(crate) fn has_impl_parent(element: SyntaxElement) -> bool { | ||
27 | not_same_range_ancestor(element) | ||
28 | .filter(|it| it.kind() == ITEM_LIST) | ||
29 | .and_then(|it| it.parent()) | ||
30 | .filter(|it| it.kind() == IMPL_DEF) | ||
31 | .is_some() | ||
32 | } | ||
33 | #[test] | ||
34 | fn test_has_impl_parent() { | ||
35 | check_pattern_is_applicable(r"impl A { f<|> }", has_impl_parent); | ||
36 | } | ||
37 | |||
38 | pub(crate) fn has_block_expr_parent(element: SyntaxElement) -> bool { | ||
39 | not_same_range_ancestor(element).filter(|it| it.kind() == BLOCK_EXPR).is_some() | ||
40 | } | ||
41 | #[test] | ||
42 | fn test_has_block_expr_parent() { | ||
43 | check_pattern_is_applicable(r"fn my_fn() { let a = 2; f<|> }", has_block_expr_parent); | ||
44 | } | ||
45 | |||
46 | pub(crate) fn has_bind_pat_parent(element: SyntaxElement) -> bool { | ||
47 | element.ancestors().find(|it| it.kind() == BIND_PAT).is_some() | ||
48 | } | ||
49 | #[test] | ||
50 | fn test_has_bind_pat_parent() { | ||
51 | check_pattern_is_applicable(r"fn my_fn(m<|>) {}", has_bind_pat_parent); | ||
52 | check_pattern_is_applicable(r"fn my_fn() { let m<|> }", has_bind_pat_parent); | ||
53 | } | ||
54 | |||
55 | pub(crate) fn has_ref_parent(element: SyntaxElement) -> bool { | ||
56 | not_same_range_ancestor(element) | ||
57 | .filter(|it| it.kind() == REF_PAT || it.kind() == REF_EXPR) | ||
58 | .is_some() | ||
59 | } | ||
60 | #[test] | ||
61 | fn test_has_ref_parent() { | ||
62 | check_pattern_is_applicable(r"fn my_fn(&m<|>) {}", has_ref_parent); | ||
63 | check_pattern_is_applicable(r"fn my() { let &m<|> }", has_ref_parent); | ||
64 | } | ||
65 | |||
66 | pub(crate) fn has_item_list_or_source_file_parent(element: SyntaxElement) -> bool { | ||
67 | let ancestor = not_same_range_ancestor(element); | ||
68 | if !ancestor.is_some() { | ||
69 | return true; | ||
70 | } | ||
71 | ancestor.filter(|it| it.kind() == SOURCE_FILE || it.kind() == ITEM_LIST).is_some() | ||
72 | } | ||
73 | #[test] | ||
74 | fn test_has_item_list_or_source_file_parent() { | ||
75 | check_pattern_is_applicable(r"i<|>", has_item_list_or_source_file_parent); | ||
76 | check_pattern_is_applicable(r"impl { f<|> }", has_item_list_or_source_file_parent); | ||
77 | } | ||
78 | |||
79 | pub(crate) fn is_match_arm(element: SyntaxElement) -> bool { | ||
80 | not_same_range_ancestor(element.clone()).filter(|it| it.kind() == MATCH_ARM).is_some() | ||
81 | && previous_sibling_or_ancestor_sibling(element) | ||
82 | .and_then(|it| it.into_token()) | ||
83 | .filter(|it| it.kind() == FAT_ARROW) | ||
84 | .is_some() | ||
85 | } | ||
86 | #[test] | ||
87 | fn test_is_match_arm() { | ||
88 | check_pattern_is_applicable(r"fn my_fn() { match () { () => m<|> } }", is_match_arm); | ||
89 | } | ||
90 | |||
91 | pub(crate) fn unsafe_is_prev(element: SyntaxElement) -> bool { | ||
92 | element | ||
93 | .into_token() | ||
94 | .and_then(|it| previous_non_trivia_token(it)) | ||
95 | .filter(|it| it.kind() == UNSAFE_KW) | ||
96 | .is_some() | ||
97 | } | ||
98 | #[test] | ||
99 | fn test_unsafe_is_prev() { | ||
100 | check_pattern_is_applicable(r"unsafe i<|>", unsafe_is_prev); | ||
101 | } | ||
102 | |||
103 | pub(crate) fn if_is_prev(element: SyntaxElement) -> bool { | ||
104 | element | ||
105 | .into_token() | ||
106 | .and_then(|it| previous_non_trivia_token(it)) | ||
107 | .filter(|it| it.kind() == IF_KW) | ||
108 | .is_some() | ||
109 | } | ||
110 | #[test] | ||
111 | fn test_if_is_prev() { | ||
112 | check_pattern_is_applicable(r"if l<|>", if_is_prev); | ||
113 | } | ||
114 | |||
115 | pub(crate) fn has_trait_as_prev_sibling(element: SyntaxElement) -> bool { | ||
116 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == TRAIT_DEF).is_some() | ||
117 | } | ||
118 | #[test] | ||
119 | fn test_has_trait_as_prev_sibling() { | ||
120 | check_pattern_is_applicable(r"trait A w<|> {}", has_trait_as_prev_sibling); | ||
121 | } | ||
122 | |||
123 | pub(crate) fn has_impl_as_prev_sibling(element: SyntaxElement) -> bool { | ||
124 | previous_sibling_or_ancestor_sibling(element).filter(|it| it.kind() == IMPL_DEF).is_some() | ||
125 | } | ||
126 | #[test] | ||
127 | fn test_has_impl_as_prev_sibling() { | ||
128 | check_pattern_is_applicable(r"impl A w<|> {}", has_impl_as_prev_sibling); | ||
129 | } | ||
130 | |||
131 | pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { | ||
132 | let leaf = match element { | ||
133 | NodeOrToken::Node(node) => node, | ||
134 | NodeOrToken::Token(token) => token.parent(), | ||
135 | }; | ||
136 | for node in leaf.ancestors() { | ||
137 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | ||
138 | break; | ||
139 | } | ||
140 | let loop_body = match_ast! { | ||
141 | match node { | ||
142 | ast::ForExpr(it) => it.loop_body(), | ||
143 | ast::WhileExpr(it) => it.loop_body(), | ||
144 | ast::LoopExpr(it) => it.loop_body(), | ||
145 | _ => None, | ||
146 | } | ||
147 | }; | ||
148 | if let Some(body) = loop_body { | ||
149 | if body.syntax().text_range().contains_range(leaf.text_range()) { | ||
150 | return true; | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | false | ||
155 | } | ||
156 | |||
157 | fn not_same_range_ancestor(element: SyntaxElement) -> Option<SyntaxNode> { | ||
158 | element | ||
159 | .ancestors() | ||
160 | .take_while(|it| it.text_range() == element.text_range()) | ||
161 | .last() | ||
162 | .and_then(|it| it.parent()) | ||
163 | } | ||
164 | |||
165 | fn previous_non_trivia_token(token: SyntaxToken) -> Option<SyntaxToken> { | ||
166 | let mut token = token.prev_token(); | ||
167 | while let Some(inner) = token.clone() { | ||
168 | if !inner.kind().is_trivia() { | ||
169 | return Some(inner); | ||
170 | } else { | ||
171 | token = inner.prev_token(); | ||
172 | } | ||
173 | } | ||
174 | None | ||
175 | } | ||
176 | |||
177 | fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option<SyntaxElement> { | ||
178 | let token_sibling = non_trivia_sibling(element.clone(), Direction::Prev); | ||
179 | if let Some(sibling) = token_sibling { | ||
180 | Some(sibling) | ||
181 | } else { | ||
182 | // if not trying to find first ancestor which has such a sibling | ||
183 | let node = match element { | ||
184 | NodeOrToken::Node(node) => node, | ||
185 | NodeOrToken::Token(token) => token.parent(), | ||
186 | }; | ||
187 | let range = node.text_range(); | ||
188 | let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; | ||
189 | let prev_sibling_node = top_node.ancestors().find(|it| { | ||
190 | non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() | ||
191 | })?; | ||
192 | non_trivia_sibling(NodeOrToken::Node(prev_sibling_node), Direction::Prev) | ||
193 | } | ||
194 | } | ||
diff --git a/crates/ra_ide/src/completion/test_utils.rs b/crates/ra_ide/src/completion/test_utils.rs index bf22452a2..1e16a43ca 100644 --- a/crates/ra_ide/src/completion/test_utils.rs +++ b/crates/ra_ide/src/completion/test_utils.rs | |||
@@ -5,25 +5,63 @@ use crate::{ | |||
5 | mock_analysis::{analysis_and_position, single_file_with_position}, | 5 | mock_analysis::{analysis_and_position, single_file_with_position}, |
6 | CompletionItem, | 6 | CompletionItem, |
7 | }; | 7 | }; |
8 | use hir::Semantics; | ||
9 | use ra_syntax::{AstNode, NodeOrToken, SyntaxElement}; | ||
8 | 10 | ||
9 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { | 11 | pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec<CompletionItem> { |
10 | do_completion_with_options(code, kind, &CompletionConfig::default()) | 12 | do_completion_with_options(code, kind, &CompletionConfig::default()) |
11 | } | 13 | } |
12 | 14 | ||
15 | pub(crate) fn completion_list(code: &str, kind: CompletionKind) -> String { | ||
16 | completion_list_with_options(code, kind, &CompletionConfig::default()) | ||
17 | } | ||
18 | |||
13 | pub(crate) fn do_completion_with_options( | 19 | pub(crate) fn do_completion_with_options( |
14 | code: &str, | 20 | code: &str, |
15 | kind: CompletionKind, | 21 | kind: CompletionKind, |
16 | options: &CompletionConfig, | 22 | options: &CompletionConfig, |
17 | ) -> Vec<CompletionItem> { | 23 | ) -> Vec<CompletionItem> { |
24 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
25 | .into_iter() | ||
26 | .filter(|c| c.completion_kind == kind) | ||
27 | .collect(); | ||
28 | kind_completions.sort_by(|l, r| l.label().cmp(r.label())); | ||
29 | kind_completions | ||
30 | } | ||
31 | |||
32 | fn get_all_completion_items(code: &str, options: &CompletionConfig) -> Vec<CompletionItem> { | ||
18 | let (analysis, position) = if code.contains("//-") { | 33 | let (analysis, position) = if code.contains("//-") { |
19 | analysis_and_position(code) | 34 | analysis_and_position(code) |
20 | } else { | 35 | } else { |
21 | single_file_with_position(code) | 36 | single_file_with_position(code) |
22 | }; | 37 | }; |
23 | let completions = analysis.completions(options, position).unwrap().unwrap(); | 38 | analysis.completions(options, position).unwrap().unwrap().into() |
24 | let completion_items: Vec<CompletionItem> = completions.into(); | 39 | } |
25 | let mut kind_completions: Vec<CompletionItem> = | 40 | |
26 | completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); | 41 | pub(crate) fn completion_list_with_options( |
42 | code: &str, | ||
43 | kind: CompletionKind, | ||
44 | options: &CompletionConfig, | ||
45 | ) -> String { | ||
46 | let mut kind_completions: Vec<CompletionItem> = get_all_completion_items(code, options) | ||
47 | .into_iter() | ||
48 | .filter(|c| c.completion_kind == kind) | ||
49 | .collect(); | ||
27 | kind_completions.sort_by_key(|c| c.label().to_owned()); | 50 | kind_completions.sort_by_key(|c| c.label().to_owned()); |
28 | kind_completions | 51 | kind_completions |
52 | .into_iter() | ||
53 | .map(|it| format!("{} {}\n", it.kind().unwrap().tag(), it.label())) | ||
54 | .collect() | ||
55 | } | ||
56 | |||
57 | pub(crate) fn check_pattern_is_applicable(code: &str, check: fn(SyntaxElement) -> bool) { | ||
58 | let (analysis, pos) = single_file_with_position(code); | ||
59 | analysis | ||
60 | .with_db(|db| { | ||
61 | let sema = Semantics::new(db); | ||
62 | let original_file = sema.parse(pos.file_id); | ||
63 | let token = original_file.syntax().token_at_offset(pos.offset).left_biased().unwrap(); | ||
64 | assert!(check(NodeOrToken::Token(token))); | ||
65 | }) | ||
66 | .unwrap(); | ||
29 | } | 67 | } |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 365d52168..a88a978d7 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -11,7 +11,7 @@ use hir::{ | |||
11 | HasSource, HirDisplay, Semantics, VariantDef, | 11 | HasSource, HirDisplay, Semantics, VariantDef, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; | 14 | use ra_db::SourceDatabase; |
15 | use ra_ide_db::RootDatabase; | 15 | use ra_ide_db::RootDatabase; |
16 | use ra_prof::profile; | 16 | use ra_prof::profile; |
17 | use ra_syntax::{ | 17 | use 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(), |
@@ -683,10 +679,10 @@ mod tests { | |||
683 | source_file_edits: [], | 679 | source_file_edits: [], |
684 | file_system_edits: [ | 680 | file_system_edits: [ |
685 | CreateFile { | 681 | CreateFile { |
686 | source_root: SourceRootId( | 682 | anchor: FileId( |
687 | 0, | 683 | 1, |
688 | ), | 684 | ), |
689 | path: "foo.rs", | 685 | dst: "foo.rs", |
690 | }, | 686 | }, |
691 | ], | 687 | ], |
692 | is_snippet: false, | 688 | is_snippet: false, |
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index c7bb1e69f..0b52b01ab 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -135,8 +135,8 @@ impl NavigationTarget { | |||
135 | db: &RootDatabase, | 135 | db: &RootDatabase, |
136 | node: InFile<&dyn ast::NameOwner>, | 136 | node: InFile<&dyn ast::NameOwner>, |
137 | ) -> NavigationTarget { | 137 | ) -> NavigationTarget { |
138 | //FIXME: use `_` instead of empty string | 138 | let name = |
139 | let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); | 139 | node.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_")); |
140 | let focus_range = | 140 | let focus_range = |
141 | node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); | 141 | node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); |
142 | let frange = original_range(db, node.map(|it| it.syntax())); | 142 | let frange = original_range(db, node.map(|it| it.syntax())); |
@@ -150,6 +150,25 @@ impl NavigationTarget { | |||
150 | ) | 150 | ) |
151 | } | 151 | } |
152 | 152 | ||
153 | /// Allows `NavigationTarget` to be created from a `DocCommentsOwner` and a `NameOwner` | ||
154 | pub(crate) fn from_doc_commented( | ||
155 | db: &RootDatabase, | ||
156 | named: InFile<&dyn ast::NameOwner>, | ||
157 | node: InFile<&dyn ast::DocCommentsOwner>, | ||
158 | ) -> NavigationTarget { | ||
159 | let name = | ||
160 | named.value.name().map(|it| it.text().clone()).unwrap_or_else(|| SmolStr::new("_")); | ||
161 | let frange = original_range(db, node.map(|it| it.syntax())); | ||
162 | |||
163 | NavigationTarget::from_syntax( | ||
164 | frange.file_id, | ||
165 | name, | ||
166 | None, | ||
167 | frange.range, | ||
168 | node.value.syntax().kind(), | ||
169 | ) | ||
170 | } | ||
171 | |||
153 | fn from_syntax( | 172 | fn from_syntax( |
154 | file_id: FileId, | 173 | file_id: FileId, |
155 | name: SmolStr, | 174 | name: SmolStr, |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index ad78b7671..d870e4cbc 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -2,7 +2,7 @@ use std::iter::once; | |||
2 | 2 | ||
3 | use hir::{ | 3 | use hir::{ |
4 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, | 4 | Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, |
5 | ModuleDef, ModuleSource, Semantics, | 5 | Module, ModuleDef, ModuleSource, Semantics, |
6 | }; | 6 | }; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
@@ -13,7 +13,9 @@ use ra_ide_db::{ | |||
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
14 | 14 | ||
15 | use crate::{ | 15 | use crate::{ |
16 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, | 16 | display::{ |
17 | macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav, TryToNav, | ||
18 | }, | ||
17 | runnables::runnable, | 19 | runnables::runnable, |
18 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, | 20 | FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, |
19 | }; | 21 | }; |
@@ -24,19 +26,21 @@ pub struct HoverConfig { | |||
24 | pub implementations: bool, | 26 | pub implementations: bool, |
25 | pub run: bool, | 27 | pub run: bool, |
26 | pub debug: bool, | 28 | pub debug: bool, |
29 | pub goto_type_def: bool, | ||
27 | } | 30 | } |
28 | 31 | ||
29 | impl Default for HoverConfig { | 32 | impl Default for HoverConfig { |
30 | fn default() -> Self { | 33 | fn default() -> Self { |
31 | Self { implementations: true, run: true, debug: true } | 34 | Self { implementations: true, run: true, debug: true, goto_type_def: true } |
32 | } | 35 | } |
33 | } | 36 | } |
34 | 37 | ||
35 | impl HoverConfig { | 38 | impl HoverConfig { |
36 | pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; | 39 | pub const NO_ACTIONS: Self = |
40 | Self { implementations: false, run: false, debug: false, goto_type_def: false }; | ||
37 | 41 | ||
38 | pub fn any(&self) -> bool { | 42 | pub fn any(&self) -> bool { |
39 | self.implementations || self.runnable() | 43 | self.implementations || self.runnable() || self.goto_type_def |
40 | } | 44 | } |
41 | 45 | ||
42 | pub fn none(&self) -> bool { | 46 | pub fn none(&self) -> bool { |
@@ -52,6 +56,13 @@ impl HoverConfig { | |||
52 | pub enum HoverAction { | 56 | pub enum HoverAction { |
53 | Runnable(Runnable), | 57 | Runnable(Runnable), |
54 | Implementaion(FilePosition), | 58 | Implementaion(FilePosition), |
59 | GoToType(Vec<HoverGotoTypeData>), | ||
60 | } | ||
61 | |||
62 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
63 | pub struct HoverGotoTypeData { | ||
64 | pub mod_path: String, | ||
65 | pub nav: NavigationTarget, | ||
55 | } | 66 | } |
56 | 67 | ||
57 | /// Contains the results when hovering over an item | 68 | /// Contains the results when hovering over an item |
@@ -138,6 +149,10 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn | |||
138 | res.push_action(action); | 149 | res.push_action(action); |
139 | } | 150 | } |
140 | 151 | ||
152 | if let Some(action) = goto_type_action(db, name_kind) { | ||
153 | res.push_action(action); | ||
154 | } | ||
155 | |||
141 | return Some(RangeInfo::new(range, res)); | 156 | return Some(RangeInfo::new(range, res)); |
142 | } | 157 | } |
143 | } | 158 | } |
@@ -218,6 +233,44 @@ fn runnable_action( | |||
218 | } | 233 | } |
219 | } | 234 | } |
220 | 235 | ||
236 | fn goto_type_action(db: &RootDatabase, def: Definition) -> Option<HoverAction> { | ||
237 | match def { | ||
238 | Definition::Local(it) => { | ||
239 | let mut targets: Vec<ModuleDef> = Vec::new(); | ||
240 | let mut push_new_def = |item: ModuleDef| { | ||
241 | if !targets.contains(&item) { | ||
242 | targets.push(item); | ||
243 | } | ||
244 | }; | ||
245 | |||
246 | it.ty(db).walk(db, |t| { | ||
247 | if let Some(adt) = t.as_adt() { | ||
248 | push_new_def(adt.into()); | ||
249 | } else if let Some(trait_) = t.as_dyn_trait() { | ||
250 | push_new_def(trait_.into()); | ||
251 | } else if let Some(traits) = t.as_impl_traits(db) { | ||
252 | traits.into_iter().for_each(|it| push_new_def(it.into())); | ||
253 | } else if let Some(trait_) = t.as_associated_type_parent_trait(db) { | ||
254 | push_new_def(trait_.into()); | ||
255 | } | ||
256 | }); | ||
257 | |||
258 | let targets = targets | ||
259 | .into_iter() | ||
260 | .filter_map(|it| { | ||
261 | Some(HoverGotoTypeData { | ||
262 | mod_path: mod_path(db, &it)?, | ||
263 | nav: it.try_to_nav(db)?, | ||
264 | }) | ||
265 | }) | ||
266 | .collect(); | ||
267 | |||
268 | Some(HoverAction::GoToType(targets)) | ||
269 | } | ||
270 | _ => None, | ||
271 | } | ||
272 | } | ||
273 | |||
221 | fn hover_text( | 274 | fn hover_text( |
222 | docs: Option<String>, | 275 | docs: Option<String>, |
223 | desc: Option<String>, | 276 | desc: Option<String>, |
@@ -248,25 +301,31 @@ fn definition_owner_name(db: &RootDatabase, def: &Definition) -> Option<String> | |||
248 | .map(|name| name.to_string()) | 301 | .map(|name| name.to_string()) |
249 | } | 302 | } |
250 | 303 | ||
251 | fn determine_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | 304 | fn determine_mod_path(db: &RootDatabase, module: Module, name: Option<String>) -> String { |
252 | let mod_path = def.module(db).map(|module| { | 305 | once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) |
253 | once(db.crate_graph()[module.krate().into()].display_name.as_ref().map(ToString::to_string)) | 306 | .chain( |
254 | .chain( | 307 | module |
255 | module | 308 | .path_to_root(db) |
256 | .path_to_root(db) | 309 | .into_iter() |
257 | .into_iter() | 310 | .rev() |
258 | .rev() | 311 | .map(|it| it.name(db).map(|name| name.to_string())), |
259 | .map(|it| it.name(db).map(|name| name.to_string())), | 312 | ) |
260 | ) | 313 | .chain(once(name)) |
261 | .chain(once(definition_owner_name(db, def))) | 314 | .flatten() |
262 | .flatten() | 315 | .join("::") |
263 | .join("::") | 316 | } |
264 | }); | 317 | |
265 | mod_path | 318 | // returns None only for ModuleDef::BuiltinType |
319 | fn mod_path(db: &RootDatabase, item: &ModuleDef) -> Option<String> { | ||
320 | Some(determine_mod_path(db, item.module(db)?, item.name(db).map(|name| name.to_string()))) | ||
321 | } | ||
322 | |||
323 | fn definition_mod_path(db: &RootDatabase, def: &Definition) -> Option<String> { | ||
324 | def.module(db).map(|module| determine_mod_path(db, module, definition_owner_name(db, def))) | ||
266 | } | 325 | } |
267 | 326 | ||
268 | fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { | 327 | fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<String> { |
269 | let mod_path = determine_mod_path(db, &def); | 328 | let mod_path = definition_mod_path(db, &def); |
270 | return match def { | 329 | return match def { |
271 | Definition::Macro(it) => { | 330 | Definition::Macro(it) => { |
272 | let src = it.source(db); | 331 | let src = it.source(db); |
@@ -1310,4 +1369,1045 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1310 | ] | 1369 | ] |
1311 | "###); | 1370 | "###); |
1312 | } | 1371 | } |
1372 | |||
1373 | #[test] | ||
1374 | fn test_hover_struct_has_goto_type_action() { | ||
1375 | let (_, actions) = check_hover_result( | ||
1376 | " | ||
1377 | //- /main.rs | ||
1378 | struct S{ f1: u32 } | ||
1379 | |||
1380 | fn main() { | ||
1381 | let s<|>t = S{ f1:0 }; | ||
1382 | } | ||
1383 | ", | ||
1384 | &["S"], | ||
1385 | ); | ||
1386 | assert_debug_snapshot!(actions, | ||
1387 | @r###" | ||
1388 | [ | ||
1389 | GoToType( | ||
1390 | [ | ||
1391 | HoverGotoTypeData { | ||
1392 | mod_path: "S", | ||
1393 | nav: NavigationTarget { | ||
1394 | file_id: FileId( | ||
1395 | 1, | ||
1396 | ), | ||
1397 | full_range: 0..19, | ||
1398 | name: "S", | ||
1399 | kind: STRUCT_DEF, | ||
1400 | focus_range: Some( | ||
1401 | 7..8, | ||
1402 | ), | ||
1403 | container_name: None, | ||
1404 | description: Some( | ||
1405 | "struct S", | ||
1406 | ), | ||
1407 | docs: None, | ||
1408 | }, | ||
1409 | }, | ||
1410 | ], | ||
1411 | ), | ||
1412 | ] | ||
1413 | "###); | ||
1414 | } | ||
1415 | |||
1416 | #[test] | ||
1417 | fn test_hover_generic_struct_has_goto_type_actions() { | ||
1418 | let (_, actions) = check_hover_result( | ||
1419 | " | ||
1420 | //- /main.rs | ||
1421 | struct Arg(u32); | ||
1422 | struct S<T>{ f1: T } | ||
1423 | |||
1424 | fn main() { | ||
1425 | let s<|>t = S{ f1:Arg(0) }; | ||
1426 | } | ||
1427 | ", | ||
1428 | &["S<Arg>"], | ||
1429 | ); | ||
1430 | assert_debug_snapshot!(actions, | ||
1431 | @r###" | ||
1432 | [ | ||
1433 | GoToType( | ||
1434 | [ | ||
1435 | HoverGotoTypeData { | ||
1436 | mod_path: "S", | ||
1437 | nav: NavigationTarget { | ||
1438 | file_id: FileId( | ||
1439 | 1, | ||
1440 | ), | ||
1441 | full_range: 17..37, | ||
1442 | name: "S", | ||
1443 | kind: STRUCT_DEF, | ||
1444 | focus_range: Some( | ||
1445 | 24..25, | ||
1446 | ), | ||
1447 | container_name: None, | ||
1448 | description: Some( | ||
1449 | "struct S", | ||
1450 | ), | ||
1451 | docs: None, | ||
1452 | }, | ||
1453 | }, | ||
1454 | HoverGotoTypeData { | ||
1455 | mod_path: "Arg", | ||
1456 | nav: NavigationTarget { | ||
1457 | file_id: FileId( | ||
1458 | 1, | ||
1459 | ), | ||
1460 | full_range: 0..16, | ||
1461 | name: "Arg", | ||
1462 | kind: STRUCT_DEF, | ||
1463 | focus_range: Some( | ||
1464 | 7..10, | ||
1465 | ), | ||
1466 | container_name: None, | ||
1467 | description: Some( | ||
1468 | "struct Arg", | ||
1469 | ), | ||
1470 | docs: None, | ||
1471 | }, | ||
1472 | }, | ||
1473 | ], | ||
1474 | ), | ||
1475 | ] | ||
1476 | "###); | ||
1477 | } | ||
1478 | |||
1479 | #[test] | ||
1480 | fn test_hover_generic_struct_has_flattened_goto_type_actions() { | ||
1481 | let (_, actions) = check_hover_result( | ||
1482 | " | ||
1483 | //- /main.rs | ||
1484 | struct Arg(u32); | ||
1485 | struct S<T>{ f1: T } | ||
1486 | |||
1487 | fn main() { | ||
1488 | let s<|>t = S{ f1: S{ f1: Arg(0) } }; | ||
1489 | } | ||
1490 | ", | ||
1491 | &["S<S<Arg>>"], | ||
1492 | ); | ||
1493 | assert_debug_snapshot!(actions, | ||
1494 | @r###" | ||
1495 | [ | ||
1496 | GoToType( | ||
1497 | [ | ||
1498 | HoverGotoTypeData { | ||
1499 | mod_path: "S", | ||
1500 | nav: NavigationTarget { | ||
1501 | file_id: FileId( | ||
1502 | 1, | ||
1503 | ), | ||
1504 | full_range: 17..37, | ||
1505 | name: "S", | ||
1506 | kind: STRUCT_DEF, | ||
1507 | focus_range: Some( | ||
1508 | 24..25, | ||
1509 | ), | ||
1510 | container_name: None, | ||
1511 | description: Some( | ||
1512 | "struct S", | ||
1513 | ), | ||
1514 | docs: None, | ||
1515 | }, | ||
1516 | }, | ||
1517 | HoverGotoTypeData { | ||
1518 | mod_path: "Arg", | ||
1519 | nav: NavigationTarget { | ||
1520 | file_id: FileId( | ||
1521 | 1, | ||
1522 | ), | ||
1523 | full_range: 0..16, | ||
1524 | name: "Arg", | ||
1525 | kind: STRUCT_DEF, | ||
1526 | focus_range: Some( | ||
1527 | 7..10, | ||
1528 | ), | ||
1529 | container_name: None, | ||
1530 | description: Some( | ||
1531 | "struct Arg", | ||
1532 | ), | ||
1533 | docs: None, | ||
1534 | }, | ||
1535 | }, | ||
1536 | ], | ||
1537 | ), | ||
1538 | ] | ||
1539 | "###); | ||
1540 | } | ||
1541 | |||
1542 | #[test] | ||
1543 | fn test_hover_tuple_has_goto_type_actions() { | ||
1544 | let (_, actions) = check_hover_result( | ||
1545 | " | ||
1546 | //- /main.rs | ||
1547 | struct A(u32); | ||
1548 | struct B(u32); | ||
1549 | mod M { | ||
1550 | pub struct C(u32); | ||
1551 | } | ||
1552 | |||
1553 | fn main() { | ||
1554 | let s<|>t = (A(1), B(2), M::C(3) ); | ||
1555 | } | ||
1556 | ", | ||
1557 | &["(A, B, C)"], | ||
1558 | ); | ||
1559 | assert_debug_snapshot!(actions, | ||
1560 | @r###" | ||
1561 | [ | ||
1562 | GoToType( | ||
1563 | [ | ||
1564 | HoverGotoTypeData { | ||
1565 | mod_path: "A", | ||
1566 | nav: NavigationTarget { | ||
1567 | file_id: FileId( | ||
1568 | 1, | ||
1569 | ), | ||
1570 | full_range: 0..14, | ||
1571 | name: "A", | ||
1572 | kind: STRUCT_DEF, | ||
1573 | focus_range: Some( | ||
1574 | 7..8, | ||
1575 | ), | ||
1576 | container_name: None, | ||
1577 | description: Some( | ||
1578 | "struct A", | ||
1579 | ), | ||
1580 | docs: None, | ||
1581 | }, | ||
1582 | }, | ||
1583 | HoverGotoTypeData { | ||
1584 | mod_path: "B", | ||
1585 | nav: NavigationTarget { | ||
1586 | file_id: FileId( | ||
1587 | 1, | ||
1588 | ), | ||
1589 | full_range: 15..29, | ||
1590 | name: "B", | ||
1591 | kind: STRUCT_DEF, | ||
1592 | focus_range: Some( | ||
1593 | 22..23, | ||
1594 | ), | ||
1595 | container_name: None, | ||
1596 | description: Some( | ||
1597 | "struct B", | ||
1598 | ), | ||
1599 | docs: None, | ||
1600 | }, | ||
1601 | }, | ||
1602 | HoverGotoTypeData { | ||
1603 | mod_path: "M::C", | ||
1604 | nav: NavigationTarget { | ||
1605 | file_id: FileId( | ||
1606 | 1, | ||
1607 | ), | ||
1608 | full_range: 42..60, | ||
1609 | name: "C", | ||
1610 | kind: STRUCT_DEF, | ||
1611 | focus_range: Some( | ||
1612 | 53..54, | ||
1613 | ), | ||
1614 | container_name: None, | ||
1615 | description: Some( | ||
1616 | "pub struct C", | ||
1617 | ), | ||
1618 | docs: None, | ||
1619 | }, | ||
1620 | }, | ||
1621 | ], | ||
1622 | ), | ||
1623 | ] | ||
1624 | "###); | ||
1625 | } | ||
1626 | |||
1627 | #[test] | ||
1628 | fn test_hover_return_impl_trait_has_goto_type_action() { | ||
1629 | let (_, actions) = check_hover_result( | ||
1630 | " | ||
1631 | //- /main.rs | ||
1632 | trait Foo {} | ||
1633 | |||
1634 | fn foo() -> impl Foo {} | ||
1635 | |||
1636 | fn main() { | ||
1637 | let s<|>t = foo(); | ||
1638 | } | ||
1639 | ", | ||
1640 | &["impl Foo"], | ||
1641 | ); | ||
1642 | assert_debug_snapshot!(actions, | ||
1643 | @r###" | ||
1644 | [ | ||
1645 | GoToType( | ||
1646 | [ | ||
1647 | HoverGotoTypeData { | ||
1648 | mod_path: "Foo", | ||
1649 | nav: NavigationTarget { | ||
1650 | file_id: FileId( | ||
1651 | 1, | ||
1652 | ), | ||
1653 | full_range: 0..12, | ||
1654 | name: "Foo", | ||
1655 | kind: TRAIT_DEF, | ||
1656 | focus_range: Some( | ||
1657 | 6..9, | ||
1658 | ), | ||
1659 | container_name: None, | ||
1660 | description: Some( | ||
1661 | "trait Foo", | ||
1662 | ), | ||
1663 | docs: None, | ||
1664 | }, | ||
1665 | }, | ||
1666 | ], | ||
1667 | ), | ||
1668 | ] | ||
1669 | "###); | ||
1670 | } | ||
1671 | |||
1672 | #[test] | ||
1673 | fn test_hover_generic_return_impl_trait_has_goto_type_action() { | ||
1674 | let (_, actions) = check_hover_result( | ||
1675 | " | ||
1676 | //- /main.rs | ||
1677 | trait Foo<T> {} | ||
1678 | struct S; | ||
1679 | |||
1680 | fn foo() -> impl Foo<S> {} | ||
1681 | |||
1682 | fn main() { | ||
1683 | let s<|>t = foo(); | ||
1684 | } | ||
1685 | ", | ||
1686 | &["impl Foo<S>"], | ||
1687 | ); | ||
1688 | assert_debug_snapshot!(actions, | ||
1689 | @r###" | ||
1690 | [ | ||
1691 | GoToType( | ||
1692 | [ | ||
1693 | HoverGotoTypeData { | ||
1694 | mod_path: "Foo", | ||
1695 | nav: NavigationTarget { | ||
1696 | file_id: FileId( | ||
1697 | 1, | ||
1698 | ), | ||
1699 | full_range: 0..15, | ||
1700 | name: "Foo", | ||
1701 | kind: TRAIT_DEF, | ||
1702 | focus_range: Some( | ||
1703 | 6..9, | ||
1704 | ), | ||
1705 | container_name: None, | ||
1706 | description: Some( | ||
1707 | "trait Foo", | ||
1708 | ), | ||
1709 | docs: None, | ||
1710 | }, | ||
1711 | }, | ||
1712 | HoverGotoTypeData { | ||
1713 | mod_path: "S", | ||
1714 | nav: NavigationTarget { | ||
1715 | file_id: FileId( | ||
1716 | 1, | ||
1717 | ), | ||
1718 | full_range: 16..25, | ||
1719 | name: "S", | ||
1720 | kind: STRUCT_DEF, | ||
1721 | focus_range: Some( | ||
1722 | 23..24, | ||
1723 | ), | ||
1724 | container_name: None, | ||
1725 | description: Some( | ||
1726 | "struct S", | ||
1727 | ), | ||
1728 | docs: None, | ||
1729 | }, | ||
1730 | }, | ||
1731 | ], | ||
1732 | ), | ||
1733 | ] | ||
1734 | "###); | ||
1735 | } | ||
1736 | |||
1737 | #[test] | ||
1738 | fn test_hover_return_impl_traits_has_goto_type_action() { | ||
1739 | let (_, actions) = check_hover_result( | ||
1740 | " | ||
1741 | //- /main.rs | ||
1742 | trait Foo {} | ||
1743 | trait Bar {} | ||
1744 | |||
1745 | fn foo() -> impl Foo + Bar {} | ||
1746 | |||
1747 | fn main() { | ||
1748 | let s<|>t = foo(); | ||
1749 | } | ||
1750 | ", | ||
1751 | &["impl Foo + Bar"], | ||
1752 | ); | ||
1753 | assert_debug_snapshot!(actions, | ||
1754 | @r###" | ||
1755 | [ | ||
1756 | GoToType( | ||
1757 | [ | ||
1758 | HoverGotoTypeData { | ||
1759 | mod_path: "Foo", | ||
1760 | nav: NavigationTarget { | ||
1761 | file_id: FileId( | ||
1762 | 1, | ||
1763 | ), | ||
1764 | full_range: 0..12, | ||
1765 | name: "Foo", | ||
1766 | kind: TRAIT_DEF, | ||
1767 | focus_range: Some( | ||
1768 | 6..9, | ||
1769 | ), | ||
1770 | container_name: None, | ||
1771 | description: Some( | ||
1772 | "trait Foo", | ||
1773 | ), | ||
1774 | docs: None, | ||
1775 | }, | ||
1776 | }, | ||
1777 | HoverGotoTypeData { | ||
1778 | mod_path: "Bar", | ||
1779 | nav: NavigationTarget { | ||
1780 | file_id: FileId( | ||
1781 | 1, | ||
1782 | ), | ||
1783 | full_range: 13..25, | ||
1784 | name: "Bar", | ||
1785 | kind: TRAIT_DEF, | ||
1786 | focus_range: Some( | ||
1787 | 19..22, | ||
1788 | ), | ||
1789 | container_name: None, | ||
1790 | description: Some( | ||
1791 | "trait Bar", | ||
1792 | ), | ||
1793 | docs: None, | ||
1794 | }, | ||
1795 | }, | ||
1796 | ], | ||
1797 | ), | ||
1798 | ] | ||
1799 | "###); | ||
1800 | } | ||
1801 | |||
1802 | #[test] | ||
1803 | fn test_hover_generic_return_impl_traits_has_goto_type_action() { | ||
1804 | let (_, actions) = check_hover_result( | ||
1805 | " | ||
1806 | //- /main.rs | ||
1807 | trait Foo<T> {} | ||
1808 | trait Bar<T> {} | ||
1809 | struct S1 {} | ||
1810 | struct S2 {} | ||
1811 | |||
1812 | fn foo() -> impl Foo<S1> + Bar<S2> {} | ||
1813 | |||
1814 | fn main() { | ||
1815 | let s<|>t = foo(); | ||
1816 | } | ||
1817 | ", | ||
1818 | &["impl Foo<S1> + Bar<S2>"], | ||
1819 | ); | ||
1820 | assert_debug_snapshot!(actions, | ||
1821 | @r###" | ||
1822 | [ | ||
1823 | GoToType( | ||
1824 | [ | ||
1825 | HoverGotoTypeData { | ||
1826 | mod_path: "Foo", | ||
1827 | nav: NavigationTarget { | ||
1828 | file_id: FileId( | ||
1829 | 1, | ||
1830 | ), | ||
1831 | full_range: 0..15, | ||
1832 | name: "Foo", | ||
1833 | kind: TRAIT_DEF, | ||
1834 | focus_range: Some( | ||
1835 | 6..9, | ||
1836 | ), | ||
1837 | container_name: None, | ||
1838 | description: Some( | ||
1839 | "trait Foo", | ||
1840 | ), | ||
1841 | docs: None, | ||
1842 | }, | ||
1843 | }, | ||
1844 | HoverGotoTypeData { | ||
1845 | mod_path: "Bar", | ||
1846 | nav: NavigationTarget { | ||
1847 | file_id: FileId( | ||
1848 | 1, | ||
1849 | ), | ||
1850 | full_range: 16..31, | ||
1851 | name: "Bar", | ||
1852 | kind: TRAIT_DEF, | ||
1853 | focus_range: Some( | ||
1854 | 22..25, | ||
1855 | ), | ||
1856 | container_name: None, | ||
1857 | description: Some( | ||
1858 | "trait Bar", | ||
1859 | ), | ||
1860 | docs: None, | ||
1861 | }, | ||
1862 | }, | ||
1863 | HoverGotoTypeData { | ||
1864 | mod_path: "S1", | ||
1865 | nav: NavigationTarget { | ||
1866 | file_id: FileId( | ||
1867 | 1, | ||
1868 | ), | ||
1869 | full_range: 32..44, | ||
1870 | name: "S1", | ||
1871 | kind: STRUCT_DEF, | ||
1872 | focus_range: Some( | ||
1873 | 39..41, | ||
1874 | ), | ||
1875 | container_name: None, | ||
1876 | description: Some( | ||
1877 | "struct S1", | ||
1878 | ), | ||
1879 | docs: None, | ||
1880 | }, | ||
1881 | }, | ||
1882 | HoverGotoTypeData { | ||
1883 | mod_path: "S2", | ||
1884 | nav: NavigationTarget { | ||
1885 | file_id: FileId( | ||
1886 | 1, | ||
1887 | ), | ||
1888 | full_range: 45..57, | ||
1889 | name: "S2", | ||
1890 | kind: STRUCT_DEF, | ||
1891 | focus_range: Some( | ||
1892 | 52..54, | ||
1893 | ), | ||
1894 | container_name: None, | ||
1895 | description: Some( | ||
1896 | "struct S2", | ||
1897 | ), | ||
1898 | docs: None, | ||
1899 | }, | ||
1900 | }, | ||
1901 | ], | ||
1902 | ), | ||
1903 | ] | ||
1904 | "###); | ||
1905 | } | ||
1906 | |||
1907 | #[test] | ||
1908 | fn test_hover_arg_impl_trait_has_goto_type_action() { | ||
1909 | let (_, actions) = check_hover_result( | ||
1910 | " | ||
1911 | //- /lib.rs | ||
1912 | trait Foo {} | ||
1913 | fn foo(ar<|>g: &impl Foo) {} | ||
1914 | ", | ||
1915 | &["&impl Foo"], | ||
1916 | ); | ||
1917 | assert_debug_snapshot!(actions, | ||
1918 | @r###" | ||
1919 | [ | ||
1920 | GoToType( | ||
1921 | [ | ||
1922 | HoverGotoTypeData { | ||
1923 | mod_path: "Foo", | ||
1924 | nav: NavigationTarget { | ||
1925 | file_id: FileId( | ||
1926 | 1, | ||
1927 | ), | ||
1928 | full_range: 0..12, | ||
1929 | name: "Foo", | ||
1930 | kind: TRAIT_DEF, | ||
1931 | focus_range: Some( | ||
1932 | 6..9, | ||
1933 | ), | ||
1934 | container_name: None, | ||
1935 | description: Some( | ||
1936 | "trait Foo", | ||
1937 | ), | ||
1938 | docs: None, | ||
1939 | }, | ||
1940 | }, | ||
1941 | ], | ||
1942 | ), | ||
1943 | ] | ||
1944 | "###); | ||
1945 | } | ||
1946 | |||
1947 | #[test] | ||
1948 | fn test_hover_arg_impl_traits_has_goto_type_action() { | ||
1949 | let (_, actions) = check_hover_result( | ||
1950 | " | ||
1951 | //- /lib.rs | ||
1952 | trait Foo {} | ||
1953 | trait Bar<T> {} | ||
1954 | struct S{} | ||
1955 | |||
1956 | fn foo(ar<|>g: &impl Foo + Bar<S>) {} | ||
1957 | ", | ||
1958 | &["&impl Foo + Bar<S>"], | ||
1959 | ); | ||
1960 | assert_debug_snapshot!(actions, | ||
1961 | @r###" | ||
1962 | [ | ||
1963 | GoToType( | ||
1964 | [ | ||
1965 | HoverGotoTypeData { | ||
1966 | mod_path: "Foo", | ||
1967 | nav: NavigationTarget { | ||
1968 | file_id: FileId( | ||
1969 | 1, | ||
1970 | ), | ||
1971 | full_range: 0..12, | ||
1972 | name: "Foo", | ||
1973 | kind: TRAIT_DEF, | ||
1974 | focus_range: Some( | ||
1975 | 6..9, | ||
1976 | ), | ||
1977 | container_name: None, | ||
1978 | description: Some( | ||
1979 | "trait Foo", | ||
1980 | ), | ||
1981 | docs: None, | ||
1982 | }, | ||
1983 | }, | ||
1984 | HoverGotoTypeData { | ||
1985 | mod_path: "Bar", | ||
1986 | nav: NavigationTarget { | ||
1987 | file_id: FileId( | ||
1988 | 1, | ||
1989 | ), | ||
1990 | full_range: 13..28, | ||
1991 | name: "Bar", | ||
1992 | kind: TRAIT_DEF, | ||
1993 | focus_range: Some( | ||
1994 | 19..22, | ||
1995 | ), | ||
1996 | container_name: None, | ||
1997 | description: Some( | ||
1998 | "trait Bar", | ||
1999 | ), | ||
2000 | docs: None, | ||
2001 | }, | ||
2002 | }, | ||
2003 | HoverGotoTypeData { | ||
2004 | mod_path: "S", | ||
2005 | nav: NavigationTarget { | ||
2006 | file_id: FileId( | ||
2007 | 1, | ||
2008 | ), | ||
2009 | full_range: 29..39, | ||
2010 | name: "S", | ||
2011 | kind: STRUCT_DEF, | ||
2012 | focus_range: Some( | ||
2013 | 36..37, | ||
2014 | ), | ||
2015 | container_name: None, | ||
2016 | description: Some( | ||
2017 | "struct S", | ||
2018 | ), | ||
2019 | docs: None, | ||
2020 | }, | ||
2021 | }, | ||
2022 | ], | ||
2023 | ), | ||
2024 | ] | ||
2025 | "###); | ||
2026 | } | ||
2027 | |||
2028 | #[test] | ||
2029 | fn test_hover_arg_generic_impl_trait_has_goto_type_action() { | ||
2030 | let (_, actions) = check_hover_result( | ||
2031 | " | ||
2032 | //- /lib.rs | ||
2033 | trait Foo<T> {} | ||
2034 | struct S {} | ||
2035 | fn foo(ar<|>g: &impl Foo<S>) {} | ||
2036 | ", | ||
2037 | &["&impl Foo<S>"], | ||
2038 | ); | ||
2039 | assert_debug_snapshot!(actions, | ||
2040 | @r###" | ||
2041 | [ | ||
2042 | GoToType( | ||
2043 | [ | ||
2044 | HoverGotoTypeData { | ||
2045 | mod_path: "Foo", | ||
2046 | nav: NavigationTarget { | ||
2047 | file_id: FileId( | ||
2048 | 1, | ||
2049 | ), | ||
2050 | full_range: 0..15, | ||
2051 | name: "Foo", | ||
2052 | kind: TRAIT_DEF, | ||
2053 | focus_range: Some( | ||
2054 | 6..9, | ||
2055 | ), | ||
2056 | container_name: None, | ||
2057 | description: Some( | ||
2058 | "trait Foo", | ||
2059 | ), | ||
2060 | docs: None, | ||
2061 | }, | ||
2062 | }, | ||
2063 | HoverGotoTypeData { | ||
2064 | mod_path: "S", | ||
2065 | nav: NavigationTarget { | ||
2066 | file_id: FileId( | ||
2067 | 1, | ||
2068 | ), | ||
2069 | full_range: 16..27, | ||
2070 | name: "S", | ||
2071 | kind: STRUCT_DEF, | ||
2072 | focus_range: Some( | ||
2073 | 23..24, | ||
2074 | ), | ||
2075 | container_name: None, | ||
2076 | description: Some( | ||
2077 | "struct S", | ||
2078 | ), | ||
2079 | docs: None, | ||
2080 | }, | ||
2081 | }, | ||
2082 | ], | ||
2083 | ), | ||
2084 | ] | ||
2085 | "###); | ||
2086 | } | ||
2087 | |||
2088 | #[test] | ||
2089 | fn test_hover_dyn_return_has_goto_type_action() { | ||
2090 | let (_, actions) = check_hover_result( | ||
2091 | " | ||
2092 | //- /main.rs | ||
2093 | trait Foo {} | ||
2094 | struct S; | ||
2095 | impl Foo for S {} | ||
2096 | |||
2097 | struct B<T>{} | ||
2098 | |||
2099 | fn foo() -> B<dyn Foo> {} | ||
2100 | |||
2101 | fn main() { | ||
2102 | let s<|>t = foo(); | ||
2103 | } | ||
2104 | ", | ||
2105 | &["B<dyn Foo>"], | ||
2106 | ); | ||
2107 | assert_debug_snapshot!(actions, | ||
2108 | @r###" | ||
2109 | [ | ||
2110 | GoToType( | ||
2111 | [ | ||
2112 | HoverGotoTypeData { | ||
2113 | mod_path: "B", | ||
2114 | nav: NavigationTarget { | ||
2115 | file_id: FileId( | ||
2116 | 1, | ||
2117 | ), | ||
2118 | full_range: 41..54, | ||
2119 | name: "B", | ||
2120 | kind: STRUCT_DEF, | ||
2121 | focus_range: Some( | ||
2122 | 48..49, | ||
2123 | ), | ||
2124 | container_name: None, | ||
2125 | description: Some( | ||
2126 | "struct B", | ||
2127 | ), | ||
2128 | docs: None, | ||
2129 | }, | ||
2130 | }, | ||
2131 | HoverGotoTypeData { | ||
2132 | mod_path: "Foo", | ||
2133 | nav: NavigationTarget { | ||
2134 | file_id: FileId( | ||
2135 | 1, | ||
2136 | ), | ||
2137 | full_range: 0..12, | ||
2138 | name: "Foo", | ||
2139 | kind: TRAIT_DEF, | ||
2140 | focus_range: Some( | ||
2141 | 6..9, | ||
2142 | ), | ||
2143 | container_name: None, | ||
2144 | description: Some( | ||
2145 | "trait Foo", | ||
2146 | ), | ||
2147 | docs: None, | ||
2148 | }, | ||
2149 | }, | ||
2150 | ], | ||
2151 | ), | ||
2152 | ] | ||
2153 | "###); | ||
2154 | } | ||
2155 | |||
2156 | #[test] | ||
2157 | fn test_hover_dyn_arg_has_goto_type_action() { | ||
2158 | let (_, actions) = check_hover_result( | ||
2159 | " | ||
2160 | //- /lib.rs | ||
2161 | trait Foo {} | ||
2162 | fn foo(ar<|>g: &dyn Foo) {} | ||
2163 | ", | ||
2164 | &["&dyn Foo"], | ||
2165 | ); | ||
2166 | assert_debug_snapshot!(actions, | ||
2167 | @r###" | ||
2168 | [ | ||
2169 | GoToType( | ||
2170 | [ | ||
2171 | HoverGotoTypeData { | ||
2172 | mod_path: "Foo", | ||
2173 | nav: NavigationTarget { | ||
2174 | file_id: FileId( | ||
2175 | 1, | ||
2176 | ), | ||
2177 | full_range: 0..12, | ||
2178 | name: "Foo", | ||
2179 | kind: TRAIT_DEF, | ||
2180 | focus_range: Some( | ||
2181 | 6..9, | ||
2182 | ), | ||
2183 | container_name: None, | ||
2184 | description: Some( | ||
2185 | "trait Foo", | ||
2186 | ), | ||
2187 | docs: None, | ||
2188 | }, | ||
2189 | }, | ||
2190 | ], | ||
2191 | ), | ||
2192 | ] | ||
2193 | "###); | ||
2194 | } | ||
2195 | |||
2196 | #[test] | ||
2197 | fn test_hover_generic_dyn_arg_has_goto_type_action() { | ||
2198 | let (_, actions) = check_hover_result( | ||
2199 | " | ||
2200 | //- /lib.rs | ||
2201 | trait Foo<T> {} | ||
2202 | struct S {} | ||
2203 | fn foo(ar<|>g: &dyn Foo<S>) {} | ||
2204 | ", | ||
2205 | &["&dyn Foo<S>"], | ||
2206 | ); | ||
2207 | assert_debug_snapshot!(actions, | ||
2208 | @r###" | ||
2209 | [ | ||
2210 | GoToType( | ||
2211 | [ | ||
2212 | HoverGotoTypeData { | ||
2213 | mod_path: "Foo", | ||
2214 | nav: NavigationTarget { | ||
2215 | file_id: FileId( | ||
2216 | 1, | ||
2217 | ), | ||
2218 | full_range: 0..15, | ||
2219 | name: "Foo", | ||
2220 | kind: TRAIT_DEF, | ||
2221 | focus_range: Some( | ||
2222 | 6..9, | ||
2223 | ), | ||
2224 | container_name: None, | ||
2225 | description: Some( | ||
2226 | "trait Foo", | ||
2227 | ), | ||
2228 | docs: None, | ||
2229 | }, | ||
2230 | }, | ||
2231 | HoverGotoTypeData { | ||
2232 | mod_path: "S", | ||
2233 | nav: NavigationTarget { | ||
2234 | file_id: FileId( | ||
2235 | 1, | ||
2236 | ), | ||
2237 | full_range: 16..27, | ||
2238 | name: "S", | ||
2239 | kind: STRUCT_DEF, | ||
2240 | focus_range: Some( | ||
2241 | 23..24, | ||
2242 | ), | ||
2243 | container_name: None, | ||
2244 | description: Some( | ||
2245 | "struct S", | ||
2246 | ), | ||
2247 | docs: None, | ||
2248 | }, | ||
2249 | }, | ||
2250 | ], | ||
2251 | ), | ||
2252 | ] | ||
2253 | "###); | ||
2254 | } | ||
2255 | |||
2256 | #[test] | ||
2257 | fn test_hover_goto_type_action_links_order() { | ||
2258 | let (_, actions) = check_hover_result( | ||
2259 | " | ||
2260 | //- /lib.rs | ||
2261 | trait ImplTrait<T> {} | ||
2262 | trait DynTrait<T> {} | ||
2263 | struct B<T> {} | ||
2264 | struct S {} | ||
2265 | |||
2266 | fn foo(a<|>rg: &impl ImplTrait<B<dyn DynTrait<B<S>>>>) {} | ||
2267 | ", | ||
2268 | &["&impl ImplTrait<B<dyn DynTrait<B<S>>>>"], | ||
2269 | ); | ||
2270 | assert_debug_snapshot!(actions, | ||
2271 | @r###" | ||
2272 | [ | ||
2273 | GoToType( | ||
2274 | [ | ||
2275 | HoverGotoTypeData { | ||
2276 | mod_path: "ImplTrait", | ||
2277 | nav: NavigationTarget { | ||
2278 | file_id: FileId( | ||
2279 | 1, | ||
2280 | ), | ||
2281 | full_range: 0..21, | ||
2282 | name: "ImplTrait", | ||
2283 | kind: TRAIT_DEF, | ||
2284 | focus_range: Some( | ||
2285 | 6..15, | ||
2286 | ), | ||
2287 | container_name: None, | ||
2288 | description: Some( | ||
2289 | "trait ImplTrait", | ||
2290 | ), | ||
2291 | docs: None, | ||
2292 | }, | ||
2293 | }, | ||
2294 | HoverGotoTypeData { | ||
2295 | mod_path: "B", | ||
2296 | nav: NavigationTarget { | ||
2297 | file_id: FileId( | ||
2298 | 1, | ||
2299 | ), | ||
2300 | full_range: 43..57, | ||
2301 | name: "B", | ||
2302 | kind: STRUCT_DEF, | ||
2303 | focus_range: Some( | ||
2304 | 50..51, | ||
2305 | ), | ||
2306 | container_name: None, | ||
2307 | description: Some( | ||
2308 | "struct B", | ||
2309 | ), | ||
2310 | docs: None, | ||
2311 | }, | ||
2312 | }, | ||
2313 | HoverGotoTypeData { | ||
2314 | mod_path: "DynTrait", | ||
2315 | nav: NavigationTarget { | ||
2316 | file_id: FileId( | ||
2317 | 1, | ||
2318 | ), | ||
2319 | full_range: 22..42, | ||
2320 | name: "DynTrait", | ||
2321 | kind: TRAIT_DEF, | ||
2322 | focus_range: Some( | ||
2323 | 28..36, | ||
2324 | ), | ||
2325 | container_name: None, | ||
2326 | description: Some( | ||
2327 | "trait DynTrait", | ||
2328 | ), | ||
2329 | docs: None, | ||
2330 | }, | ||
2331 | }, | ||
2332 | HoverGotoTypeData { | ||
2333 | mod_path: "S", | ||
2334 | nav: NavigationTarget { | ||
2335 | file_id: FileId( | ||
2336 | 1, | ||
2337 | ), | ||
2338 | full_range: 58..69, | ||
2339 | name: "S", | ||
2340 | kind: STRUCT_DEF, | ||
2341 | focus_range: Some( | ||
2342 | 65..66, | ||
2343 | ), | ||
2344 | container_name: None, | ||
2345 | description: Some( | ||
2346 | "struct S", | ||
2347 | ), | ||
2348 | docs: None, | ||
2349 | }, | ||
2350 | }, | ||
2351 | ], | ||
2352 | ), | ||
2353 | ] | ||
2354 | "###); | ||
2355 | } | ||
2356 | |||
2357 | #[test] | ||
2358 | fn test_hover_associated_type_has_goto_type_action() { | ||
2359 | let (_, actions) = check_hover_result( | ||
2360 | " | ||
2361 | //- /main.rs | ||
2362 | trait Foo { | ||
2363 | type Item; | ||
2364 | fn get(self) -> Self::Item {} | ||
2365 | } | ||
2366 | |||
2367 | struct Bar{} | ||
2368 | struct S{} | ||
2369 | |||
2370 | impl Foo for S{ | ||
2371 | type Item = Bar; | ||
2372 | } | ||
2373 | |||
2374 | fn test() -> impl Foo { | ||
2375 | S{} | ||
2376 | } | ||
2377 | |||
2378 | fn main() { | ||
2379 | let s<|>t = test().get(); | ||
2380 | } | ||
2381 | ", | ||
2382 | &["Foo::Item<impl Foo>"], | ||
2383 | ); | ||
2384 | assert_debug_snapshot!(actions, | ||
2385 | @r###" | ||
2386 | [ | ||
2387 | GoToType( | ||
2388 | [ | ||
2389 | HoverGotoTypeData { | ||
2390 | mod_path: "Foo", | ||
2391 | nav: NavigationTarget { | ||
2392 | file_id: FileId( | ||
2393 | 1, | ||
2394 | ), | ||
2395 | full_range: 0..62, | ||
2396 | name: "Foo", | ||
2397 | kind: TRAIT_DEF, | ||
2398 | focus_range: Some( | ||
2399 | 6..9, | ||
2400 | ), | ||
2401 | container_name: None, | ||
2402 | description: Some( | ||
2403 | "trait Foo", | ||
2404 | ), | ||
2405 | docs: None, | ||
2406 | }, | ||
2407 | }, | ||
2408 | ], | ||
2409 | ), | ||
2410 | ] | ||
2411 | "###); | ||
2412 | } | ||
1313 | } | 2413 | } |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 28f686767..be9ab62c0 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -66,7 +66,7 @@ pub use crate::{ | |||
66 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, | 66 | display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, |
67 | expand_macro::ExpandedMacro, | 67 | expand_macro::ExpandedMacro, |
68 | folding_ranges::{Fold, FoldKind}, | 68 | folding_ranges::{Fold, FoldKind}, |
69 | hover::{HoverAction, HoverConfig, HoverResult}, | 69 | hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, |
70 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, | 70 | inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, |
71 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, | 71 | references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, |
72 | runnables::{Runnable, RunnableKind, TestId}, | 72 | runnables::{Runnable, RunnableKind, TestId}, |
@@ -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 | }; |
84 | pub use ra_ide_db::{ | 84 | pub 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 | ||
8 | pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { | 8 | pub(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 | ||
3 | use hir::{ModuleSource, Semantics}; | 3 | use hir::{ModuleSource, Semantics}; |
4 | use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; | 4 | use ra_db::{RelativePathBuf, SourceDatabaseExt}; |
5 | use ra_ide_db::RootDatabase; | 5 | use ra_ide_db::RootDatabase; |
6 | use ra_syntax::{ | 6 | use 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; | 619 | mod bar; |
629 | 620 | ||
630 | //- /bar.rs | 621 | //- /bar.rs |
631 | mod foo<|>; | 622 | mod 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; | 674 | mod 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; | 749 | mod bar; |
759 | fn f() { | 750 | fn f() { |
760 | bar::foo::fun() | 751 | bar::foo::fun() |
761 | } | 752 | } |
762 | 753 | ||
763 | //- /bar.rs | 754 | //- /bar.rs |
764 | pub mod foo<|>; | 755 | pub 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/runnables.rs b/crates/ra_ide/src/runnables.rs index fc57dc33d..8105ef373 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -171,7 +171,15 @@ fn runnable_fn( | |||
171 | let cfg_exprs = | 171 | let cfg_exprs = |
172 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); | 172 | attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); |
173 | 173 | ||
174 | let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); | 174 | let nav = if let RunnableKind::DocTest { .. } = kind { |
175 | NavigationTarget::from_doc_commented( | ||
176 | sema.db, | ||
177 | InFile::new(file_id.into(), &fn_def), | ||
178 | InFile::new(file_id.into(), &fn_def), | ||
179 | ) | ||
180 | } else { | ||
181 | NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)) | ||
182 | }; | ||
175 | Some(Runnable { nav, kind, cfg_exprs }) | 183 | Some(Runnable { nav, kind, cfg_exprs }) |
176 | } | 184 | } |
177 | 185 | ||
@@ -419,9 +427,7 @@ mod tests { | |||
419 | full_range: 22..64, | 427 | full_range: 22..64, |
420 | name: "foo", | 428 | name: "foo", |
421 | kind: FN_DEF, | 429 | kind: FN_DEF, |
422 | focus_range: Some( | 430 | focus_range: None, |
423 | 56..59, | ||
424 | ), | ||
425 | container_name: None, | 431 | container_name: None, |
426 | description: None, | 432 | description: None, |
427 | docs: None, | 433 | docs: None, |
@@ -486,9 +492,7 @@ mod tests { | |||
486 | full_range: 51..105, | 492 | full_range: 51..105, |
487 | name: "foo", | 493 | name: "foo", |
488 | kind: FN_DEF, | 494 | kind: FN_DEF, |
489 | focus_range: Some( | 495 | focus_range: None, |
490 | 97..100, | ||
491 | ), | ||
492 | container_name: None, | 496 | container_name: None, |
493 | description: None, | 497 | description: None, |
494 | docs: None, | 498 | docs: None, |
diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html index 0ae8c7efc..f61c0daa5 100644 --- a/crates/ra_ide/src/snapshots/highlight_doctest.html +++ b/crates/ra_ide/src/snapshots/highlight_doctest.html | |||
@@ -25,46 +25,62 @@ 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> { |
34 | <span class="comment">/// Constructs a new `Foo`.</span> | 36 | <span class="field declaration">bar</span>: <span class="builtin_type">bool</span>, |
35 | <span class="comment">///</span> | 37 | } |
36 | <span class="comment">/// # Examples</span> | 38 | |
37 | <span class="comment">///</span> | 39 | <span class="keyword">impl</span> <span class="struct">Foo</span> { |
38 | <span class="comment">/// ```</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>; |
39 | <span class="comment">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> | 41 | |
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>(); | 42 | <span class="comment documentation">/// Constructs a new `Foo`.</span> |
41 | <span class="comment">/// ```</span> | 43 | <span class="comment documentation">///</span> |
42 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="unresolved_reference">Foo</span> { | 44 | <span class="comment documentation">/// # Examples</span> |
43 | <span class="unresolved_reference">Foo</span> { } | 45 | <span class="comment documentation">///</span> |
46 | <span class="comment documentation">/// ```</span> | ||
47 | <span class="comment documentation">/// #</span> <span class="attribute">#![</span><span class="function attribute">allow</span><span class="attribute">(unused_mut)]</span> | ||
48 | <span class="comment documentation">/// </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>(); | ||
49 | <span class="comment documentation">/// ```</span> | ||
50 | <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration">new</span>() -> <span class="struct">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 documentation">/// `bar` method on `Foo`.</span> |
47 | <span class="comment">///</span> | 55 | <span class="comment documentation">///</span> |
48 | <span class="comment">/// # Examples</span> | 56 | <span class="comment documentation">/// # Examples</span> |
49 | <span class="comment">///</span> | 57 | <span class="comment documentation">///</span> |
50 | <span class="comment">/// ```</span> | 58 | <span class="comment documentation">/// ```</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 documentation">/// </span><span class="keyword">use</span> <span class="module">x</span>::<span class="module">y</span>; |
52 | <span class="comment">///</span> | 60 | <span class="comment documentation">///</span> |
53 | <span class="comment">/// </span><span class="comment">// calls bar on foo</span> | 61 | <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">foo</span> = <span class="struct">Foo</span>::<span class="function">new</span>(); |
54 | <span class="comment">/// </span><span class="macro">assert!</span>(foo.bar()); | 62 | <span class="comment documentation">///</span> |
55 | <span class="comment">///</span> | 63 | <span class="comment documentation">/// </span><span class="comment">// calls bar on foo</span> |
56 | <span class="comment">/// </span><span class="comment">/* multi-line | 64 | <span class="comment documentation">/// </span><span class="macro">assert!</span>(foo.bar()); |
57 | </span><span class="comment">/// </span><span class="comment"> comment */</span> | 65 | <span class="comment documentation">///</span> |
58 | <span class="comment">///</span> | 66 | <span class="comment documentation">/// </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>; |
59 | <span class="comment">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo | 67 | <span class="comment documentation">///</span> |
60 | </span><span class="comment">/// </span><span class="string_literal"> bar | 68 | <span class="comment documentation">/// </span><span class="comment">/* multi-line |
61 | </span><span class="comment">/// </span><span class="string_literal"> "</span>; | 69 | </span><span class="comment documentation">/// </span><span class="comment"> comment */</span> |
62 | <span class="comment">///</span> | 70 | <span class="comment documentation">///</span> |
63 | <span class="comment">/// ```</span> | 71 | <span class="comment documentation">/// </span><span class="keyword">let</span> <span class="variable declaration">multi_line_string</span> = <span class="string_literal">"Foo |
64 | <span class="comment">///</span> | 72 | </span><span class="comment documentation">/// </span><span class="string_literal"> bar |
65 | <span class="comment">/// ```</span> | 73 | </span><span class="comment documentation">/// </span><span class="string_literal"> "</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>(); | 74 | <span class="comment documentation">///</span> |
67 | <span class="comment">/// ```</span> | 75 | <span class="comment documentation">/// ```</span> |
76 | <span class="comment documentation">///</span> | ||
77 | <span class="comment documentation">/// ```rust,no_run</span> | ||
78 | <span class="comment documentation">/// </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>(); | ||
79 | <span class="comment documentation">/// ```</span> | ||
80 | <span class="comment documentation">///</span> | ||
81 | <span class="comment documentation">/// ```sh</span> | ||
82 | <span class="comment documentation">/// echo 1</span> | ||
83 | <span class="comment documentation">/// ```</span> | ||
68 | <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration">foo</span>(&<span class="self_keyword">self</span>) -> <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>) -> <span class="builtin_type">bool</span> { |
69 | <span class="bool_literal">true</span> | 85 | <span class="bool_literal">true</span> |
70 | } | 86 | } |
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) => { | ||
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)] |
84 | struct Var(String); | 84 | struct 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 | ||
169 | fn split_by_var(s: &str) -> Result<(&str, &str, &str), SsrError> { | 168 | fn 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 | ||
180 | fn is_name(s: &str) -> Result<(), SsrError> { | 175 | fn is_name(s: &str) -> Result<(), SsrError> { |
@@ -185,14 +180,6 @@ fn is_name(s: &str) -> Result<(), SsrError> { | |||
185 | } | 180 | } |
186 | } | 181 | } |
187 | 182 | ||
188 | fn 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 | |||
196 | fn replace_in_template(template: String, var: &str, new_var: &str) -> String { | 183 | fn 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}; | |||
16 | use ra_syntax::{ast, Parse, SyntaxNode}; | 16 | use ra_syntax::{ast, Parse, SyntaxNode}; |
17 | 17 | ||
18 | use crate::FileId; | 18 | use crate::FileId; |
19 | use rustc_hash::FxHashMap; | ||
19 | 20 | ||
20 | fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | 21 | fn 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 | ||
124 | impl fmt::Display for LibrarySymbolsStats { | 125 | impl 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 | ||
130 | impl FromIterator<TableEntry<SourceRootId, Arc<SymbolIndex>>> for LibrarySymbolsStats { | 131 | impl 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..f8f790e59 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> { | |||
408 | fn highlight_element( | 429 | fn 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,12 +483,20 @@ 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 | } |
467 | 490 | ||
468 | // Simple token-based highlighting | 491 | // Simple token-based highlighting |
469 | COMMENT => HighlightTag::Comment.into(), | 492 | COMMENT => { |
493 | let comment = element.into_token().and_then(ast::Comment::cast)?; | ||
494 | let h = HighlightTag::Comment; | ||
495 | match comment.kind().doc { | ||
496 | Some(_) => h | HighlightModifier::Documentation, | ||
497 | None => h.into(), | ||
498 | } | ||
499 | } | ||
470 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), | 500 | STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(), |
471 | ATTR => HighlightTag::Attribute.into(), | 501 | ATTR => HighlightTag::Attribute.into(), |
472 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), | 502 | INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(), |
@@ -493,6 +523,9 @@ fn highlight_element( | |||
493 | h |= HighlightModifier::Unsafe; | 523 | h |= HighlightModifier::Unsafe; |
494 | h | 524 | h |
495 | } | 525 | } |
526 | T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { | ||
527 | Highlight::new(HighlightTag::Macro) | ||
528 | } | ||
496 | 529 | ||
497 | k if k.is_keyword() => { | 530 | k if k.is_keyword() => { |
498 | let h = Highlight::new(HighlightTag::Keyword); | 531 | let h = Highlight::new(HighlightTag::Keyword); |
@@ -609,3 +642,53 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
609 | 642 | ||
610 | tag.into() | 643 | tag.into() |
611 | } | 644 | } |
645 | |||
646 | fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | ||
647 | let default = HighlightTag::UnresolvedReference; | ||
648 | |||
649 | let parent = match name.syntax().parent() { | ||
650 | Some(it) => it, | ||
651 | _ => return default.into(), | ||
652 | }; | ||
653 | |||
654 | let tag = match parent.kind() { | ||
655 | METHOD_CALL_EXPR => HighlightTag::Function, | ||
656 | FIELD_EXPR => HighlightTag::Field, | ||
657 | PATH_SEGMENT => { | ||
658 | let path = match parent.parent().and_then(ast::Path::cast) { | ||
659 | Some(it) => it, | ||
660 | _ => return default.into(), | ||
661 | }; | ||
662 | let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) { | ||
663 | Some(it) => it, | ||
664 | _ => { | ||
665 | // within path, decide whether it is module or adt by checking for uppercase name | ||
666 | return if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
667 | HighlightTag::Struct | ||
668 | } else { | ||
669 | HighlightTag::Module | ||
670 | } | ||
671 | .into(); | ||
672 | } | ||
673 | }; | ||
674 | let parent = match expr.syntax().parent() { | ||
675 | Some(it) => it, | ||
676 | None => return default.into(), | ||
677 | }; | ||
678 | |||
679 | match parent.kind() { | ||
680 | CALL_EXPR => HighlightTag::Function, | ||
681 | _ => { | ||
682 | if name.text().chars().next().unwrap_or_default().is_uppercase() { | ||
683 | HighlightTag::Struct | ||
684 | } else { | ||
685 | HighlightTag::Constant | ||
686 | } | ||
687 | } | ||
688 | } | ||
689 | } | ||
690 | _ => default, | ||
691 | }; | ||
692 | |||
693 | tag.into() | ||
694 | } | ||
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..415f24a6d 100644 --- a/crates/ra_ide/src/syntax_highlighting/injection.rs +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs | |||
@@ -7,7 +7,10 @@ use hir::Semantics; | |||
7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; | 7 | use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; |
8 | use stdx::SepBy; | 8 | use stdx::SepBy; |
9 | 9 | ||
10 | use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; | 10 | use crate::{ |
11 | call_info::ActiveParameter, Analysis, HighlightModifier, HighlightTag, HighlightedRange, | ||
12 | RootDatabase, | ||
13 | }; | ||
11 | 14 | ||
12 | use super::HighlightedRangeStack; | 15 | use super::HighlightedRangeStack; |
13 | 16 | ||
@@ -53,6 +56,10 @@ pub(super) fn highlight_injection( | |||
53 | /// Mapping from extracted documentation code to original code | 56 | /// Mapping from extracted documentation code to original code |
54 | type RangesMap = BTreeMap<TextSize, TextSize>; | 57 | type RangesMap = BTreeMap<TextSize, TextSize>; |
55 | 58 | ||
59 | const RUSTDOC_FENCE: &'static str = "```"; | ||
60 | const RUSTDOC_FENCE_TOKENS: &[&'static str] = | ||
61 | &["", "rust", "should_panic", "ignore", "no_run", "compile_fail", "edition2015", "edition2018"]; | ||
62 | |||
56 | /// Extracts Rust code from documentation comments as well as a mapping from | 63 | /// Extracts Rust code from documentation comments as well as a mapping from |
57 | /// the extracted source code back to the original source ranges. | 64 | /// the extracted source code back to the original source ranges. |
58 | /// Lastly, a vector of new comment highlight ranges (spanning only the | 65 | /// Lastly, a vector of new comment highlight ranges (spanning only the |
@@ -67,6 +74,7 @@ pub(super) fn extract_doc_comments( | |||
67 | // Mapping from extracted documentation code to original code | 74 | // Mapping from extracted documentation code to original code |
68 | let mut range_mapping: RangesMap = BTreeMap::new(); | 75 | let mut range_mapping: RangesMap = BTreeMap::new(); |
69 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); | 76 | let mut line_start = TextSize::try_from(prefix.len()).unwrap(); |
77 | let mut is_codeblock = false; | ||
70 | let mut is_doctest = false; | 78 | let mut is_doctest = false; |
71 | // Replace the original, line-spanning comment ranges by new, only comment-prefix | 79 | // Replace the original, line-spanning comment ranges by new, only comment-prefix |
72 | // spanning comment ranges. | 80 | // spanning comment ranges. |
@@ -76,8 +84,13 @@ pub(super) fn extract_doc_comments( | |||
76 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) | 84 | .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) |
77 | .filter(|comment| comment.kind().doc.is_some()) | 85 | .filter(|comment| comment.kind().doc.is_some()) |
78 | .filter(|comment| { | 86 | .filter(|comment| { |
79 | if comment.text().contains("```") { | 87 | if let Some(idx) = comment.text().find(RUSTDOC_FENCE) { |
80 | is_doctest = !is_doctest; | 88 | is_codeblock = !is_codeblock; |
89 | // Check whether code is rust by inspecting fence guards | ||
90 | let guards = &comment.text()[idx + RUSTDOC_FENCE.len()..]; | ||
91 | let is_rust = | ||
92 | guards.split(',').all(|sub| RUSTDOC_FENCE_TOKENS.contains(&sub.trim())); | ||
93 | is_doctest = is_codeblock && is_rust; | ||
81 | false | 94 | false |
82 | } else { | 95 | } else { |
83 | is_doctest | 96 | is_doctest |
@@ -108,7 +121,7 @@ pub(super) fn extract_doc_comments( | |||
108 | range.start(), | 121 | range.start(), |
109 | range.start() + TextSize::try_from(pos).unwrap(), | 122 | range.start() + TextSize::try_from(pos).unwrap(), |
110 | ), | 123 | ), |
111 | highlight: HighlightTag::Comment.into(), | 124 | highlight: HighlightTag::Comment | HighlightModifier::Documentation, |
112 | binding_hash: None, | 125 | binding_hash: None, |
113 | }); | 126 | }); |
114 | line_start += range.len() - TextSize::try_from(pos).unwrap(); | 127 | line_start += range.len() - TextSize::try_from(pos).unwrap(); |
@@ -137,7 +150,7 @@ pub(super) fn highlight_doc_comment( | |||
137 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); | 150 | let (analysis, tmp_file_id) = Analysis::from_single_file(text); |
138 | 151 | ||
139 | stack.push(); | 152 | stack.push(); |
140 | for mut h in analysis.highlight(tmp_file_id).unwrap() { | 153 | 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 | 154 | // Determine start offset and end offset in case of multi-line ranges |
142 | let mut start_offset = None; | 155 | let mut start_offset = None; |
143 | let mut end_offset = None; | 156 | let mut end_offset = None; |
@@ -154,6 +167,7 @@ pub(super) fn highlight_doc_comment( | |||
154 | h.range.start() + start_offset, | 167 | h.range.start() + start_offset, |
155 | h.range.end() + end_offset.unwrap_or(start_offset), | 168 | h.range.end() + end_offset.unwrap_or(start_offset), |
156 | ); | 169 | ); |
170 | |||
157 | stack.add(h); | 171 | stack.add(h); |
158 | } | 172 | } |
159 | } | 173 | } |
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index 94f466966..93bbb4b4d 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, |
@@ -55,6 +56,7 @@ pub enum HighlightModifier { | |||
55 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is | 56 | /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is |
56 | /// not. | 57 | /// not. |
57 | Definition, | 58 | Definition, |
59 | Documentation, | ||
58 | Mutable, | 60 | Mutable, |
59 | Unsafe, | 61 | Unsafe, |
60 | } | 62 | } |
@@ -71,6 +73,7 @@ impl HighlightTag { | |||
71 | HighlightTag::Constant => "constant", | 73 | HighlightTag::Constant => "constant", |
72 | HighlightTag::Enum => "enum", | 74 | HighlightTag::Enum => "enum", |
73 | HighlightTag::EnumVariant => "enum_variant", | 75 | HighlightTag::EnumVariant => "enum_variant", |
76 | HighlightTag::EscapeSequence => "escape_sequence", | ||
74 | HighlightTag::Field => "field", | 77 | HighlightTag::Field => "field", |
75 | HighlightTag::FormatSpecifier => "format_specifier", | 78 | HighlightTag::FormatSpecifier => "format_specifier", |
76 | HighlightTag::Function => "function", | 79 | HighlightTag::Function => "function", |
@@ -106,6 +109,7 @@ impl HighlightModifier { | |||
106 | HighlightModifier::Attribute, | 109 | HighlightModifier::Attribute, |
107 | HighlightModifier::ControlFlow, | 110 | HighlightModifier::ControlFlow, |
108 | HighlightModifier::Definition, | 111 | HighlightModifier::Definition, |
112 | HighlightModifier::Documentation, | ||
109 | HighlightModifier::Mutable, | 113 | HighlightModifier::Mutable, |
110 | HighlightModifier::Unsafe, | 114 | HighlightModifier::Unsafe, |
111 | ]; | 115 | ]; |
@@ -115,6 +119,7 @@ impl HighlightModifier { | |||
115 | HighlightModifier::Attribute => "attribute", | 119 | HighlightModifier::Attribute => "attribute", |
116 | HighlightModifier::ControlFlow => "control", | 120 | HighlightModifier::ControlFlow => "control", |
117 | HighlightModifier::Definition => "declaration", | 121 | HighlightModifier::Definition => "declaration", |
122 | HighlightModifier::Documentation => "documentation", | ||
118 | HighlightModifier::Mutable => "mutable", | 123 | HighlightModifier::Mutable => "mutable", |
119 | HighlightModifier::Unsafe => "unsafe", | 124 | HighlightModifier::Unsafe => "unsafe", |
120 | } | 125 | } |
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 | ||
46 | macro_rules! noop { | ||
47 | ($expr:expr) => { | ||
48 | $expr | ||
49 | } | ||
50 | } | ||
51 | |||
46 | // comment | 52 | // comment |
47 | fn main() { | 53 | fn 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() { | |||
279 | fn test_highlight_doctest() { | 291 | fn test_highlight_doctest() { |
280 | check_highlighting( | 292 | check_highlighting( |
281 | r#" | 293 | r#" |
294 | struct Foo { | ||
295 | bar: bool, | ||
296 | } | ||
297 | |||
282 | impl Foo { | 298 | impl 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 | }; |
11 | use ra_prof::{memory_usage, profile, Bytes}; | 11 | use ra_prof::{memory_usage, profile, Bytes}; |
12 | use ra_syntax::SourceFile; | ||
13 | #[cfg(not(feature = "wasm"))] | ||
14 | use rayon::prelude::*; | ||
15 | use rustc_hash::FxHashMap; | 12 | use rustc_hash::FxHashMap; |
16 | 13 | ||
17 | use crate::{ | 14 | use crate::{symbol_index::SymbolsDatabase, RootDatabase}; |
18 | symbol_index::{SymbolIndex, SymbolsDatabase}, | ||
19 | RootDatabase, | ||
20 | }; | ||
21 | 15 | ||
22 | #[derive(Default)] | 16 | #[derive(Default)] |
23 | pub struct AnalysisChange { | 17 | pub 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 | ||
119 | pub struct LibraryData { | ||
120 | root_id: SourceRootId, | ||
121 | root_change: RootChange, | ||
122 | symbol_index: SymbolIndex, | ||
123 | } | ||
124 | |||
125 | impl 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 | |||
135 | impl 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 | |||
160 | const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100); | 105 | const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100); |
161 | 106 | ||
162 | impl RootDatabase { | 107 | impl 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 | ||
6 | use ra_db::{FileId, RelativePathBuf, SourceRootId}; | 6 | use ra_db::FileId; |
7 | use ra_text_edit::TextEdit; | 7 | use 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)] |
46 | pub enum FileSystemEdit { | 46 | pub 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 | ||
51 | impl From<FileSystemEdit> for SourceChange { | 51 | impl 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 | }; |
37 | use ra_prof::profile; | ||
37 | use ra_syntax::{ | 38 | use 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"))] | ||
44 | use rayon::prelude::*; | 44 | use rayon::prelude::*; |
45 | use rustc_hash::FxHashMap; | ||
45 | 46 | ||
46 | use crate::RootDatabase; | 47 | use crate::RootDatabase; |
47 | 48 | ||
@@ -86,10 +87,9 @@ impl Query { | |||
86 | } | 87 | } |
87 | 88 | ||
88 | #[salsa::query_group(SymbolsDatabaseStorage)] | 89 | #[salsa::query_group(SymbolsDatabaseStorage)] |
89 | pub trait SymbolsDatabase: hir::db::HirDatabase { | 90 | pub 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 | ||
103 | fn 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 | |||
103 | fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex> { | 126 | fn 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` |
115 | struct Snap(salsa::Snapshot<RootDatabase>); | 138 | struct Snap<DB>(DB); |
116 | impl Clone for Snap { | 139 | impl<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 { | |||
143 | pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { | 166 | pub 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 | ||
321 | impl Query { | 315 | impl 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 | ||
52 | fn is_expr_stmt_attr_allowed(kind: SyntaxKind) -> bool { | 52 | fn 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 | ||
59 | pub(super) fn stmt(p: &mut Parser, with_semi: StmtWithSemi) { | 57 | pub(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 | |||
35 | impl 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 | }; |
57 | pub use ra_parser::{SyntaxKind, T}; | 58 | pub use ra_parser::{SyntaxKind, T}; |
diff --git a/crates/ra_syntax/src/parsing/text_token_source.rs b/crates/ra_syntax/src/parsing/text_token_source.rs index 7ddc2c2c3..97aa3e795 100644 --- a/crates/ra_syntax/src/parsing/text_token_source.rs +++ b/crates/ra_syntax/src/parsing/text_token_source.rs | |||
@@ -1,40 +1,35 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! See `TextTokenSource` docs. |
2 | 2 | ||
3 | use ra_parser::Token as PToken; | ||
4 | use ra_parser::TokenSource; | 3 | use ra_parser::TokenSource; |
5 | 4 | ||
6 | use crate::{parsing::lexer::Token, SyntaxKind::EOF, TextRange, TextSize}; | 5 | use crate::{parsing::lexer::Token, SyntaxKind::EOF, TextRange, TextSize}; |
7 | 6 | ||
7 | /// Implementation of `ra_parser::TokenSource` that takes tokens from source code text. | ||
8 | pub(crate) struct TextTokenSource<'t> { | 8 | pub(crate) struct TextTokenSource<'t> { |
9 | text: &'t str, | 9 | text: &'t str, |
10 | /// start position of each token(expect whitespace and comment) | 10 | /// token and its start position (non-whitespace/comment tokens) |
11 | /// ```non-rust | 11 | /// ```non-rust |
12 | /// struct Foo; | 12 | /// struct Foo; |
13 | /// ^------^--- | 13 | /// ^------^--^- |
14 | /// | | ^- | 14 | /// | | \________ |
15 | /// 0 7 10 | 15 | /// | \____ \ |
16 | /// | \ | | ||
17 | /// (struct, 0) (Foo, 7) (;, 10) | ||
16 | /// ``` | 18 | /// ``` |
17 | /// (token, start_offset): `[(struct, 0), (Foo, 7), (;, 10)]` | 19 | /// `[(struct, 0), (Foo, 7), (;, 10)]` |
18 | start_offsets: Vec<TextSize>, | 20 | token_offset_pairs: Vec<(Token, TextSize)>, |
19 | /// non-whitespace/comment tokens | ||
20 | /// ```non-rust | ||
21 | /// struct Foo {} | ||
22 | /// ^^^^^^ ^^^ ^^ | ||
23 | /// ``` | ||
24 | /// tokens: `[struct, Foo, {, }]` | ||
25 | tokens: Vec<Token>, | ||
26 | 21 | ||
27 | /// Current token and position | 22 | /// Current token and position |
28 | curr: (PToken, usize), | 23 | curr: (ra_parser::Token, usize), |
29 | } | 24 | } |
30 | 25 | ||
31 | impl<'t> TokenSource for TextTokenSource<'t> { | 26 | impl<'t> TokenSource for TextTokenSource<'t> { |
32 | fn current(&self) -> PToken { | 27 | fn current(&self) -> ra_parser::Token { |
33 | self.curr.0 | 28 | self.curr.0 |
34 | } | 29 | } |
35 | 30 | ||
36 | fn lookahead_nth(&self, n: usize) -> PToken { | 31 | fn lookahead_nth(&self, n: usize) -> ra_parser::Token { |
37 | mk_token(self.curr.1 + n, &self.start_offsets, &self.tokens) | 32 | mk_token(self.curr.1 + n, &self.token_offset_pairs) |
38 | } | 33 | } |
39 | 34 | ||
40 | fn bump(&mut self) { | 35 | fn bump(&mut self) { |
@@ -43,45 +38,47 @@ impl<'t> TokenSource for TextTokenSource<'t> { | |||
43 | } | 38 | } |
44 | 39 | ||
45 | let pos = self.curr.1 + 1; | 40 | let pos = self.curr.1 + 1; |
46 | self.curr = (mk_token(pos, &self.start_offsets, &self.tokens), pos); | 41 | self.curr = (mk_token(pos, &self.token_offset_pairs), pos); |
47 | } | 42 | } |
48 | 43 | ||
49 | fn is_keyword(&self, kw: &str) -> bool { | 44 | fn is_keyword(&self, kw: &str) -> bool { |
50 | let pos = self.curr.1; | 45 | self.token_offset_pairs |
51 | if pos >= self.tokens.len() { | 46 | .get(self.curr.1) |
52 | return false; | 47 | .map(|(token, offset)| &self.text[TextRange::at(*offset, token.len)] == kw) |
53 | } | 48 | .unwrap_or(false) |
54 | let range = TextRange::at(self.start_offsets[pos], self.tokens[pos].len); | ||
55 | self.text[range] == *kw | ||
56 | } | 49 | } |
57 | } | 50 | } |
58 | 51 | ||
59 | fn mk_token(pos: usize, start_offsets: &[TextSize], tokens: &[Token]) -> PToken { | 52 | fn mk_token(pos: usize, token_offset_pairs: &[(Token, TextSize)]) -> ra_parser::Token { |
60 | let kind = tokens.get(pos).map(|t| t.kind).unwrap_or(EOF); | 53 | let (kind, is_jointed_to_next) = match token_offset_pairs.get(pos) { |
61 | let is_jointed_to_next = if pos + 1 < start_offsets.len() { | 54 | Some((token, offset)) => ( |
62 | start_offsets[pos] + tokens[pos].len == start_offsets[pos + 1] | 55 | token.kind, |
63 | } else { | 56 | token_offset_pairs |
64 | false | 57 | .get(pos + 1) |
58 | .map(|(_, next_offset)| offset + token.len == *next_offset) | ||
59 | .unwrap_or(false), | ||
60 | ), | ||
61 | None => (EOF, false), | ||
65 | }; | 62 | }; |
66 | 63 | ra_parser::Token { kind, is_jointed_to_next } | |
67 | PToken { kind, is_jointed_to_next } | ||
68 | } | 64 | } |
69 | 65 | ||
70 | impl<'t> TextTokenSource<'t> { | 66 | impl<'t> TextTokenSource<'t> { |
71 | /// Generate input from tokens(expect comment and whitespace). | 67 | /// Generate input from tokens(expect comment and whitespace). |
72 | pub fn new(text: &'t str, raw_tokens: &'t [Token]) -> TextTokenSource<'t> { | 68 | pub fn new(text: &'t str, raw_tokens: &'t [Token]) -> TextTokenSource<'t> { |
73 | let mut tokens = Vec::new(); | 69 | let token_offset_pairs: Vec<_> = raw_tokens |
74 | let mut start_offsets = Vec::new(); | 70 | .iter() |
75 | let mut len = 0.into(); | 71 | .filter_map({ |
76 | for &token in raw_tokens.iter() { | 72 | let mut len = 0.into(); |
77 | if !token.kind.is_trivia() { | 73 | move |token| { |
78 | tokens.push(token); | 74 | let pair = if token.kind.is_trivia() { None } else { Some((*token, len)) }; |
79 | start_offsets.push(len); | 75 | len += token.len; |
80 | } | 76 | pair |
81 | len += token.len; | 77 | } |
82 | } | 78 | }) |
79 | .collect(); | ||
83 | 80 | ||
84 | let first = mk_token(0, &start_offsets, &tokens); | 81 | let first = mk_token(0, &token_offset_pairs); |
85 | TextTokenSource { text, start_offsets, tokens, curr: (first, 0) } | 82 | TextTokenSource { text, token_offset_pairs, curr: (first, 0) } |
86 | } | 83 | } |
87 | } | 84 | } |
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" |
58 | error 24..24: attributes are not allowed on BIN_EXPR | 58 | error 24..24: attributes are not allowed on BIN_EXPR |
59 | error 44..44: attributes are not allowed on IF_EXPR | ||
diff --git a/crates/rust-analyzer/build.rs b/crates/rust-analyzer/build.rs index d4b010c04..5ae76ba30 100644 --- a/crates/rust-analyzer/build.rs +++ b/crates/rust-analyzer/build.rs | |||
@@ -5,11 +5,14 @@ use std::{env, path::PathBuf, process::Command}; | |||
5 | fn main() { | 5 | fn main() { |
6 | set_rerun(); | 6 | set_rerun(); |
7 | 7 | ||
8 | let rev = rev().unwrap_or_else(|| "???????".to_string()); | 8 | let rev = |
9 | env::var("RUST_ANALYZER_REV").ok().or_else(rev).unwrap_or_else(|| "???????".to_string()); | ||
9 | println!("cargo:rustc-env=REV={}", rev) | 10 | println!("cargo:rustc-env=REV={}", rev) |
10 | } | 11 | } |
11 | 12 | ||
12 | fn set_rerun() { | 13 | fn set_rerun() { |
14 | println!("cargo:rerun-if-env-changed=RUST_ANALYZER_REV"); | ||
15 | |||
13 | let mut manifest_dir = PathBuf::from( | 16 | let mut manifest_dir = PathBuf::from( |
14 | env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` is always set by cargo."), | 17 | env::var("CARGO_MANIFEST_DIR").expect("`CARGO_MANIFEST_DIR` is always set by cargo."), |
15 | ); | 18 | ); |
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 | ||
3 | use ra_cfg::CfgExpr; | 3 | use ra_cfg::CfgExpr; |
4 | use ra_ide::{FileId, RunnableKind, TestId}; | 4 | use ra_ide::{FileId, RunnableKind, TestId}; |
5 | use ra_project_model::{self, ProjectWorkspace, TargetKind}; | 5 | use ra_project_model::{self, TargetKind}; |
6 | 6 | ||
7 | use crate::{global_state::GlobalStateSnapshot, Result}; | 7 | use 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..aa2c4ae15 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -9,6 +9,7 @@ | |||
9 | 9 | ||
10 | use std::{ffi::OsString, path::PathBuf}; | 10 | use std::{ffi::OsString, path::PathBuf}; |
11 | 11 | ||
12 | use crate::diagnostics::DiagnosticsConfig; | ||
12 | use lsp_types::ClientCapabilities; | 13 | use lsp_types::ClientCapabilities; |
13 | use ra_flycheck::FlycheckConfig; | 14 | use ra_flycheck::FlycheckConfig; |
14 | use ra_ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig}; | 15 | use 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)] |
44 | pub enum LinkedProject { | 47 | pub enum LinkedProject { |
45 | ProjectManifest(ProjectManifest), | 48 | ProjectManifest(ProjectManifest), |
46 | JsonProject(JsonProject), | 49 | InlineJsonProject(JsonProject), |
47 | } | 50 | } |
48 | 51 | ||
49 | impl From<ProjectManifest> for LinkedProject { | 52 | impl From<ProjectManifest> for LinkedProject { |
@@ -54,7 +57,7 @@ impl From<ProjectManifest> for LinkedProject { | |||
54 | 57 | ||
55 | impl From<JsonProject> for LinkedProject { | 58 | impl 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, |
@@ -289,6 +296,7 @@ impl Config { | |||
289 | set(value, "/hoverActions/implementations", &mut self.hover.implementations); | 296 | set(value, "/hoverActions/implementations", &mut self.hover.implementations); |
290 | set(value, "/hoverActions/run", &mut self.hover.run); | 297 | set(value, "/hoverActions/run", &mut self.hover.run); |
291 | set(value, "/hoverActions/debug", &mut self.hover.debug); | 298 | set(value, "/hoverActions/debug", &mut self.hover.debug); |
299 | set(value, "/hoverActions/gotoTypeDef", &mut self.hover.goto_type_def); | ||
292 | } else { | 300 | } else { |
293 | self.hover = HoverConfig::NO_ACTIONS; | 301 | self.hover = HoverConfig::NO_ACTIONS; |
294 | } | 302 | } |
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; | |||
11 | pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; | 11 | pub type CheckFixes = Arc<HashMap<FileId, Vec<Fix>>>; |
12 | 12 | ||
13 | #[derive(Debug, Default, Clone)] | 13 | #[derive(Debug, Default, Clone)] |
14 | pub struct DiagnosticsConfig { | ||
15 | pub warnings_as_info: Vec<String>, | ||
16 | pub warnings_as_hint: Vec<String>, | ||
17 | } | ||
18 | |||
19 | #[derive(Debug, Default, Clone)] | ||
14 | pub struct DiagnosticCollection { | 20 | pub 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.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap index f0273315e..9a7972ff5 100644 --- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap | |||
@@ -29,7 +29,7 @@ expression: diag | |||
29 | }, | 29 | }, |
30 | }, | 30 | }, |
31 | severity: Some( | 31 | severity: Some( |
32 | Hint, | 32 | Warning, |
33 | ), | 33 | ), |
34 | code: Some( | 34 | code: Some( |
35 | String( | 35 | String( |
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 | --- | ||
2 | source: crates/rust-analyzer/src/diagnostics/to_proto.rs | ||
3 | expression: 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 | --- | ||
2 | source: crates/rust-analyzer/src/diagnostics/to_proto.rs | ||
3 | expression: 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 04e286780..ba74f15f3 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs | |||
@@ -1,10 +1,6 @@ | |||
1 | //! This module provides the functionality needed to convert diagnostics from | 1 | //! This module provides the functionality needed to convert diagnostics from |
2 | //! `cargo check` json format to the LSP diagnostic format. | 2 | //! `cargo check` json format to the LSP diagnostic format. |
3 | use std::{ | 3 | use std::{collections::HashMap, path::Path}; |
4 | collections::HashMap, | ||
5 | path::{Component, Path, Prefix}, | ||
6 | str::FromStr, | ||
7 | }; | ||
8 | 4 | ||
9 | use lsp_types::{ | 5 | use lsp_types::{ |
10 | Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, | 6 | Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, |
@@ -13,14 +9,24 @@ use lsp_types::{ | |||
13 | use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; | 9 | use ra_flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; |
14 | use stdx::format_to; | 10 | use stdx::format_to; |
15 | 11 | ||
16 | use crate::{lsp_ext, Result}; | 12 | use super::DiagnosticsConfig; |
13 | use crate::{lsp_ext, to_proto::url_from_abs_path}; | ||
17 | 14 | ||
18 | /// Converts a Rust level string to a LSP severity | 15 | /// Determines the LSP severity from a diagnostic |
19 | fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> { | 16 | fn map_diagnostic_to_severity( |
20 | let res = match val { | 17 | config: &DiagnosticsConfig, |
18 | val: &ra_flycheck::Diagnostic, | ||
19 | ) -> Option<DiagnosticSeverity> { | ||
20 | let res = match val.level { | ||
21 | DiagnosticLevel::Ice => DiagnosticSeverity::Error, | 21 | DiagnosticLevel::Ice => DiagnosticSeverity::Error, |
22 | DiagnosticLevel::Error => DiagnosticSeverity::Error, | 22 | DiagnosticLevel::Error => DiagnosticSeverity::Error, |
23 | 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 | }, | ||
24 | DiagnosticLevel::Note => DiagnosticSeverity::Information, | 30 | DiagnosticLevel::Note => DiagnosticSeverity::Information, |
25 | DiagnosticLevel::Help => DiagnosticSeverity::Hint, | 31 | DiagnosticLevel::Help => DiagnosticSeverity::Hint, |
26 | DiagnosticLevel::Unknown => return None, | 32 | DiagnosticLevel::Unknown => return None, |
@@ -65,7 +71,7 @@ fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &Path) -> Locatio | |||
65 | fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location { | 71 | fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location { |
66 | let mut file_name = workspace_root.to_path_buf(); | 72 | let mut file_name = workspace_root.to_path_buf(); |
67 | file_name.push(&span.file_name); | 73 | file_name.push(&span.file_name); |
68 | let uri = url_from_path_with_drive_lowercasing(file_name).unwrap(); | 74 | let uri = url_from_abs_path(&file_name); |
69 | 75 | ||
70 | // FIXME: this doesn't handle UTF16 offsets correctly | 76 | // FIXME: this doesn't handle UTF16 offsets correctly |
71 | let range = Range::new( | 77 | let range = Range::new( |
@@ -176,6 +182,7 @@ pub(crate) struct MappedRustDiagnostic { | |||
176 | /// | 182 | /// |
177 | /// If the diagnostic has no primary span this will return `None` | 183 | /// If the diagnostic has no primary span this will return `None` |
178 | pub(crate) fn map_rust_diagnostic_to_lsp( | 184 | pub(crate) fn map_rust_diagnostic_to_lsp( |
185 | config: &DiagnosticsConfig, | ||
179 | rd: &ra_flycheck::Diagnostic, | 186 | rd: &ra_flycheck::Diagnostic, |
180 | workspace_root: &Path, | 187 | workspace_root: &Path, |
181 | ) -> Vec<MappedRustDiagnostic> { | 188 | ) -> Vec<MappedRustDiagnostic> { |
@@ -184,7 +191,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
184 | return Vec::new(); | 191 | return Vec::new(); |
185 | } | 192 | } |
186 | 193 | ||
187 | let mut severity = map_level_to_severity(rd.level); | 194 | let severity = map_diagnostic_to_severity(config, rd); |
188 | 195 | ||
189 | let mut source = String::from("rustc"); | 196 | let mut source = String::from("rustc"); |
190 | 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()); |
@@ -226,7 +233,6 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
226 | } | 233 | } |
227 | 234 | ||
228 | if is_unused_or_unnecessary(rd) { | 235 | if is_unused_or_unnecessary(rd) { |
229 | severity = Some(DiagnosticSeverity::Hint); | ||
230 | tags.push(DiagnosticTag::Unnecessary); | 236 | tags.push(DiagnosticTag::Unnecessary); |
231 | } | 237 | } |
232 | 238 | ||
@@ -275,70 +281,16 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
275 | .collect() | 281 | .collect() |
276 | } | 282 | } |
277 | 283 | ||
278 | /// Returns a `Url` object from a given path, will lowercase drive letters if present. | ||
279 | /// This will only happen when processing windows paths. | ||
280 | /// | ||
281 | /// When processing non-windows path, this is essentially the same as `Url::from_file_path`. | ||
282 | pub fn url_from_path_with_drive_lowercasing(path: impl AsRef<Path>) -> Result<Url> { | ||
283 | let component_has_windows_drive = path.as_ref().components().any(|comp| { | ||
284 | if let Component::Prefix(c) = comp { | ||
285 | return matches!(c.kind(), Prefix::Disk(_) | Prefix::VerbatimDisk(_)); | ||
286 | } | ||
287 | false | ||
288 | }); | ||
289 | |||
290 | // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters. | ||
291 | let res = if component_has_windows_drive { | ||
292 | let url_original = Url::from_file_path(&path) | ||
293 | .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?; | ||
294 | |||
295 | let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect(); | ||
296 | |||
297 | // There is a drive partition, but we never found a colon. | ||
298 | // This should not happen, but in this case we just pass it through. | ||
299 | if drive_partition.len() == 1 { | ||
300 | return Ok(url_original); | ||
301 | } | ||
302 | |||
303 | let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0]; | ||
304 | let url = Url::from_str(&joined).expect("This came from a valid `Url`"); | ||
305 | |||
306 | url | ||
307 | } else { | ||
308 | Url::from_file_path(&path) | ||
309 | .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))? | ||
310 | }; | ||
311 | Ok(res) | ||
312 | } | ||
313 | |||
314 | #[cfg(test)] | 284 | #[cfg(test)] |
285 | #[cfg(not(windows))] | ||
315 | mod tests { | 286 | mod tests { |
316 | use super::*; | 287 | use super::*; |
317 | 288 | ||
318 | // `Url` is not able to parse windows paths on unix machines. | ||
319 | #[test] | ||
320 | #[cfg(target_os = "windows")] | ||
321 | fn test_lowercase_drive_letter_with_drive() { | ||
322 | let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap(); | ||
323 | |||
324 | assert_eq!(url.to_string(), "file:///c:/Test"); | ||
325 | } | ||
326 | |||
327 | #[test] | ||
328 | #[cfg(target_os = "windows")] | ||
329 | fn test_drive_without_colon_passthrough() { | ||
330 | let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap(); | ||
331 | |||
332 | assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); | ||
333 | } | ||
334 | |||
335 | #[cfg(not(windows))] | ||
336 | fn parse_diagnostic(val: &str) -> ra_flycheck::Diagnostic { | 289 | fn parse_diagnostic(val: &str) -> ra_flycheck::Diagnostic { |
337 | serde_json::from_str::<ra_flycheck::Diagnostic>(val).unwrap() | 290 | serde_json::from_str::<ra_flycheck::Diagnostic>(val).unwrap() |
338 | } | 291 | } |
339 | 292 | ||
340 | #[test] | 293 | #[test] |
341 | #[cfg(not(windows))] | ||
342 | fn snap_rustc_incompatible_type_for_trait() { | 294 | fn snap_rustc_incompatible_type_for_trait() { |
343 | let diag = parse_diagnostic( | 295 | let diag = parse_diagnostic( |
344 | r##"{ | 296 | r##"{ |
@@ -387,12 +339,11 @@ mod tests { | |||
387 | ); | 339 | ); |
388 | 340 | ||
389 | let workspace_root = Path::new("/test/"); | 341 | let workspace_root = Path::new("/test/"); |
390 | let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); | 342 | let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root); |
391 | insta::assert_debug_snapshot!(diag); | 343 | insta::assert_debug_snapshot!(diag); |
392 | } | 344 | } |
393 | 345 | ||
394 | #[test] | 346 | #[test] |
395 | #[cfg(not(windows))] | ||
396 | fn snap_rustc_unused_variable() { | 347 | fn snap_rustc_unused_variable() { |
397 | let diag = parse_diagnostic( | 348 | let diag = parse_diagnostic( |
398 | r##"{ | 349 | r##"{ |
@@ -470,12 +421,187 @@ mod tests { | |||
470 | ); | 421 | ); |
471 | 422 | ||
472 | let workspace_root = Path::new("/test/"); | 423 | let workspace_root = Path::new("/test/"); |
473 | 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); | ||
474 | insta::assert_debug_snapshot!(diag); | 513 | insta::assert_debug_snapshot!(diag); |
475 | } | 514 | } |
476 | 515 | ||
477 | #[test] | 516 | #[test] |
478 | #[cfg(not(windows))] | 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); | ||
601 | insta::assert_debug_snapshot!(diag); | ||
602 | } | ||
603 | |||
604 | #[test] | ||
479 | fn snap_rustc_wrong_number_of_parameters() { | 605 | fn snap_rustc_wrong_number_of_parameters() { |
480 | let diag = parse_diagnostic( | 606 | let diag = parse_diagnostic( |
481 | r##"{ | 607 | r##"{ |
@@ -595,12 +721,11 @@ mod tests { | |||
595 | ); | 721 | ); |
596 | 722 | ||
597 | let workspace_root = Path::new("/test/"); | 723 | let workspace_root = Path::new("/test/"); |
598 | let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); | 724 | let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root); |
599 | insta::assert_debug_snapshot!(diag); | 725 | insta::assert_debug_snapshot!(diag); |
600 | } | 726 | } |
601 | 727 | ||
602 | #[test] | 728 | #[test] |
603 | #[cfg(not(windows))] | ||
604 | fn snap_clippy_pass_by_ref() { | 729 | fn snap_clippy_pass_by_ref() { |
605 | let diag = parse_diagnostic( | 730 | let diag = parse_diagnostic( |
606 | r##"{ | 731 | r##"{ |
@@ -716,12 +841,11 @@ mod tests { | |||
716 | ); | 841 | ); |
717 | 842 | ||
718 | let workspace_root = Path::new("/test/"); | 843 | let workspace_root = Path::new("/test/"); |
719 | let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); | 844 | let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root); |
720 | insta::assert_debug_snapshot!(diag); | 845 | insta::assert_debug_snapshot!(diag); |
721 | } | 846 | } |
722 | 847 | ||
723 | #[test] | 848 | #[test] |
724 | #[cfg(not(windows))] | ||
725 | fn snap_rustc_mismatched_type() { | 849 | fn snap_rustc_mismatched_type() { |
726 | let diag = parse_diagnostic( | 850 | let diag = parse_diagnostic( |
727 | r##"{ | 851 | r##"{ |
@@ -760,12 +884,11 @@ mod tests { | |||
760 | ); | 884 | ); |
761 | 885 | ||
762 | let workspace_root = Path::new("/test/"); | 886 | let workspace_root = Path::new("/test/"); |
763 | let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); | 887 | let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root); |
764 | insta::assert_debug_snapshot!(diag); | 888 | insta::assert_debug_snapshot!(diag); |
765 | } | 889 | } |
766 | 890 | ||
767 | #[test] | 891 | #[test] |
768 | #[cfg(not(windows))] | ||
769 | fn snap_handles_macro_location() { | 892 | fn snap_handles_macro_location() { |
770 | let diag = parse_diagnostic( | 893 | let diag = parse_diagnostic( |
771 | r##"{ | 894 | r##"{ |
@@ -1032,12 +1155,11 @@ mod tests { | |||
1032 | ); | 1155 | ); |
1033 | 1156 | ||
1034 | let workspace_root = Path::new("/test/"); | 1157 | let workspace_root = Path::new("/test/"); |
1035 | let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); | 1158 | let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root); |
1036 | insta::assert_debug_snapshot!(diag); | 1159 | insta::assert_debug_snapshot!(diag); |
1037 | } | 1160 | } |
1038 | 1161 | ||
1039 | #[test] | 1162 | #[test] |
1040 | #[cfg(not(windows))] | ||
1041 | fn snap_macro_compiler_error() { | 1163 | fn snap_macro_compiler_error() { |
1042 | let diag = parse_diagnostic( | 1164 | let diag = parse_diagnostic( |
1043 | r##"{ | 1165 | r##"{ |
@@ -1262,12 +1384,11 @@ mod tests { | |||
1262 | ); | 1384 | ); |
1263 | 1385 | ||
1264 | let workspace_root = Path::new("/test/"); | 1386 | let workspace_root = Path::new("/test/"); |
1265 | let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); | 1387 | let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root); |
1266 | insta::assert_debug_snapshot!(diag); | 1388 | insta::assert_debug_snapshot!(diag); |
1267 | } | 1389 | } |
1268 | 1390 | ||
1269 | #[test] | 1391 | #[test] |
1270 | #[cfg(not(windows))] | ||
1271 | fn snap_multi_line_fix() { | 1392 | fn snap_multi_line_fix() { |
1272 | let diag = parse_diagnostic( | 1393 | let diag = parse_diagnostic( |
1273 | r##"{ | 1394 | r##"{ |
@@ -1396,7 +1517,7 @@ mod tests { | |||
1396 | ); | 1517 | ); |
1397 | 1518 | ||
1398 | let workspace_root = Path::new("/test/"); | 1519 | let workspace_root = Path::new("/test/"); |
1399 | let diag = map_rust_diagnostic_to_lsp(&diag, workspace_root); | 1520 | let diag = map_rust_diagnostic_to_lsp(&DiagnosticsConfig::default(), &diag, workspace_root); |
1400 | insta::assert_debug_snapshot!(diag); | 1521 | insta::assert_debug_snapshot!(diag); |
1401 | } | 1522 | } |
1402 | } | 1523 | } |
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 206673829..40f856e6e 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs | |||
@@ -17,7 +17,7 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex | |||
17 | } | 17 | } |
18 | 18 | ||
19 | pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { | 19 | pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result<FileId> { |
20 | world.uri_to_file_id(url) | 20 | world.url_to_file_id(url) |
21 | } | 21 | } |
22 | 22 | ||
23 | pub(crate) fn file_position( | 23 | pub(crate) fn file_position( |
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index 21116e165..1527c9947 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs | |||
@@ -12,42 +12,34 @@ use crossbeam_channel::{unbounded, Receiver}; | |||
12 | use lsp_types::Url; | 12 | use lsp_types::Url; |
13 | use parking_lot::RwLock; | 13 | use parking_lot::RwLock; |
14 | use ra_flycheck::{Flycheck, FlycheckConfig}; | 14 | use ra_flycheck::{Flycheck, FlycheckConfig}; |
15 | use ra_ide::{ | 15 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, SourceRootId}; |
16 | Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, | 16 | use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; |
17 | }; | 17 | use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsTask, Watch}; |
18 | use ra_project_model::{ProcMacroClient, ProjectWorkspace}; | ||
19 | use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; | ||
20 | use relative_path::RelativePathBuf; | ||
21 | use stdx::format_to; | 18 | use stdx::format_to; |
22 | 19 | ||
23 | use crate::{ | 20 | use crate::{ |
24 | config::Config, | 21 | config::{Config, FilesWatcher}, |
25 | diagnostics::{ | 22 | diagnostics::{CheckFixes, DiagnosticCollection}, |
26 | to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection, | ||
27 | }, | ||
28 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, | 23 | main_loop::pending_requests::{CompletedRequest, LatestRequests}, |
24 | to_proto::url_from_abs_path, | ||
29 | vfs_glob::{Glob, RustPackageFilterBuilder}, | 25 | vfs_glob::{Glob, RustPackageFilterBuilder}, |
30 | LspError, Result, | 26 | LspError, Result, |
31 | }; | 27 | }; |
32 | use ra_db::ExternSourceId; | 28 | use ra_db::{CrateId, ExternSourceId}; |
33 | use rustc_hash::{FxHashMap, FxHashSet}; | 29 | use rustc_hash::{FxHashMap, FxHashSet}; |
34 | 30 | ||
35 | fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { | 31 | fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> { |
36 | // FIXME: Figure out the multi-workspace situation | 32 | // FIXME: Figure out the multi-workspace situation |
37 | workspaces | 33 | workspaces.iter().find_map(|w| match w { |
38 | .iter() | 34 | ProjectWorkspace::Cargo { cargo, .. } => { |
39 | .find_map(|w| match w { | ||
40 | ProjectWorkspace::Cargo { cargo, .. } => Some(cargo), | ||
41 | ProjectWorkspace::Json { .. } => None, | ||
42 | }) | ||
43 | .map(|cargo| { | ||
44 | let cargo_project_root = cargo.workspace_root().to_path_buf(); | 35 | let cargo_project_root = cargo.workspace_root().to_path_buf(); |
45 | Some(Flycheck::new(config.clone(), cargo_project_root)) | 36 | Some(Flycheck::new(config.clone(), cargo_project_root)) |
46 | }) | 37 | } |
47 | .unwrap_or_else(|| { | 38 | ProjectWorkspace::Json { .. } => { |
48 | log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); | 39 | log::warn!("Cargo check watching only supported for cargo workspaces, disabling"); |
49 | None | 40 | None |
50 | }) | 41 | } |
42 | }) | ||
51 | } | 43 | } |
52 | 44 | ||
53 | /// `GlobalState` is the primary mutable state of the language server | 45 | /// `GlobalState` is the primary mutable state of the language server |
@@ -84,7 +76,6 @@ impl GlobalState { | |||
84 | workspaces: Vec<ProjectWorkspace>, | 76 | workspaces: Vec<ProjectWorkspace>, |
85 | lru_capacity: Option<usize>, | 77 | lru_capacity: Option<usize>, |
86 | exclude_globs: &[Glob], | 78 | exclude_globs: &[Glob], |
87 | watch: Watch, | ||
88 | config: Config, | 79 | config: Config, |
89 | ) -> GlobalState { | 80 | ) -> GlobalState { |
90 | let mut change = AnalysisChange::new(); | 81 | let mut change = AnalysisChange::new(); |
@@ -119,6 +110,7 @@ impl GlobalState { | |||
119 | 110 | ||
120 | let (task_sender, task_receiver) = unbounded(); | 111 | let (task_sender, task_receiver) = unbounded(); |
121 | let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); | 112 | let task_sender = Box::new(move |t| task_sender.send(t).unwrap()); |
113 | let watch = Watch(matches!(config.files.watcher, FilesWatcher::Notify)); | ||
122 | let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); | 114 | let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch); |
123 | 115 | ||
124 | let mut extern_source_roots = FxHashMap::default(); | 116 | let mut extern_source_roots = FxHashMap::default(); |
@@ -196,32 +188,18 @@ impl GlobalState { | |||
196 | 188 | ||
197 | /// Returns a vec of libraries | 189 | /// Returns a vec of libraries |
198 | /// FIXME: better API here | 190 | /// FIXME: better API here |
199 | pub fn process_changes( | 191 | pub fn process_changes(&mut self, roots_scanned: &mut usize) -> bool { |
200 | &mut self, | ||
201 | roots_scanned: &mut usize, | ||
202 | ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> { | ||
203 | let changes = self.vfs.write().commit_changes(); | 192 | let changes = self.vfs.write().commit_changes(); |
204 | if changes.is_empty() { | 193 | if changes.is_empty() { |
205 | return None; | 194 | return false; |
206 | } | 195 | } |
207 | let mut libs = Vec::new(); | ||
208 | let mut change = AnalysisChange::new(); | 196 | let mut change = AnalysisChange::new(); |
209 | for c in changes { | 197 | for c in changes { |
210 | match c { | 198 | match c { |
211 | VfsChange::AddRoot { root, files } => { | 199 | VfsChange::AddRoot { root, files } => { |
212 | let root_path = self.vfs.read().root2path(root); | 200 | *roots_scanned += 1; |
213 | let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r)); | 201 | for (file, path, text) in files { |
214 | if is_local { | 202 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); |
215 | *roots_scanned += 1; | ||
216 | for (file, path, text) in files { | ||
217 | change.add_file(SourceRootId(root.0), FileId(file.0), path, text); | ||
218 | } | ||
219 | } else { | ||
220 | let files = files | ||
221 | .into_iter() | ||
222 | .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text)) | ||
223 | .collect(); | ||
224 | libs.push((SourceRootId(root.0), files)); | ||
225 | } | 203 | } |
226 | } | 204 | } |
227 | VfsChange::AddFile { root, file, path, text } => { | 205 | VfsChange::AddFile { root, file, path, text } => { |
@@ -236,13 +214,7 @@ impl GlobalState { | |||
236 | } | 214 | } |
237 | } | 215 | } |
238 | self.analysis_host.apply_change(change); | 216 | self.analysis_host.apply_change(change); |
239 | Some(libs) | 217 | true |
240 | } | ||
241 | |||
242 | pub fn add_lib(&mut self, data: LibraryData) { | ||
243 | let mut change = AnalysisChange::new(); | ||
244 | change.add_library(data); | ||
245 | self.analysis_host.apply_change(change); | ||
246 | } | 218 | } |
247 | 219 | ||
248 | pub fn snapshot(&self) -> GlobalStateSnapshot { | 220 | pub fn snapshot(&self) -> GlobalStateSnapshot { |
@@ -274,8 +246,8 @@ impl GlobalStateSnapshot { | |||
274 | &self.analysis | 246 | &self.analysis |
275 | } | 247 | } |
276 | 248 | ||
277 | pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> { | 249 | pub fn url_to_file_id(&self, url: &Url) -> Result<FileId> { |
278 | let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; | 250 | let path = url.to_file_path().map_err(|()| format!("invalid uri: {}", url))?; |
279 | let file = self.vfs.read().path2file(&path).ok_or_else(|| { | 251 | let file = self.vfs.read().path2file(&path).ok_or_else(|| { |
280 | // Show warning as this file is outside current workspace | 252 | // Show warning as this file is outside current workspace |
281 | // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`. | 253 | // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`. |
@@ -287,27 +259,33 @@ impl GlobalStateSnapshot { | |||
287 | Ok(FileId(file.0)) | 259 | Ok(FileId(file.0)) |
288 | } | 260 | } |
289 | 261 | ||
290 | pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> { | 262 | pub fn file_id_to_url(&self, id: FileId) -> Url { |
291 | let path = self.vfs.read().file2path(VfsFile(id.0)); | 263 | file_id_to_url(&self.vfs.read(), id) |
292 | let url = url_from_path_with_drive_lowercasing(path)?; | ||
293 | |||
294 | Ok(url) | ||
295 | } | ||
296 | |||
297 | pub fn file_id_to_path(&self, id: FileId) -> PathBuf { | ||
298 | self.vfs.read().file2path(VfsFile(id.0)) | ||
299 | } | 264 | } |
300 | 265 | ||
301 | pub fn file_line_endings(&self, id: FileId) -> LineEndings { | 266 | pub fn file_line_endings(&self, id: FileId) -> LineEndings { |
302 | self.vfs.read().file_line_endings(VfsFile(id.0)) | 267 | self.vfs.read().file_line_endings(VfsFile(id.0)) |
303 | } | 268 | } |
304 | 269 | ||
305 | pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> { | 270 | pub fn anchored_path(&self, file_id: FileId, path: &str) -> Url { |
306 | let base = self.vfs.read().root2path(VfsRoot(root.0)); | 271 | let mut base = self.vfs.read().file2path(VfsFile(file_id.0)); |
307 | let path = path.to_path(base); | 272 | base.pop(); |
308 | let url = Url::from_file_path(&path) | 273 | let path = base.join(path); |
309 | .map_err(|_| format!("can't convert path to url: {}", path.display()))?; | 274 | url_from_abs_path(&path) |
310 | Ok(url) | 275 | } |
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 | }) | ||
311 | } | 289 | } |
312 | 290 | ||
313 | pub fn status(&self) -> String { | 291 | pub fn status(&self) -> String { |
@@ -335,3 +313,8 @@ impl GlobalStateSnapshot { | |||
335 | self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) | 313 | self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path)) |
336 | } | 314 | } |
337 | } | 315 | } |
316 | |||
317 | pub(crate) fn file_id_to_url(vfs: &Vfs, id: FileId) -> Url { | ||
318 | let path = vfs.file2path(VfsFile(id.0)); | ||
319 | url_from_abs_path(&path) | ||
320 | } | ||
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 752dbf145..f0aaaa21e 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -24,20 +24,19 @@ use lsp_types::{ | |||
24 | WorkDoneProgressReport, | 24 | WorkDoneProgressReport, |
25 | }; | 25 | }; |
26 | use ra_flycheck::{CheckTask, Status}; | 26 | use ra_flycheck::{CheckTask, Status}; |
27 | use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId}; | 27 | use ra_ide::{Canceled, FileId, LineIndex}; |
28 | use ra_prof::profile; | 28 | use ra_prof::profile; |
29 | use ra_project_model::{PackageRoot, ProjectWorkspace}; | 29 | use ra_project_model::{PackageRoot, ProjectWorkspace}; |
30 | use ra_vfs::{VfsFile, VfsTask, Watch}; | 30 | use ra_vfs::VfsTask; |
31 | use relative_path::RelativePathBuf; | ||
32 | use rustc_hash::FxHashSet; | 31 | use rustc_hash::FxHashSet; |
33 | use serde::{de::DeserializeOwned, Serialize}; | 32 | use serde::{de::DeserializeOwned, Serialize}; |
34 | use threadpool::ThreadPool; | 33 | use threadpool::ThreadPool; |
35 | 34 | ||
36 | use crate::{ | 35 | use crate::{ |
37 | config::{Config, FilesWatcher, LinkedProject}, | 36 | config::{Config, FilesWatcher, LinkedProject}, |
38 | diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, | 37 | diagnostics::DiagnosticTask, |
39 | from_proto, | 38 | from_proto, |
40 | global_state::{GlobalState, GlobalStateSnapshot}, | 39 | global_state::{file_id_to_url, GlobalState, GlobalStateSnapshot}, |
41 | lsp_ext, | 40 | lsp_ext, |
42 | main_loop::{ | 41 | main_loop::{ |
43 | pending_requests::{PendingRequest, PendingRequests}, | 42 | pending_requests::{PendingRequest, PendingRequests}, |
@@ -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 | }; |
@@ -156,25 +160,17 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
156 | connection.sender.send(request.into()).unwrap(); | 160 | connection.sender.send(request.into()).unwrap(); |
157 | } | 161 | } |
158 | 162 | ||
159 | GlobalState::new( | 163 | GlobalState::new(workspaces, config.lru_capacity, &globs, config) |
160 | workspaces, | ||
161 | config.lru_capacity, | ||
162 | &globs, | ||
163 | Watch(matches!(config.files.watcher, FilesWatcher::Notify)), | ||
164 | config, | ||
165 | ) | ||
166 | }; | 164 | }; |
167 | 165 | ||
168 | loop_state.roots_total = global_state.vfs.read().n_roots(); | 166 | loop_state.roots_total = global_state.vfs.read().n_roots(); |
169 | 167 | ||
170 | let pool = ThreadPool::default(); | 168 | let pool = ThreadPool::default(); |
171 | let (task_sender, task_receiver) = unbounded::<Task>(); | 169 | let (task_sender, task_receiver) = unbounded::<Task>(); |
172 | let (libdata_sender, libdata_receiver) = unbounded::<LibraryData>(); | ||
173 | 170 | ||
174 | log::info!("server initialized, serving requests"); | 171 | log::info!("server initialized, serving requests"); |
175 | { | 172 | { |
176 | let task_sender = task_sender; | 173 | let task_sender = task_sender; |
177 | let libdata_sender = libdata_sender; | ||
178 | loop { | 174 | loop { |
179 | log::trace!("selecting"); | 175 | log::trace!("selecting"); |
180 | let event = select! { | 176 | let event = select! { |
@@ -187,7 +183,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
187 | Ok(task) => Event::Vfs(task), | 183 | Ok(task) => Event::Vfs(task), |
188 | Err(RecvError) => return Err("vfs died".into()), | 184 | Err(RecvError) => return Err("vfs died".into()), |
189 | }, | 185 | }, |
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 { | 186 | recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { |
192 | Ok(task) => Event::CheckWatcher(task), | 187 | Ok(task) => Event::CheckWatcher(task), |
193 | Err(RecvError) => return Err("check watcher died".into()), | 188 | Err(RecvError) => return Err("check watcher died".into()), |
@@ -198,15 +193,7 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
198 | break; | 193 | break; |
199 | }; | 194 | }; |
200 | } | 195 | } |
201 | loop_turn( | 196 | 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 | } | 197 | } |
211 | } | 198 | } |
212 | global_state.analysis_host.request_cancellation(); | 199 | global_state.analysis_host.request_cancellation(); |
@@ -214,7 +201,6 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
214 | task_receiver.into_iter().for_each(|task| { | 201 | task_receiver.into_iter().for_each(|task| { |
215 | on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state) | 202 | on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state) |
216 | }); | 203 | }); |
217 | libdata_receiver.into_iter().for_each(drop); | ||
218 | log::info!("...tasks have finished"); | 204 | log::info!("...tasks have finished"); |
219 | log::info!("joining threadpool..."); | 205 | log::info!("joining threadpool..."); |
220 | pool.join(); | 206 | pool.join(); |
@@ -238,7 +224,6 @@ enum Event { | |||
238 | Msg(Message), | 224 | Msg(Message), |
239 | Task(Task), | 225 | Task(Task), |
240 | Vfs(VfsTask), | 226 | Vfs(VfsTask), |
241 | Lib(LibraryData), | ||
242 | CheckWatcher(CheckTask), | 227 | CheckWatcher(CheckTask), |
243 | } | 228 | } |
244 | 229 | ||
@@ -274,7 +259,6 @@ impl fmt::Debug for Event { | |||
274 | Event::Msg(it) => fmt::Debug::fmt(it, f), | 259 | Event::Msg(it) => fmt::Debug::fmt(it, f), |
275 | Event::Task(it) => fmt::Debug::fmt(it, f), | 260 | Event::Task(it) => fmt::Debug::fmt(it, f), |
276 | Event::Vfs(it) => fmt::Debug::fmt(it, f), | 261 | 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), | 262 | Event::CheckWatcher(it) => fmt::Debug::fmt(it, f), |
279 | } | 263 | } |
280 | } | 264 | } |
@@ -286,10 +270,6 @@ struct LoopState { | |||
286 | pending_responses: FxHashSet<RequestId>, | 270 | pending_responses: FxHashSet<RequestId>, |
287 | pending_requests: PendingRequests, | 271 | pending_requests: PendingRequests, |
288 | subscriptions: Subscriptions, | 272 | 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, | 273 | workspace_loaded: bool, |
294 | roots_progress_reported: Option<usize>, | 274 | roots_progress_reported: Option<usize>, |
295 | roots_scanned: usize, | 275 | roots_scanned: usize, |
@@ -310,7 +290,6 @@ impl LoopState { | |||
310 | fn loop_turn( | 290 | fn loop_turn( |
311 | pool: &ThreadPool, | 291 | pool: &ThreadPool, |
312 | task_sender: &Sender<Task>, | 292 | task_sender: &Sender<Task>, |
313 | libdata_sender: &Sender<LibraryData>, | ||
314 | connection: &Connection, | 293 | connection: &Connection, |
315 | global_state: &mut GlobalState, | 294 | global_state: &mut GlobalState, |
316 | loop_state: &mut LoopState, | 295 | loop_state: &mut LoopState, |
@@ -334,12 +313,6 @@ fn loop_turn( | |||
334 | Event::Vfs(task) => { | 313 | Event::Vfs(task) => { |
335 | global_state.vfs.write().handle_task(task); | 314 | global_state.vfs.write().handle_task(task); |
336 | } | 315 | } |
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)?, | 316 | Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, |
344 | Event::Msg(msg) => match msg { | 317 | Event::Msg(msg) => match msg { |
345 | Message::Request(req) => on_request( | 318 | Message::Request(req) => on_request( |
@@ -385,36 +358,12 @@ fn loop_turn( | |||
385 | }, | 358 | }, |
386 | }; | 359 | }; |
387 | 360 | ||
388 | let mut state_changed = false; | 361 | 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 | 362 | ||
410 | let show_progress = | 363 | let show_progress = |
411 | !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; | 364 | !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; |
412 | 365 | ||
413 | if !loop_state.workspace_loaded | 366 | 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; | 367 | state_changed = true; |
419 | loop_state.workspace_loaded = true; | 368 | loop_state.workspace_loaded = true; |
420 | if let Some(flycheck) = &global_state.flycheck { | 369 | if let Some(flycheck) = &global_state.flycheck { |
@@ -664,14 +613,11 @@ fn apply_document_changes( | |||
664 | mut line_index: Cow<'_, LineIndex>, | 613 | mut line_index: Cow<'_, LineIndex>, |
665 | content_changes: Vec<TextDocumentContentChangeEvent>, | 614 | content_changes: Vec<TextDocumentContentChangeEvent>, |
666 | ) { | 615 | ) { |
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 | 616 | // The changes we got must be applied sequentially, but can cross lines so we |
672 | // have to keep our line index updated. | 617 | // have to keep our line index updated. |
673 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we | 618 | // 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. | 619 | // remember the last valid line in the index and only rebuild it if needed. |
620 | // The VFS will normalize the end of lines to `\n`. | ||
675 | enum IndexValid { | 621 | enum IndexValid { |
676 | All, | 622 | All, |
677 | UpToLineExclusive(u64), | 623 | UpToLineExclusive(u64), |
@@ -695,19 +641,7 @@ fn apply_document_changes( | |||
695 | } | 641 | } |
696 | index_valid = IndexValid::UpToLineExclusive(range.start.line); | 642 | index_valid = IndexValid::UpToLineExclusive(range.start.line); |
697 | let range = from_proto::text_range(&line_index, range); | 643 | let range = from_proto::text_range(&line_index, range); |
698 | let mut text = old_text.to_owned(); | 644 | 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 | } | 645 | } |
712 | None => { | 646 | None => { |
713 | *old_text = change.text; | 647 | *old_text = change.text; |
@@ -729,6 +663,7 @@ fn on_check_task( | |||
729 | 663 | ||
730 | CheckTask::AddDiagnostic { workspace_root, diagnostic } => { | 664 | CheckTask::AddDiagnostic { workspace_root, diagnostic } => { |
731 | let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( | 665 | let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( |
666 | &global_state.config.diagnostics, | ||
732 | &diagnostic, | 667 | &diagnostic, |
733 | &workspace_root, | 668 | &workspace_root, |
734 | ); | 669 | ); |
@@ -801,17 +736,9 @@ fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender<Message>, state: | |||
801 | let subscriptions = state.diagnostics.handle_task(task); | 736 | let subscriptions = state.diagnostics.handle_task(task); |
802 | 737 | ||
803 | for file_id in subscriptions { | 738 | for file_id in subscriptions { |
804 | let path = state.vfs.read().file2path(VfsFile(file_id.0)); | 739 | let url = file_id_to_url(&state.vfs.read(), file_id); |
805 | let uri = match url_from_path_with_drive_lowercasing(&path) { | ||
806 | Ok(uri) => uri, | ||
807 | Err(err) => { | ||
808 | log::error!("Couldn't convert path to url ({}): {}", err, path.display()); | ||
809 | continue; | ||
810 | } | ||
811 | }; | ||
812 | |||
813 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); | 740 | let diagnostics = state.diagnostics.diagnostics_for(file_id).cloned().collect(); |
814 | let params = lsp_types::PublishDiagnosticsParams { uri, diagnostics, version: None }; | 741 | let params = lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }; |
815 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); | 742 | let not = notification_new::<lsp_types::notification::PublishDiagnostics>(params); |
816 | msg_sender.send(not.into()).unwrap(); | 743 | msg_sender.send(not.into()).unwrap(); |
817 | } | 744 | } |
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index a41adf8b0..2d7e649d2 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs | |||
@@ -18,8 +18,8 @@ use lsp_types::{ | |||
18 | TextDocumentIdentifier, Url, WorkspaceEdit, | 18 | TextDocumentIdentifier, Url, WorkspaceEdit, |
19 | }; | 19 | }; |
20 | use ra_ide::{ | 20 | use ra_ide::{ |
21 | FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind, | 21 | FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, |
22 | SearchScope, TextEdit, | 22 | RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit, |
23 | }; | 23 | }; |
24 | use ra_prof::profile; | 24 | use ra_prof::profile; |
25 | use ra_project_model::TargetKind; | 25 | use ra_project_model::TargetKind; |
@@ -258,7 +258,7 @@ pub fn handle_document_symbol( | |||
258 | let res = if snap.config.client_caps.hierarchical_symbols { | 258 | let res = if snap.config.client_caps.hierarchical_symbols { |
259 | document_symbols.into() | 259 | document_symbols.into() |
260 | } else { | 260 | } else { |
261 | let url = to_proto::url(&snap, file_id)?; | 261 | let url = to_proto::url(&snap, file_id); |
262 | let mut symbol_information = Vec::<SymbolInformation>::new(); | 262 | let mut symbol_information = Vec::<SymbolInformation>::new(); |
263 | for symbol in document_symbols { | 263 | for symbol in document_symbols { |
264 | flatten_document_symbol(&symbol, None, &url, &mut symbol_information); | 264 | flatten_document_symbol(&symbol, None, &url, &mut symbol_information); |
@@ -1150,6 +1150,23 @@ fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command { | |||
1150 | } | 1150 | } |
1151 | } | 1151 | } |
1152 | 1152 | ||
1153 | fn goto_location_command(snap: &GlobalStateSnapshot, nav: &NavigationTarget) -> Option<Command> { | ||
1154 | let value = if snap.config.client_caps.location_link { | ||
1155 | let link = to_proto::location_link(snap, None, nav.clone()).ok()?; | ||
1156 | to_value(link).ok()? | ||
1157 | } else { | ||
1158 | let range = FileRange { file_id: nav.file_id(), range: nav.range() }; | ||
1159 | let location = to_proto::location(snap, range).ok()?; | ||
1160 | to_value(location).ok()? | ||
1161 | }; | ||
1162 | |||
1163 | Some(Command { | ||
1164 | title: nav.name().to_string(), | ||
1165 | command: "rust-analyzer.gotoLocation".into(), | ||
1166 | arguments: Some(vec![value]), | ||
1167 | }) | ||
1168 | } | ||
1169 | |||
1153 | fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { | 1170 | fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { |
1154 | lsp_ext::CommandLink { tooltip: Some(tooltip), command } | 1171 | lsp_ext::CommandLink { tooltip: Some(tooltip), command } |
1155 | } | 1172 | } |
@@ -1160,7 +1177,7 @@ fn show_impl_command_link( | |||
1160 | ) -> Option<lsp_ext::CommandLinkGroup> { | 1177 | ) -> Option<lsp_ext::CommandLinkGroup> { |
1161 | if snap.config.hover.implementations { | 1178 | if snap.config.hover.implementations { |
1162 | if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) { | 1179 | if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) { |
1163 | let uri = to_proto::url(snap, position.file_id).ok()?; | 1180 | let uri = to_proto::url(snap, position.file_id); |
1164 | let line_index = snap.analysis().file_line_index(position.file_id).ok()?; | 1181 | let line_index = snap.analysis().file_line_index(position.file_id).ok()?; |
1165 | let position = to_proto::position(&line_index, position.offset); | 1182 | let position = to_proto::position(&line_index, position.offset); |
1166 | let locations: Vec<_> = nav_data | 1183 | let locations: Vec<_> = nav_data |
@@ -1180,13 +1197,13 @@ fn show_impl_command_link( | |||
1180 | None | 1197 | None |
1181 | } | 1198 | } |
1182 | 1199 | ||
1183 | fn to_runnable_action( | 1200 | fn runnable_action_links( |
1184 | snap: &GlobalStateSnapshot, | 1201 | snap: &GlobalStateSnapshot, |
1185 | file_id: FileId, | 1202 | file_id: FileId, |
1186 | runnable: Runnable, | 1203 | runnable: Runnable, |
1187 | ) -> Option<lsp_ext::CommandLinkGroup> { | 1204 | ) -> Option<lsp_ext::CommandLinkGroup> { |
1188 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?; | 1205 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?; |
1189 | if should_skip_target(&runnable, cargo_spec.as_ref()) { | 1206 | if !snap.config.hover.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) { |
1190 | return None; | 1207 | return None; |
1191 | } | 1208 | } |
1192 | 1209 | ||
@@ -1208,6 +1225,26 @@ fn to_runnable_action( | |||
1208 | }) | 1225 | }) |
1209 | } | 1226 | } |
1210 | 1227 | ||
1228 | fn goto_type_action_links( | ||
1229 | snap: &GlobalStateSnapshot, | ||
1230 | nav_targets: &[HoverGotoTypeData], | ||
1231 | ) -> Option<lsp_ext::CommandLinkGroup> { | ||
1232 | if !snap.config.hover.goto_type_def || nav_targets.is_empty() { | ||
1233 | return None; | ||
1234 | } | ||
1235 | |||
1236 | Some(lsp_ext::CommandLinkGroup { | ||
1237 | title: Some("Go to ".into()), | ||
1238 | commands: nav_targets | ||
1239 | .iter() | ||
1240 | .filter_map(|it| { | ||
1241 | goto_location_command(snap, &it.nav) | ||
1242 | .map(|cmd| to_command_link(cmd, it.mod_path.clone())) | ||
1243 | }) | ||
1244 | .collect(), | ||
1245 | }) | ||
1246 | } | ||
1247 | |||
1211 | fn prepare_hover_actions( | 1248 | fn prepare_hover_actions( |
1212 | snap: &GlobalStateSnapshot, | 1249 | snap: &GlobalStateSnapshot, |
1213 | file_id: FileId, | 1250 | file_id: FileId, |
@@ -1221,7 +1258,8 @@ fn prepare_hover_actions( | |||
1221 | .iter() | 1258 | .iter() |
1222 | .filter_map(|it| match it { | 1259 | .filter_map(|it| match it { |
1223 | HoverAction::Implementaion(position) => show_impl_command_link(snap, position), | 1260 | HoverAction::Implementaion(position) => show_impl_command_link(snap, position), |
1224 | HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r.clone()), | 1261 | HoverAction::Runnable(r) => runnable_action_links(snap, file_id, r.clone()), |
1262 | HoverAction::GoToType(targets) => goto_type_action_links(snap, targets), | ||
1225 | }) | 1263 | }) |
1226 | .collect() | 1264 | .collect() |
1227 | } | 1265 | } |
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 | ||
50 | macro_rules! define_semantic_token_modifiers { | 51 | macro_rules! define_semantic_token_modifiers { |
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 710df1fbd..ec153097e 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -1,4 +1,7 @@ | |||
1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. | 1 | //! Conversion of rust-analyzer specific types to lsp_types equivalents. |
2 | use std::path::{self, Path}; | ||
3 | |||
4 | use itertools::Itertools; | ||
2 | use ra_db::{FileId, FileRange}; | 5 | use ra_db::{FileId, FileRange}; |
3 | use ra_ide::{ | 6 | use ra_ide::{ |
4 | Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, | 7 | Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, |
@@ -321,12 +324,14 @@ fn semantic_token_type_and_modifiers( | |||
321 | HighlightTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE, | 324 | HighlightTag::UnresolvedReference => semantic_tokens::UNRESOLVED_REFERENCE, |
322 | HighlightTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER, | 325 | HighlightTag::FormatSpecifier => semantic_tokens::FORMAT_SPECIFIER, |
323 | HighlightTag::Operator => lsp_types::SemanticTokenType::OPERATOR, | 326 | HighlightTag::Operator => lsp_types::SemanticTokenType::OPERATOR, |
327 | HighlightTag::EscapeSequence => semantic_tokens::ESCAPE_SEQUENCE, | ||
324 | }; | 328 | }; |
325 | 329 | ||
326 | for modifier in highlight.modifiers.iter() { | 330 | for modifier in highlight.modifiers.iter() { |
327 | let modifier = match modifier { | 331 | let modifier = match modifier { |
328 | HighlightModifier::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER, | 332 | HighlightModifier::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER, |
329 | HighlightModifier::Definition => lsp_types::SemanticTokenModifier::DECLARATION, | 333 | HighlightModifier::Definition => lsp_types::SemanticTokenModifier::DECLARATION, |
334 | HighlightModifier::Documentation => lsp_types::SemanticTokenModifier::DOCUMENTATION, | ||
330 | HighlightModifier::ControlFlow => semantic_tokens::CONTROL_FLOW, | 335 | HighlightModifier::ControlFlow => semantic_tokens::CONTROL_FLOW, |
331 | HighlightModifier::Mutable => semantic_tokens::MUTABLE, | 336 | HighlightModifier::Mutable => semantic_tokens::MUTABLE, |
332 | HighlightModifier::Unsafe => semantic_tokens::UNSAFE, | 337 | HighlightModifier::Unsafe => semantic_tokens::UNSAFE, |
@@ -385,24 +390,55 @@ pub(crate) fn folding_range( | |||
385 | } | 390 | } |
386 | } | 391 | } |
387 | 392 | ||
388 | pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result<lsp_types::Url> { | 393 | pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> lsp_types::Url { |
389 | snap.file_id_to_uri(file_id) | 394 | snap.file_id_to_url(file_id) |
395 | } | ||
396 | |||
397 | /// Returns a `Url` object from a given path, will lowercase drive letters if present. | ||
398 | /// This will only happen when processing windows paths. | ||
399 | /// | ||
400 | /// When processing non-windows path, this is essentially the same as `Url::from_file_path`. | ||
401 | pub(crate) fn url_from_abs_path(path: &Path) -> lsp_types::Url { | ||
402 | assert!(path.is_absolute()); | ||
403 | let url = lsp_types::Url::from_file_path(path).unwrap(); | ||
404 | match path.components().next() { | ||
405 | Some(path::Component::Prefix(prefix)) if matches!(prefix.kind(), path::Prefix::Disk(_) | path::Prefix::VerbatimDisk(_)) => | ||
406 | { | ||
407 | // Need to lowercase driver letter | ||
408 | } | ||
409 | _ => return url, | ||
410 | } | ||
411 | |||
412 | let driver_letter_range = { | ||
413 | let (scheme, drive_letter, _rest) = match url.as_str().splitn(3, ':').collect_tuple() { | ||
414 | Some(it) => it, | ||
415 | None => return url, | ||
416 | }; | ||
417 | let start = scheme.len() + ':'.len_utf8(); | ||
418 | start..(start + drive_letter.len()) | ||
419 | }; | ||
420 | |||
421 | // Note: lowercasing the `path` itself doesn't help, the `Url::parse` | ||
422 | // machinery *also* canonicalizes the drive letter. So, just massage the | ||
423 | // string in place. | ||
424 | let mut url = url.into_string(); | ||
425 | url[driver_letter_range].make_ascii_lowercase(); | ||
426 | lsp_types::Url::parse(&url).unwrap() | ||
390 | } | 427 | } |
391 | 428 | ||
392 | pub(crate) fn versioned_text_document_identifier( | 429 | pub(crate) fn versioned_text_document_identifier( |
393 | snap: &GlobalStateSnapshot, | 430 | snap: &GlobalStateSnapshot, |
394 | file_id: FileId, | 431 | file_id: FileId, |
395 | version: Option<i64>, | 432 | version: Option<i64>, |
396 | ) -> Result<lsp_types::VersionedTextDocumentIdentifier> { | 433 | ) -> lsp_types::VersionedTextDocumentIdentifier { |
397 | let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id)?, version }; | 434 | lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id), version } |
398 | Ok(res) | ||
399 | } | 435 | } |
400 | 436 | ||
401 | pub(crate) fn location( | 437 | pub(crate) fn location( |
402 | snap: &GlobalStateSnapshot, | 438 | snap: &GlobalStateSnapshot, |
403 | frange: FileRange, | 439 | frange: FileRange, |
404 | ) -> Result<lsp_types::Location> { | 440 | ) -> Result<lsp_types::Location> { |
405 | let url = url(snap, frange.file_id)?; | 441 | let url = url(snap, frange.file_id); |
406 | let line_index = snap.analysis().file_line_index(frange.file_id)?; | 442 | let line_index = snap.analysis().file_line_index(frange.file_id)?; |
407 | let range = range(&line_index, frange.range); | 443 | let range = range(&line_index, frange.range); |
408 | let loc = lsp_types::Location::new(url, range); | 444 | let loc = lsp_types::Location::new(url, range); |
@@ -438,7 +474,7 @@ fn location_info( | |||
438 | ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { | 474 | ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { |
439 | let line_index = snap.analysis().file_line_index(target.file_id())?; | 475 | let line_index = snap.analysis().file_line_index(target.file_id())?; |
440 | 476 | ||
441 | let target_uri = url(snap, target.file_id())?; | 477 | let target_uri = url(snap, target.file_id()); |
442 | let target_range = range(&line_index, target.full_range()); | 478 | let target_range = range(&line_index, target.full_range()); |
443 | let target_selection_range = | 479 | let target_selection_range = |
444 | target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); | 480 | target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); |
@@ -478,7 +514,7 @@ pub(crate) fn snippet_text_document_edit( | |||
478 | is_snippet: bool, | 514 | is_snippet: bool, |
479 | source_file_edit: SourceFileEdit, | 515 | source_file_edit: SourceFileEdit, |
480 | ) -> Result<lsp_ext::SnippetTextDocumentEdit> { | 516 | ) -> Result<lsp_ext::SnippetTextDocumentEdit> { |
481 | let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None)?; | 517 | let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None); |
482 | let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?; | 518 | let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?; |
483 | let line_endings = snap.file_line_endings(source_file_edit.file_id); | 519 | let line_endings = snap.file_line_endings(source_file_edit.file_id); |
484 | let edits = source_file_edit | 520 | let edits = source_file_edit |
@@ -492,19 +528,18 @@ pub(crate) fn snippet_text_document_edit( | |||
492 | pub(crate) fn resource_op( | 528 | pub(crate) fn resource_op( |
493 | snap: &GlobalStateSnapshot, | 529 | snap: &GlobalStateSnapshot, |
494 | file_system_edit: FileSystemEdit, | 530 | file_system_edit: FileSystemEdit, |
495 | ) -> Result<lsp_types::ResourceOp> { | 531 | ) -> lsp_types::ResourceOp { |
496 | let res = match file_system_edit { | 532 | match file_system_edit { |
497 | FileSystemEdit::CreateFile { source_root, path } => { | 533 | FileSystemEdit::CreateFile { anchor, dst } => { |
498 | let uri = snap.path_to_uri(source_root, &path)?; | 534 | let uri = snap.anchored_path(anchor, &dst); |
499 | lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) | 535 | lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) |
500 | } | 536 | } |
501 | FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { | 537 | FileSystemEdit::MoveFile { src, anchor, dst } => { |
502 | let old_uri = snap.file_id_to_uri(src)?; | 538 | let old_uri = snap.file_id_to_url(src); |
503 | let new_uri = snap.path_to_uri(dst_source_root, &dst_path)?; | 539 | let new_uri = snap.anchored_path(anchor, &dst); |
504 | lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) | 540 | lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) |
505 | } | 541 | } |
506 | }; | 542 | } |
507 | Ok(res) | ||
508 | } | 543 | } |
509 | 544 | ||
510 | pub(crate) fn snippet_workspace_edit( | 545 | pub(crate) fn snippet_workspace_edit( |
@@ -513,7 +548,7 @@ pub(crate) fn snippet_workspace_edit( | |||
513 | ) -> Result<lsp_ext::SnippetWorkspaceEdit> { | 548 | ) -> Result<lsp_ext::SnippetWorkspaceEdit> { |
514 | let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); | 549 | let mut document_changes: Vec<lsp_ext::SnippetDocumentChangeOperation> = Vec::new(); |
515 | for op in source_change.file_system_edits { | 550 | for op in source_change.file_system_edits { |
516 | let op = resource_op(&snap, op)?; | 551 | let op = resource_op(&snap, op); |
517 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); | 552 | document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); |
518 | } | 553 | } |
519 | for edit in source_change.source_file_edits { | 554 | for edit in source_change.source_file_edits { |
@@ -568,7 +603,7 @@ impl From<lsp_ext::SnippetWorkspaceEdit> for lsp_types::WorkspaceEdit { | |||
568 | } | 603 | } |
569 | } | 604 | } |
570 | 605 | ||
571 | pub fn call_hierarchy_item( | 606 | pub(crate) fn call_hierarchy_item( |
572 | snap: &GlobalStateSnapshot, | 607 | snap: &GlobalStateSnapshot, |
573 | target: NavigationTarget, | 608 | target: NavigationTarget, |
574 | ) -> Result<lsp_types::CallHierarchyItem> { | 609 | ) -> Result<lsp_types::CallHierarchyItem> { |
@@ -579,50 +614,6 @@ pub fn call_hierarchy_item( | |||
579 | Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) | 614 | Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) |
580 | } | 615 | } |
581 | 616 | ||
582 | #[cfg(test)] | ||
583 | mod tests { | ||
584 | use test_utils::extract_ranges; | ||
585 | |||
586 | use super::*; | ||
587 | |||
588 | #[test] | ||
589 | fn conv_fold_line_folding_only_fixup() { | ||
590 | let text = r#"<fold>mod a; | ||
591 | mod b; | ||
592 | mod c;</fold> | ||
593 | |||
594 | fn main() <fold>{ | ||
595 | if cond <fold>{ | ||
596 | a::do_a(); | ||
597 | }</fold> else <fold>{ | ||
598 | b::do_b(); | ||
599 | }</fold> | ||
600 | }</fold>"#; | ||
601 | |||
602 | let (ranges, text) = extract_ranges(text, "fold"); | ||
603 | assert_eq!(ranges.len(), 4); | ||
604 | let folds = vec![ | ||
605 | Fold { range: ranges[0], kind: FoldKind::Mods }, | ||
606 | Fold { range: ranges[1], kind: FoldKind::Block }, | ||
607 | Fold { range: ranges[2], kind: FoldKind::Block }, | ||
608 | Fold { range: ranges[3], kind: FoldKind::Block }, | ||
609 | ]; | ||
610 | |||
611 | let line_index = LineIndex::new(&text); | ||
612 | let converted: Vec<lsp_types::FoldingRange> = | ||
613 | folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect(); | ||
614 | |||
615 | let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)]; | ||
616 | assert_eq!(converted.len(), expected_lines.len()); | ||
617 | for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) { | ||
618 | assert_eq!(folding_range.start_line, *start_line); | ||
619 | assert_eq!(folding_range.start_character, None); | ||
620 | assert_eq!(folding_range.end_line, *end_line); | ||
621 | assert_eq!(folding_range.end_character, None); | ||
622 | } | ||
623 | } | ||
624 | } | ||
625 | |||
626 | pub(crate) fn unresolved_code_action( | 617 | pub(crate) fn unresolved_code_action( |
627 | snap: &GlobalStateSnapshot, | 618 | snap: &GlobalStateSnapshot, |
628 | assist: Assist, | 619 | assist: Assist, |
@@ -676,3 +667,62 @@ pub(crate) fn runnable( | |||
676 | }, | 667 | }, |
677 | }) | 668 | }) |
678 | } | 669 | } |
670 | |||
671 | #[cfg(test)] | ||
672 | mod tests { | ||
673 | use test_utils::extract_ranges; | ||
674 | |||
675 | use super::*; | ||
676 | |||
677 | #[test] | ||
678 | fn conv_fold_line_folding_only_fixup() { | ||
679 | let text = r#"<fold>mod a; | ||
680 | mod b; | ||
681 | mod c;</fold> | ||
682 | |||
683 | fn main() <fold>{ | ||
684 | if cond <fold>{ | ||
685 | a::do_a(); | ||
686 | }</fold> else <fold>{ | ||
687 | b::do_b(); | ||
688 | }</fold> | ||
689 | }</fold>"#; | ||
690 | |||
691 | let (ranges, text) = extract_ranges(text, "fold"); | ||
692 | assert_eq!(ranges.len(), 4); | ||
693 | let folds = vec![ | ||
694 | Fold { range: ranges[0], kind: FoldKind::Mods }, | ||
695 | Fold { range: ranges[1], kind: FoldKind::Block }, | ||
696 | Fold { range: ranges[2], kind: FoldKind::Block }, | ||
697 | Fold { range: ranges[3], kind: FoldKind::Block }, | ||
698 | ]; | ||
699 | |||
700 | let line_index = LineIndex::new(&text); | ||
701 | let converted: Vec<lsp_types::FoldingRange> = | ||
702 | folds.into_iter().map(|it| folding_range(&text, &line_index, true, it)).collect(); | ||
703 | |||
704 | let expected_lines = [(0, 2), (4, 10), (5, 6), (7, 9)]; | ||
705 | assert_eq!(converted.len(), expected_lines.len()); | ||
706 | for (folding_range, (start_line, end_line)) in converted.iter().zip(expected_lines.iter()) { | ||
707 | assert_eq!(folding_range.start_line, *start_line); | ||
708 | assert_eq!(folding_range.start_character, None); | ||
709 | assert_eq!(folding_range.end_line, *end_line); | ||
710 | assert_eq!(folding_range.end_character, None); | ||
711 | } | ||
712 | } | ||
713 | |||
714 | // `Url` is not able to parse windows paths on unix machines. | ||
715 | #[test] | ||
716 | #[cfg(target_os = "windows")] | ||
717 | fn test_lowercase_drive_letter_with_drive() { | ||
718 | let url = url_from_abs_path(Path::new("C:\\Test")); | ||
719 | assert_eq!(url.to_string(), "file:///c:/Test"); | ||
720 | } | ||
721 | |||
722 | #[test] | ||
723 | #[cfg(target_os = "windows")] | ||
724 | fn test_drive_without_colon_passthrough() { | ||
725 | let url = url_from_abs_path(Path::new(r#"\\localhost\C$\my_dir"#)); | ||
726 | assert_eq!(url.to_string(), "file://localhost/C$/my_dir"); | ||
727 | } | ||
728 | } | ||
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index c0356344c..f2ff0e435 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs | |||
@@ -1,5 +1,4 @@ | |||
1 | //! Missing batteries for standard libraries. | 1 | //! Missing batteries for standard libraries. |
2 | |||
3 | use std::{cell::Cell, fmt, time::Instant}; | 2 | use std::{cell::Cell, fmt, time::Instant}; |
4 | 3 | ||
5 | #[inline(always)] | 4 | #[inline(always)] |
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 @@ | |||
10 | pub mod mark; | 10 | pub mod mark; |
11 | 11 | ||
12 | use std::{ | 12 | use std::{ |
13 | fs, | 13 | env, fs, |
14 | path::{Path, PathBuf}, | 14 | path::{Path, PathBuf}, |
15 | }; | 15 | }; |
16 | 16 | ||
17 | pub use ra_cfg::CfgOptions; | 17 | use serde_json::Value; |
18 | use stdx::split1; | 18 | use stdx::split1; |
19 | use text_size::{TextRange, TextSize}; | ||
19 | 20 | ||
21 | pub use ra_cfg::CfgOptions; | ||
20 | pub use relative_path::{RelativePath, RelativePathBuf}; | 22 | pub use relative_path::{RelativePath, RelativePathBuf}; |
21 | pub use rustc_hash::FxHashMap; | 23 | pub use rustc_hash::FxHashMap; |
22 | use serde_json::Value; | ||
23 | use text_size::{TextRange, TextSize}; | ||
24 | 24 | ||
25 | pub use difference::Changeset as __Changeset; | 25 | pub 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 | ||
628 | const 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] | ||
2 | name = "vfs" | ||
3 | version = "0.1.0" | ||
4 | authors = ["rust-analyzer developers"] | ||
5 | edition = "2018" | ||
6 | |||
7 | [dependencies] | ||
8 | rustc-hash = "1.0" | ||
9 | jod-thread = "0.1.0" | ||
10 | walkdir = "2.3.1" | ||
11 | globset = "0.4.5" | ||
12 | crossbeam-channel = "0.4.0" | ||
13 | |||
14 | paths = { path = "../paths" } | ||
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs new file mode 100644 index 000000000..724606a3d --- /dev/null +++ b/crates/vfs/src/file_set.rs | |||
@@ -0,0 +1,113 @@ | |||
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`. | ||
5 | use std::{fmt, iter}; | ||
6 | |||
7 | use paths::AbsPathBuf; | ||
8 | use rustc_hash::FxHashMap; | ||
9 | |||
10 | use crate::{FileId, Vfs, VfsPath}; | ||
11 | |||
12 | #[derive(Default, Clone, Eq, PartialEq)] | ||
13 | pub struct FileSet { | ||
14 | files: FxHashMap<VfsPath, FileId>, | ||
15 | paths: FxHashMap<FileId, VfsPath>, | ||
16 | } | ||
17 | |||
18 | impl 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 | |||
35 | impl 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)] | ||
42 | pub struct FileSetConfig { | ||
43 | n_file_sets: usize, | ||
44 | roots: Vec<(AbsPathBuf, usize)>, | ||
45 | } | ||
46 | |||
47 | impl Default for FileSetConfig { | ||
48 | fn default() -> Self { | ||
49 | FileSetConfig::builder().build() | ||
50 | } | ||
51 | } | ||
52 | |||
53 | impl FileSetConfig { | ||
54 | pub fn builder() -> FileSetConfigBuilder { | ||
55 | FileSetConfigBuilder::default() | ||
56 | } | ||
57 | pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> { | ||
58 | let mut res = vec![FileSet::default(); self.len()]; | ||
59 | for (file_id, path) in vfs.iter() { | ||
60 | let root = self.classify(&path); | ||
61 | res[root].insert(file_id, path) | ||
62 | } | ||
63 | res | ||
64 | } | ||
65 | fn len(&self) -> usize { | ||
66 | self.n_file_sets | ||
67 | } | ||
68 | fn classify(&self, path: &VfsPath) -> usize { | ||
69 | let path = match path.as_path() { | ||
70 | Some(it) => it, | ||
71 | None => return self.len() - 1, | ||
72 | }; | ||
73 | let idx = match self.roots.binary_search_by(|(p, _)| p.as_path().cmp(path)) { | ||
74 | Ok(it) => it, | ||
75 | Err(it) => it.saturating_sub(1), | ||
76 | }; | ||
77 | if path.starts_with(&self.roots[idx].0) { | ||
78 | self.roots[idx].1 | ||
79 | } else { | ||
80 | self.len() - 1 | ||
81 | } | ||
82 | } | ||
83 | } | ||
84 | |||
85 | pub struct FileSetConfigBuilder { | ||
86 | roots: Vec<Vec<AbsPathBuf>>, | ||
87 | } | ||
88 | |||
89 | impl Default for FileSetConfigBuilder { | ||
90 | fn default() -> Self { | ||
91 | FileSetConfigBuilder { roots: Vec::new() } | ||
92 | } | ||
93 | } | ||
94 | |||
95 | impl FileSetConfigBuilder { | ||
96 | pub fn len(&self) -> usize { | ||
97 | self.roots.len() | ||
98 | } | ||
99 | pub fn add_file_set(&mut self, roots: Vec<AbsPathBuf>) { | ||
100 | self.roots.push(roots) | ||
101 | } | ||
102 | pub fn build(self) -> FileSetConfig { | ||
103 | let n_file_sets = self.roots.len() + 1; | ||
104 | let mut roots: Vec<(AbsPathBuf, usize)> = self | ||
105 | .roots | ||
106 | .into_iter() | ||
107 | .enumerate() | ||
108 | .flat_map(|(i, paths)| paths.into_iter().zip(iter::repeat(i))) | ||
109 | .collect(); | ||
110 | roots.sort(); | ||
111 | FileSetConfig { n_file_sets, roots } | ||
112 | } | ||
113 | } | ||
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs new file mode 100644 index 000000000..055219b0c --- /dev/null +++ b/crates/vfs/src/lib.rs | |||
@@ -0,0 +1,141 @@ | |||
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. | ||
37 | mod vfs_path; | ||
38 | mod path_interner; | ||
39 | pub mod file_set; | ||
40 | pub mod loader; | ||
41 | pub mod walkdir_loader; | ||
42 | |||
43 | use std::{fmt, mem}; | ||
44 | |||
45 | use crate::path_interner::PathInterner; | ||
46 | |||
47 | pub use crate::vfs_path::VfsPath; | ||
48 | pub use paths::{AbsPath, AbsPathBuf}; | ||
49 | |||
50 | #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] | ||
51 | pub struct FileId(pub u32); | ||
52 | |||
53 | #[derive(Default)] | ||
54 | pub struct Vfs { | ||
55 | interner: PathInterner, | ||
56 | data: Vec<Option<Vec<u8>>>, | ||
57 | changes: Vec<ChangedFile>, | ||
58 | } | ||
59 | |||
60 | pub struct ChangedFile { | ||
61 | pub file_id: FileId, | ||
62 | pub change_kind: ChangeKind, | ||
63 | } | ||
64 | |||
65 | impl 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)] | ||
75 | pub enum ChangeKind { | ||
76 | Create, | ||
77 | Modify, | ||
78 | Delete, | ||
79 | } | ||
80 | |||
81 | impl Vfs { | ||
82 | pub fn len(&self) -> usize { | ||
83 | self.data.len() | ||
84 | } | ||
85 | pub fn file_id(&self, path: &VfsPath) -> Option<FileId> { | ||
86 | self.interner.get(path).filter(|&it| self.get(it).is_some()) | ||
87 | } | ||
88 | pub fn file_path(&self, file_id: FileId) -> VfsPath { | ||
89 | self.interner.lookup(file_id).clone() | ||
90 | } | ||
91 | pub fn file_contents(&self, file_id: FileId) -> &[u8] { | ||
92 | self.get(file_id).as_deref().unwrap() | ||
93 | } | ||
94 | pub fn iter(&self) -> impl Iterator<Item = (FileId, VfsPath)> + '_ { | ||
95 | (0..self.data.len()) | ||
96 | .map(|it| FileId(it as u32)) | ||
97 | .filter(move |&file_id| self.get(file_id).is_some()) | ||
98 | .map(move |file_id| { | ||
99 | let path = self.interner.lookup(file_id).clone(); | ||
100 | (file_id, path) | ||
101 | }) | ||
102 | } | ||
103 | pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) { | ||
104 | let file_id = self.alloc_file_id(path); | ||
105 | let change_kind = match (&self.get(file_id), &contents) { | ||
106 | (None, None) => return, | ||
107 | (None, Some(_)) => ChangeKind::Create, | ||
108 | (Some(_), None) => ChangeKind::Delete, | ||
109 | (Some(old), Some(new)) if old == new => return, | ||
110 | (Some(_), Some(_)) => ChangeKind::Modify, | ||
111 | }; | ||
112 | |||
113 | *self.get_mut(file_id) = contents; | ||
114 | self.changes.push(ChangedFile { file_id, change_kind }) | ||
115 | } | ||
116 | pub fn has_changes(&self) -> bool { | ||
117 | !self.changes.is_empty() | ||
118 | } | ||
119 | pub fn take_changes(&mut self) -> Vec<ChangedFile> { | ||
120 | mem::take(&mut self.changes) | ||
121 | } | ||
122 | fn alloc_file_id(&mut self, path: VfsPath) -> FileId { | ||
123 | let file_id = self.interner.intern(path); | ||
124 | let idx = file_id.0 as usize; | ||
125 | let len = self.data.len().max(idx + 1); | ||
126 | self.data.resize_with(len, || None); | ||
127 | file_id | ||
128 | } | ||
129 | fn get(&self, file_id: FileId) -> &Option<Vec<u8>> { | ||
130 | &self.data[file_id.0 as usize] | ||
131 | } | ||
132 | fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> { | ||
133 | &mut self.data[file_id.0 as usize] | ||
134 | } | ||
135 | } | ||
136 | |||
137 | impl fmt::Debug for Vfs { | ||
138 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
139 | f.debug_struct("Vfs").field("n_files", &self.data.len()).finish() | ||
140 | } | ||
141 | } | ||
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. | ||
2 | use std::fmt; | ||
3 | |||
4 | use paths::AbsPathBuf; | ||
5 | |||
6 | pub enum Entry { | ||
7 | Files(Vec<AbsPathBuf>), | ||
8 | Directory { path: AbsPathBuf, globs: Vec<String> }, | ||
9 | } | ||
10 | |||
11 | pub struct Config { | ||
12 | pub load: Vec<Entry>, | ||
13 | pub watch: Vec<usize>, | ||
14 | } | ||
15 | |||
16 | pub enum Message { | ||
17 | DidSwitchConfig { n_entries: usize }, | ||
18 | DidLoadAllEntries, | ||
19 | Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> }, | ||
20 | } | ||
21 | |||
22 | pub type Sender = Box<dyn Fn(Message) + Send>; | ||
23 | |||
24 | pub 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 | |||
33 | impl 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 | |||
48 | fn globs(globs: &[&str]) -> Vec<String> { | ||
49 | globs.iter().map(|it| it.to_string()).collect() | ||
50 | } | ||
51 | |||
52 | impl 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] | ||
67 | fn 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. | ||
4 | use rustc_hash::FxHashMap; | ||
5 | |||
6 | use crate::{FileId, VfsPath}; | ||
7 | |||
8 | #[derive(Default)] | ||
9 | pub(crate) struct PathInterner { | ||
10 | map: FxHashMap<VfsPath, FileId>, | ||
11 | vec: Vec<VfsPath>, | ||
12 | } | ||
13 | |||
14 | impl 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. | ||
2 | use std::fmt; | ||
3 | |||
4 | use 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)] | ||
9 | pub struct VfsPath(VfsPathRepr); | ||
10 | |||
11 | impl 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)] | ||
33 | enum VfsPathRepr { | ||
34 | PathBuf(AbsPathBuf), | ||
35 | } | ||
36 | |||
37 | impl From<AbsPathBuf> for VfsPath { | ||
38 | fn from(v: AbsPathBuf) -> Self { | ||
39 | VfsPath(VfsPathRepr::PathBuf(v)) | ||
40 | } | ||
41 | } | ||
42 | |||
43 | impl 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. | ||
3 | use std::convert::TryFrom; | ||
4 | |||
5 | use globset::{Glob, GlobSetBuilder}; | ||
6 | use paths::{AbsPath, AbsPathBuf}; | ||
7 | use walkdir::WalkDir; | ||
8 | |||
9 | use crate::loader; | ||
10 | |||
11 | #[derive(Debug)] | ||
12 | pub struct WalkdirLoaderHandle { | ||
13 | // Relative order of fields below is significant. | ||
14 | sender: crossbeam_channel::Sender<Message>, | ||
15 | _thread: jod_thread::JoinHandle, | ||
16 | } | ||
17 | |||
18 | enum Message { | ||
19 | Config(loader::Config), | ||
20 | Invalidate(AbsPathBuf), | ||
21 | } | ||
22 | |||
23 | impl 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 | |||
41 | struct WalkdirLoaderActor { | ||
42 | sender: loader::Sender, | ||
43 | } | ||
44 | |||
45 | impl 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 | |||
106 | fn read(path: &AbsPath) -> Option<Vec<u8>> { | ||
107 | std::fs::read(path).ok() | ||
108 | } | ||