diff options
50 files changed, 1399 insertions, 419 deletions
diff --git a/Cargo.lock b/Cargo.lock index de361cc23..dc49fc4bd 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -125,9 +125,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" | |||
125 | 125 | ||
126 | [[package]] | 126 | [[package]] |
127 | name = "chalk-derive" | 127 | name = "chalk-derive" |
128 | version = "0.19.0" | 128 | version = "0.21.0" |
129 | source = "registry+https://github.com/rust-lang/crates.io-index" | 129 | source = "registry+https://github.com/rust-lang/crates.io-index" |
130 | checksum = "654c611946ba2629c5028cb7708687af975faf2c29d731824cb294c873df4697" | 130 | checksum = "c1df0dbb57d74b4acd20f20fa66ab2acd09776b79eaeb9d8f947b2f3e01c40bf" |
131 | dependencies = [ | 131 | dependencies = [ |
132 | "proc-macro2", | 132 | "proc-macro2", |
133 | "quote", | 133 | "quote", |
@@ -137,9 +137,9 @@ dependencies = [ | |||
137 | 137 | ||
138 | [[package]] | 138 | [[package]] |
139 | name = "chalk-ir" | 139 | name = "chalk-ir" |
140 | version = "0.19.0" | 140 | version = "0.21.0" |
141 | source = "registry+https://github.com/rust-lang/crates.io-index" | 141 | source = "registry+https://github.com/rust-lang/crates.io-index" |
142 | checksum = "0a5341fbc654ca886b73b804a36aebf0e621057ccc1a68e9815b5b39b3ac9ae8" | 142 | checksum = "44361a25dbdb1dc428f56ad7a3c21ba9ca12f3225c26a47919ff6fcb10a583d4" |
143 | dependencies = [ | 143 | dependencies = [ |
144 | "chalk-derive", | 144 | "chalk-derive", |
145 | "lazy_static", | 145 | "lazy_static", |
@@ -147,9 +147,9 @@ dependencies = [ | |||
147 | 147 | ||
148 | [[package]] | 148 | [[package]] |
149 | name = "chalk-recursive" | 149 | name = "chalk-recursive" |
150 | version = "0.19.0" | 150 | version = "0.21.0" |
151 | source = "registry+https://github.com/rust-lang/crates.io-index" | 151 | source = "registry+https://github.com/rust-lang/crates.io-index" |
152 | checksum = "4484807b155b5a411e6135d330295f9ba5042e2920b8712c6574ca6ea91e9ee5" | 152 | checksum = "dd89556b98de156d5eaf21077d297cd2198628f10f2df140798ea3a5dd84bc86" |
153 | dependencies = [ | 153 | dependencies = [ |
154 | "chalk-derive", | 154 | "chalk-derive", |
155 | "chalk-ir", | 155 | "chalk-ir", |
@@ -160,9 +160,9 @@ dependencies = [ | |||
160 | 160 | ||
161 | [[package]] | 161 | [[package]] |
162 | name = "chalk-solve" | 162 | name = "chalk-solve" |
163 | version = "0.19.0" | 163 | version = "0.21.0" |
164 | source = "registry+https://github.com/rust-lang/crates.io-index" | 164 | source = "registry+https://github.com/rust-lang/crates.io-index" |
165 | checksum = "281f82facd2538997fbe52132b1941ed213d266748215c31d15f62a8664429ad" | 165 | checksum = "a886da37a0dc457057d86f78f026f7a09c6d8088aa13f4f4127fdb8dc80119a3" |
166 | dependencies = [ | 166 | dependencies = [ |
167 | "chalk-derive", | 167 | "chalk-derive", |
168 | "chalk-ir", | 168 | "chalk-ir", |
@@ -318,9 +318,9 @@ dependencies = [ | |||
318 | 318 | ||
319 | [[package]] | 319 | [[package]] |
320 | name = "filetime" | 320 | name = "filetime" |
321 | version = "0.2.11" | 321 | version = "0.2.12" |
322 | source = "registry+https://github.com/rust-lang/crates.io-index" | 322 | source = "registry+https://github.com/rust-lang/crates.io-index" |
323 | checksum = "e500da2fab70bdc43f8f0e0b350a227f31c72311c56aba48f01d5cd62bb0345b" | 323 | checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e" |
324 | dependencies = [ | 324 | dependencies = [ |
325 | "cfg-if", | 325 | "cfg-if", |
326 | "libc", | 326 | "libc", |
@@ -1371,9 +1371,9 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" | |||
1371 | 1371 | ||
1372 | [[package]] | 1372 | [[package]] |
1373 | name = "salsa" | 1373 | name = "salsa" |
1374 | version = "0.15.1" | 1374 | version = "0.15.2" |
1375 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1376 | checksum = "d4cdc109fcc9e9450c7ef47fb7474e99bffd51799da03ed0a6c7f0e2cb3848a6" | 1376 | checksum = "9ab29056d4fb4048a5f0d169c9b6e5526160c9ec37aded5a6879c2c9c445a8e4" |
1377 | dependencies = [ | 1377 | dependencies = [ |
1378 | "crossbeam-utils", | 1378 | "crossbeam-utils", |
1379 | "indexmap", | 1379 | "indexmap", |
@@ -1388,9 +1388,9 @@ dependencies = [ | |||
1388 | 1388 | ||
1389 | [[package]] | 1389 | [[package]] |
1390 | name = "salsa-macros" | 1390 | name = "salsa-macros" |
1391 | version = "0.15.0" | 1391 | version = "0.15.2" |
1392 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1393 | checksum = "2c280ac85b15ac214b86ac4b407626a48e6a1c4f90769a582fec74aa57942b9f" | 1393 | checksum = "a1c3aec007c63c4ed4cd7a018529fb0b5575c4562575fc6a40d6cd2ae0b792ef" |
1394 | dependencies = [ | 1394 | dependencies = [ |
1395 | "heck", | 1395 | "heck", |
1396 | "proc-macro2", | 1396 | "proc-macro2", |
@@ -1533,9 +1533,9 @@ version = "0.1.0" | |||
1533 | 1533 | ||
1534 | [[package]] | 1534 | [[package]] |
1535 | name = "syn" | 1535 | name = "syn" |
1536 | version = "1.0.36" | 1536 | version = "1.0.38" |
1537 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1538 | checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" | 1538 | checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" |
1539 | dependencies = [ | 1539 | dependencies = [ |
1540 | "proc-macro2", | 1540 | "proc-macro2", |
1541 | "quote", | 1541 | "quote", |
@@ -1644,9 +1644,9 @@ dependencies = [ | |||
1644 | 1644 | ||
1645 | [[package]] | 1645 | [[package]] |
1646 | name = "tracing-core" | 1646 | name = "tracing-core" |
1647 | version = "0.1.12" | 1647 | version = "0.1.13" |
1648 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1649 | checksum = "b2734b5a028fa697686f16c6d18c2c6a3c7e41513f9a213abb6754c4acb3c8d7" | 1649 | checksum = "d593f98af59ebc017c0648f0117525db358745a8894a8d684e185ba3f45954f9" |
1650 | dependencies = [ | 1650 | dependencies = [ |
1651 | "lazy_static", | 1651 | "lazy_static", |
1652 | ] | 1652 | ] |
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index 3407df856..afd3fd4b9 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs | |||
@@ -73,6 +73,10 @@ impl<'a> AssistContext<'a> { | |||
73 | self.sema.db | 73 | self.sema.db |
74 | } | 74 | } |
75 | 75 | ||
76 | pub(crate) fn source_file(&self) -> &SourceFile { | ||
77 | &self.source_file | ||
78 | } | ||
79 | |||
76 | // NB, this ignores active selection. | 80 | // NB, this ignores active selection. |
77 | pub(crate) fn offset(&self) -> TextSize { | 81 | pub(crate) fn offset(&self) -> TextSize { |
78 | self.frange.range.start() | 82 | self.frange.range.start() |
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs index 28f3f3546..15ec75c95 100644 --- a/crates/ra_assists/src/ast_transform.rs +++ b/crates/ra_assists/src/ast_transform.rs | |||
@@ -63,7 +63,7 @@ impl<'a> SubstituteTypeParams<'a> { | |||
63 | let default = k.default(source_scope.db)?; | 63 | let default = k.default(source_scope.db)?; |
64 | Some(( | 64 | Some(( |
65 | k, | 65 | k, |
66 | ast::make::type_ref( | 66 | ast::make::ty( |
67 | &default | 67 | &default |
68 | .display_source_code(source_scope.db, source_scope.module()?.into()) | 68 | .display_source_code(source_scope.db, source_scope.module()?.into()) |
69 | .ok()?, | 69 | .ok()?, |
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index 69852b611..6816a2709 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs | |||
@@ -123,7 +123,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) | |||
123 | let happy_arm = { | 123 | let happy_arm = { |
124 | let pat = make::tuple_struct_pat( | 124 | let pat = make::tuple_struct_pat( |
125 | path, | 125 | path, |
126 | once(make::bind_pat(make::name("it")).into()), | 126 | once(make::ident_pat(make::name("it")).into()), |
127 | ); | 127 | ); |
128 | let expr = { | 128 | let expr = { |
129 | let name_ref = make::name_ref("it"); | 129 | let name_ref = make::name_ref("it"); |
@@ -136,7 +136,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) | |||
136 | 136 | ||
137 | let sad_arm = make::match_arm( | 137 | let sad_arm = make::match_arm( |
138 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate | 138 | // FIXME: would be cool to use `None` or `Err(_)` if appropriate |
139 | once(make::placeholder_pat().into()), | 139 | once(make::wildcard_pat().into()), |
140 | early_expression, | 140 | early_expression, |
141 | ); | 141 | ); |
142 | 142 | ||
@@ -144,7 +144,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) | |||
144 | }; | 144 | }; |
145 | 145 | ||
146 | let let_stmt = make::let_stmt( | 146 | let let_stmt = make::let_stmt( |
147 | make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(), | 147 | make::ident_pat(make::name(&bound_ident.syntax().to_string())).into(), |
148 | Some(match_expr), | 148 | Some(match_expr), |
149 | ); | 149 | ); |
150 | let let_stmt = let_stmt.indent(if_indent_level); | 150 | let let_stmt = let_stmt.indent(if_indent_level); |
diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs new file mode 100644 index 000000000..eb216a81a --- /dev/null +++ b/crates/ra_assists/src/handlers/expand_glob_import.rs | |||
@@ -0,0 +1,391 @@ | |||
1 | use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; | ||
2 | use ra_ide_db::{ | ||
3 | defs::{classify_name_ref, Definition, NameRefClass}, | ||
4 | RootDatabase, | ||
5 | }; | ||
6 | use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T}; | ||
7 | |||
8 | use crate::{ | ||
9 | assist_context::{AssistBuilder, AssistContext, Assists}, | ||
10 | AssistId, AssistKind, | ||
11 | }; | ||
12 | |||
13 | use either::Either; | ||
14 | |||
15 | // Assist: expand_glob_import | ||
16 | // | ||
17 | // Expands glob imports. | ||
18 | // | ||
19 | // ``` | ||
20 | // mod foo { | ||
21 | // pub struct Bar; | ||
22 | // pub struct Baz; | ||
23 | // } | ||
24 | // | ||
25 | // use foo::*<|>; | ||
26 | // | ||
27 | // fn qux(bar: Bar, baz: Baz) {} | ||
28 | // ``` | ||
29 | // -> | ||
30 | // ``` | ||
31 | // mod foo { | ||
32 | // pub struct Bar; | ||
33 | // pub struct Baz; | ||
34 | // } | ||
35 | // | ||
36 | // use foo::{Baz, Bar}; | ||
37 | // | ||
38 | // fn qux(bar: Bar, baz: Baz) {} | ||
39 | // ``` | ||
40 | pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
41 | let star = ctx.find_token_at_offset(T![*])?; | ||
42 | let mod_path = find_mod_path(&star)?; | ||
43 | |||
44 | let source_file = ctx.source_file(); | ||
45 | let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); | ||
46 | |||
47 | let defs_in_mod = find_defs_in_mod(ctx, scope, &mod_path)?; | ||
48 | let name_refs_in_source_file = | ||
49 | source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect(); | ||
50 | let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file); | ||
51 | |||
52 | let parent = star.parent().parent()?; | ||
53 | acc.add( | ||
54 | AssistId("expand_glob_import", AssistKind::RefactorRewrite), | ||
55 | "Expand glob import", | ||
56 | parent.text_range(), | ||
57 | |builder| { | ||
58 | replace_ast(builder, &parent, mod_path, used_names); | ||
59 | }, | ||
60 | ) | ||
61 | } | ||
62 | |||
63 | fn find_mod_path(star: &SyntaxToken) -> Option<ast::Path> { | ||
64 | star.ancestors().find_map(|n| ast::UseTree::cast(n).and_then(|u| u.path())) | ||
65 | } | ||
66 | |||
67 | #[derive(PartialEq)] | ||
68 | enum Def { | ||
69 | ModuleDef(ModuleDef), | ||
70 | MacroDef(MacroDef), | ||
71 | } | ||
72 | |||
73 | impl Def { | ||
74 | fn name(&self, db: &RootDatabase) -> Option<Name> { | ||
75 | match self { | ||
76 | Def::ModuleDef(def) => def.name(db), | ||
77 | Def::MacroDef(def) => def.name(db), | ||
78 | } | ||
79 | } | ||
80 | } | ||
81 | |||
82 | fn find_defs_in_mod( | ||
83 | ctx: &AssistContext, | ||
84 | from: SemanticsScope<'_>, | ||
85 | path: &ast::Path, | ||
86 | ) -> Option<Vec<Def>> { | ||
87 | let hir_path = ctx.sema.lower_path(&path)?; | ||
88 | let module = if let Some(PathResolution::Def(ModuleDef::Module(module))) = | ||
89 | from.resolve_hir_path_qualifier(&hir_path) | ||
90 | { | ||
91 | module | ||
92 | } else { | ||
93 | return None; | ||
94 | }; | ||
95 | |||
96 | let module_scope = module.scope(ctx.db(), from.module()); | ||
97 | |||
98 | let mut defs = vec![]; | ||
99 | for (_, def) in module_scope { | ||
100 | match def { | ||
101 | ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)), | ||
102 | ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)), | ||
103 | _ => continue, | ||
104 | } | ||
105 | } | ||
106 | |||
107 | Some(defs) | ||
108 | } | ||
109 | |||
110 | fn find_used_names( | ||
111 | ctx: &AssistContext, | ||
112 | defs_in_mod: Vec<Def>, | ||
113 | name_refs_in_source_file: Vec<ast::NameRef>, | ||
114 | ) -> Vec<Name> { | ||
115 | let defs_in_source_file = name_refs_in_source_file | ||
116 | .iter() | ||
117 | .filter_map(|r| classify_name_ref(&ctx.sema, r)) | ||
118 | .filter_map(|rc| match rc { | ||
119 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), | ||
120 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), | ||
121 | _ => None, | ||
122 | }) | ||
123 | .collect::<Vec<Def>>(); | ||
124 | |||
125 | defs_in_mod | ||
126 | .iter() | ||
127 | .filter(|def| { | ||
128 | if let Def::ModuleDef(ModuleDef::Trait(tr)) = def { | ||
129 | for item in tr.items(ctx.db()) { | ||
130 | if let AssocItem::Function(f) = item { | ||
131 | if defs_in_source_file.contains(&Def::ModuleDef(ModuleDef::Function(f))) { | ||
132 | return true; | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | } | ||
137 | |||
138 | defs_in_source_file.contains(def) | ||
139 | }) | ||
140 | .filter_map(|d| d.name(ctx.db())) | ||
141 | .collect() | ||
142 | } | ||
143 | |||
144 | fn replace_ast( | ||
145 | builder: &mut AssistBuilder, | ||
146 | node: &SyntaxNode, | ||
147 | path: ast::Path, | ||
148 | used_names: Vec<Name>, | ||
149 | ) { | ||
150 | let replacement: Either<ast::UseTree, ast::UseTreeList> = match used_names.as_slice() { | ||
151 | [name] => Either::Left(ast::make::use_tree( | ||
152 | ast::make::path_from_text(&format!("{}::{}", path, name)), | ||
153 | None, | ||
154 | None, | ||
155 | false, | ||
156 | )), | ||
157 | names => Either::Right(ast::make::use_tree_list(names.iter().map(|n| { | ||
158 | ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) | ||
159 | }))), | ||
160 | }; | ||
161 | |||
162 | let mut replace_node = |replacement: Either<ast::UseTree, ast::UseTreeList>| { | ||
163 | algo::diff(node, &replacement.either(|u| u.syntax().clone(), |ut| ut.syntax().clone())) | ||
164 | .into_text_edit(builder.text_edit_builder()); | ||
165 | }; | ||
166 | |||
167 | match_ast! { | ||
168 | match node { | ||
169 | ast::UseTree(use_tree) => { | ||
170 | replace_node(replacement); | ||
171 | }, | ||
172 | ast::UseTreeList(use_tree_list) => { | ||
173 | replace_node(replacement); | ||
174 | }, | ||
175 | ast::Use(use_item) => { | ||
176 | builder.replace_ast(use_item, ast::make::use_(replacement.left_or_else(|ut| ast::make::use_tree(path, Some(ut), None, false)))); | ||
177 | }, | ||
178 | _ => {}, | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | |||
183 | #[cfg(test)] | ||
184 | mod tests { | ||
185 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
186 | |||
187 | use super::*; | ||
188 | |||
189 | #[test] | ||
190 | fn expanding_glob_import() { | ||
191 | check_assist( | ||
192 | expand_glob_import, | ||
193 | r" | ||
194 | mod foo { | ||
195 | pub struct Bar; | ||
196 | pub struct Baz; | ||
197 | pub struct Qux; | ||
198 | |||
199 | pub fn f() {} | ||
200 | } | ||
201 | |||
202 | use foo::*<|>; | ||
203 | |||
204 | fn qux(bar: Bar, baz: Baz) { | ||
205 | f(); | ||
206 | } | ||
207 | ", | ||
208 | r" | ||
209 | mod foo { | ||
210 | pub struct Bar; | ||
211 | pub struct Baz; | ||
212 | pub struct Qux; | ||
213 | |||
214 | pub fn f() {} | ||
215 | } | ||
216 | |||
217 | use foo::{Baz, Bar, f}; | ||
218 | |||
219 | fn qux(bar: Bar, baz: Baz) { | ||
220 | f(); | ||
221 | } | ||
222 | ", | ||
223 | ) | ||
224 | } | ||
225 | |||
226 | #[test] | ||
227 | fn expanding_glob_import_with_existing_explicit_names() { | ||
228 | check_assist( | ||
229 | expand_glob_import, | ||
230 | r" | ||
231 | mod foo { | ||
232 | pub struct Bar; | ||
233 | pub struct Baz; | ||
234 | pub struct Qux; | ||
235 | |||
236 | pub fn f() {} | ||
237 | } | ||
238 | |||
239 | use foo::{*<|>, f}; | ||
240 | |||
241 | fn qux(bar: Bar, baz: Baz) { | ||
242 | f(); | ||
243 | } | ||
244 | ", | ||
245 | r" | ||
246 | mod foo { | ||
247 | pub struct Bar; | ||
248 | pub struct Baz; | ||
249 | pub struct Qux; | ||
250 | |||
251 | pub fn f() {} | ||
252 | } | ||
253 | |||
254 | use foo::{Baz, Bar, f}; | ||
255 | |||
256 | fn qux(bar: Bar, baz: Baz) { | ||
257 | f(); | ||
258 | } | ||
259 | ", | ||
260 | ) | ||
261 | } | ||
262 | |||
263 | #[test] | ||
264 | fn expanding_nested_glob_import() { | ||
265 | check_assist( | ||
266 | expand_glob_import, | ||
267 | r" | ||
268 | mod foo { | ||
269 | mod bar { | ||
270 | pub struct Bar; | ||
271 | pub struct Baz; | ||
272 | pub struct Qux; | ||
273 | |||
274 | pub fn f() {} | ||
275 | } | ||
276 | |||
277 | mod baz { | ||
278 | pub fn g() {} | ||
279 | } | ||
280 | } | ||
281 | |||
282 | use foo::{bar::{*<|>, f}, baz::*}; | ||
283 | |||
284 | fn qux(bar: Bar, baz: Baz) { | ||
285 | f(); | ||
286 | g(); | ||
287 | } | ||
288 | ", | ||
289 | r" | ||
290 | mod foo { | ||
291 | mod bar { | ||
292 | pub struct Bar; | ||
293 | pub struct Baz; | ||
294 | pub struct Qux; | ||
295 | |||
296 | pub fn f() {} | ||
297 | } | ||
298 | |||
299 | mod baz { | ||
300 | pub fn g() {} | ||
301 | } | ||
302 | } | ||
303 | |||
304 | use foo::{bar::{Baz, Bar, f}, baz::*}; | ||
305 | |||
306 | fn qux(bar: Bar, baz: Baz) { | ||
307 | f(); | ||
308 | g(); | ||
309 | } | ||
310 | ", | ||
311 | ) | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn expanding_glob_import_with_macro_defs() { | ||
316 | check_assist( | ||
317 | expand_glob_import, | ||
318 | r" | ||
319 | //- /lib.rs crate:foo | ||
320 | #[macro_export] | ||
321 | macro_rules! bar { | ||
322 | () => () | ||
323 | } | ||
324 | |||
325 | pub fn baz() {} | ||
326 | |||
327 | //- /main.rs crate:main deps:foo | ||
328 | use foo::*<|>; | ||
329 | |||
330 | fn main() { | ||
331 | bar!(); | ||
332 | baz(); | ||
333 | } | ||
334 | ", | ||
335 | r" | ||
336 | use foo::{bar, baz}; | ||
337 | |||
338 | fn main() { | ||
339 | bar!(); | ||
340 | baz(); | ||
341 | } | ||
342 | ", | ||
343 | ) | ||
344 | } | ||
345 | |||
346 | #[test] | ||
347 | fn expanding_glob_import_with_trait_method_uses() { | ||
348 | check_assist( | ||
349 | expand_glob_import, | ||
350 | r" | ||
351 | //- /lib.rs crate:foo | ||
352 | pub trait Tr { | ||
353 | fn method(&self) {} | ||
354 | } | ||
355 | impl Tr for () {} | ||
356 | |||
357 | //- /main.rs crate:main deps:foo | ||
358 | use foo::*<|>; | ||
359 | |||
360 | fn main() { | ||
361 | ().method(); | ||
362 | } | ||
363 | ", | ||
364 | r" | ||
365 | use foo::Tr; | ||
366 | |||
367 | fn main() { | ||
368 | ().method(); | ||
369 | } | ||
370 | ", | ||
371 | ) | ||
372 | } | ||
373 | |||
374 | #[test] | ||
375 | fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() { | ||
376 | check_assist_not_applicable( | ||
377 | expand_glob_import, | ||
378 | r" | ||
379 | mod foo { | ||
380 | pub struct Bar; | ||
381 | pub struct Baz; | ||
382 | pub struct Qux; | ||
383 | } | ||
384 | |||
385 | use foo::Bar<|>; | ||
386 | |||
387 | fn qux(bar: Bar, baz: Baz) {} | ||
388 | ", | ||
389 | ) | ||
390 | } | ||
391 | } | ||
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs index b2e14f9d7..6698d1a27 100644 --- a/crates/ra_assists/src/handlers/fill_match_arms.rs +++ b/crates/ra_assists/src/handlers/fill_match_arms.rs | |||
@@ -197,12 +197,11 @@ fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> O | |||
197 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though | 197 | // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though |
198 | let pat: ast::Pat = match var.source(db).value.kind() { | 198 | let pat: ast::Pat = match var.source(db).value.kind() { |
199 | ast::StructKind::Tuple(field_list) => { | 199 | ast::StructKind::Tuple(field_list) => { |
200 | let pats = | 200 | let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count()); |
201 | iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count()); | ||
202 | make::tuple_struct_pat(path, pats).into() | 201 | make::tuple_struct_pat(path, pats).into() |
203 | } | 202 | } |
204 | ast::StructKind::Record(field_list) => { | 203 | ast::StructKind::Record(field_list) => { |
205 | let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into()); | 204 | let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into()); |
206 | make::record_pat(path, pats).into() | 205 | make::record_pat(path, pats).into() |
207 | } | 206 | } |
208 | ast::StructKind::Unit => make::path_pat(path), | 207 | ast::StructKind::Unit => make::path_pat(path), |
diff --git a/crates/ra_assists/src/handlers/generate_function.rs b/crates/ra_assists/src/handlers/generate_function.rs index 56510861d..acc97e648 100644 --- a/crates/ra_assists/src/handlers/generate_function.rs +++ b/crates/ra_assists/src/handlers/generate_function.rs | |||
@@ -142,7 +142,7 @@ impl FunctionBuilder { | |||
142 | let fn_body = make::block_expr(vec![], Some(placeholder_expr)); | 142 | let fn_body = make::block_expr(vec![], Some(placeholder_expr)); |
143 | let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; | 143 | let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None }; |
144 | let mut fn_def = | 144 | let mut fn_def = |
145 | make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body); | 145 | make::fn_(visibility, self.fn_name, self.type_params, self.params, fn_body); |
146 | let leading_ws; | 146 | let leading_ws; |
147 | let trailing_ws; | 147 | let trailing_ws; |
148 | 148 | ||
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs index 4e8a0c2db..4c797178f 100644 --- a/crates/ra_assists/src/handlers/raw_string.rs +++ b/crates/ra_assists/src/handlers/raw_string.rs | |||
@@ -173,7 +173,7 @@ fn test_required_hashes() { | |||
173 | } | 173 | } |
174 | 174 | ||
175 | #[cfg(test)] | 175 | #[cfg(test)] |
176 | mod test { | 176 | mod tests { |
177 | use test_utils::mark; | 177 | use test_utils::mark; |
178 | 178 | ||
179 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 179 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs index b7e30a7f2..ecafb74a1 100644 --- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs | |||
@@ -65,7 +65,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) | |||
65 | .type_of_pat(&pat) | 65 | .type_of_pat(&pat) |
66 | .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) | 66 | .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty)) |
67 | .map(|it| it.sad_pattern()) | 67 | .map(|it| it.sad_pattern()) |
68 | .unwrap_or_else(|| make::placeholder_pat().into()); | 68 | .unwrap_or_else(|| make::wildcard_pat().into()); |
69 | let else_expr = unwrap_trivial_block(else_block); | 69 | let else_expr = unwrap_trivial_block(else_block); |
70 | make::match_arm(vec![pattern], else_expr) | 70 | make::match_arm(vec![pattern], else_expr) |
71 | }; | 71 | }; |
diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs index 64ad15a23..e4d436dec 100644 --- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs | |||
@@ -50,10 +50,10 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> | |||
50 | target, | 50 | target, |
51 | |edit| { | 51 | |edit| { |
52 | let with_placeholder: ast::Pat = match happy_variant { | 52 | let with_placeholder: ast::Pat = match happy_variant { |
53 | None => make::placeholder_pat().into(), | 53 | None => make::wildcard_pat().into(), |
54 | Some(var_name) => make::tuple_struct_pat( | 54 | Some(var_name) => make::tuple_struct_pat( |
55 | make::path_unqualified(make::path_segment(make::name_ref(var_name))), | 55 | make::path_unqualified(make::path_segment(make::name_ref(var_name))), |
56 | once(make::placeholder_pat().into()), | 56 | once(make::wildcard_pat().into()), |
57 | ) | 57 | ) |
58 | .into(), | 58 | .into(), |
59 | }; | 59 | }; |
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 53496ede1..da0a860c5 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 | |||
@@ -643,4 +643,46 @@ fn main() { | |||
643 | ", | 643 | ", |
644 | ); | 644 | ); |
645 | } | 645 | } |
646 | |||
647 | #[test] | ||
648 | fn does_not_replace_pub_use() { | ||
649 | check_assist( | ||
650 | replace_qualified_name_with_use, | ||
651 | r" | ||
652 | pub use std::fmt; | ||
653 | |||
654 | impl std::io<|> for Foo { | ||
655 | } | ||
656 | ", | ||
657 | r" | ||
658 | use std::io; | ||
659 | |||
660 | pub use std::fmt; | ||
661 | |||
662 | impl io for Foo { | ||
663 | } | ||
664 | ", | ||
665 | ); | ||
666 | } | ||
667 | |||
668 | #[test] | ||
669 | fn does_not_replace_pub_crate_use() { | ||
670 | check_assist( | ||
671 | replace_qualified_name_with_use, | ||
672 | r" | ||
673 | pub(crate) use std::fmt; | ||
674 | |||
675 | impl std::io<|> for Foo { | ||
676 | } | ||
677 | ", | ||
678 | r" | ||
679 | use std::io; | ||
680 | |||
681 | pub(crate) use std::fmt; | ||
682 | |||
683 | impl io for Foo { | ||
684 | } | ||
685 | ", | ||
686 | ); | ||
687 | } | ||
646 | } | 688 | } |
diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs index e5a4bb23c..d69f2c1b0 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs | |||
@@ -52,7 +52,7 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) | |||
52 | target, | 52 | target, |
53 | |builder| { | 53 | |builder| { |
54 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); | 54 | let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); |
55 | let it = make::bind_pat(make::name("a")).into(); | 55 | let it = make::ident_pat(make::name("a")).into(); |
56 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); | 56 | let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); |
57 | 57 | ||
58 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); | 58 | let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); |
@@ -60,7 +60,7 @@ pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) | |||
60 | 60 | ||
61 | let unreachable_call = make::expr_unreachable(); | 61 | let unreachable_call = make::expr_unreachable(); |
62 | let err_arm = | 62 | let err_arm = |
63 | make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); | 63 | make::match_arm(iter::once(make::wildcard_pat().into()), unreachable_call); |
64 | 64 | ||
65 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); | 65 | let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); |
66 | let match_expr = make::expr_match(caller.clone(), match_arm_list) | 66 | let match_expr = make::expr_match(caller.clone(), match_arm_list) |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 465b90415..507646cc8 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -140,6 +140,7 @@ mod handlers { | |||
140 | mod change_return_type_to_result; | 140 | mod change_return_type_to_result; |
141 | mod change_visibility; | 141 | mod change_visibility; |
142 | mod early_return; | 142 | mod early_return; |
143 | mod expand_glob_import; | ||
143 | mod extract_struct_from_enum_variant; | 144 | mod extract_struct_from_enum_variant; |
144 | mod extract_variable; | 145 | mod extract_variable; |
145 | mod fill_match_arms; | 146 | mod fill_match_arms; |
@@ -181,6 +182,7 @@ mod handlers { | |||
181 | change_return_type_to_result::change_return_type_to_result, | 182 | change_return_type_to_result::change_return_type_to_result, |
182 | change_visibility::change_visibility, | 183 | change_visibility::change_visibility, |
183 | early_return::convert_to_guarded_return, | 184 | early_return::convert_to_guarded_return, |
185 | expand_glob_import::expand_glob_import, | ||
184 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 186 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
185 | extract_variable::extract_variable, | 187 | extract_variable::extract_variable, |
186 | fill_match_arms::fill_match_arms, | 188 | fill_match_arms::fill_match_arms, |
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index eff7feded..97978e7a2 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs | |||
@@ -229,6 +229,33 @@ fn main() { | |||
229 | } | 229 | } |
230 | 230 | ||
231 | #[test] | 231 | #[test] |
232 | fn doctest_expand_glob_import() { | ||
233 | check_doc_test( | ||
234 | "expand_glob_import", | ||
235 | r#####" | ||
236 | mod foo { | ||
237 | pub struct Bar; | ||
238 | pub struct Baz; | ||
239 | } | ||
240 | |||
241 | use foo::*<|>; | ||
242 | |||
243 | fn qux(bar: Bar, baz: Baz) {} | ||
244 | "#####, | ||
245 | r#####" | ||
246 | mod foo { | ||
247 | pub struct Bar; | ||
248 | pub struct Baz; | ||
249 | } | ||
250 | |||
251 | use foo::{Baz, Bar}; | ||
252 | |||
253 | fn qux(bar: Bar, baz: Baz) {} | ||
254 | "#####, | ||
255 | ) | ||
256 | } | ||
257 | |||
258 | #[test] | ||
232 | fn doctest_extract_struct_from_enum_variant() { | 259 | fn doctest_extract_struct_from_enum_variant() { |
233 | check_doc_test( | 260 | check_doc_test( |
234 | "extract_struct_from_enum_variant", | 261 | "extract_struct_from_enum_variant", |
diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index 373de273c..54d5678d1 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs | |||
@@ -181,10 +181,10 @@ impl TryEnum { | |||
181 | match self { | 181 | match self { |
182 | TryEnum::Result => make::tuple_struct_pat( | 182 | TryEnum::Result => make::tuple_struct_pat( |
183 | make::path_unqualified(make::path_segment(make::name_ref("Err"))), | 183 | make::path_unqualified(make::path_segment(make::name_ref("Err"))), |
184 | iter::once(make::placeholder_pat().into()), | 184 | iter::once(make::wildcard_pat().into()), |
185 | ) | 185 | ) |
186 | .into(), | 186 | .into(), |
187 | TryEnum::Option => make::bind_pat(make::name("None")).into(), | 187 | TryEnum::Option => make::ident_pat(make::name("None")).into(), |
188 | } | 188 | } |
189 | } | 189 | } |
190 | 190 | ||
diff --git a/crates/ra_assists/src/utils/insert_use.rs b/crates/ra_assists/src/utils/insert_use.rs index 617afe2e9..32780fceb 100644 --- a/crates/ra_assists/src/utils/insert_use.rs +++ b/crates/ra_assists/src/utils/insert_use.rs | |||
@@ -4,7 +4,7 @@ | |||
4 | 4 | ||
5 | use hir::{self, ModPath}; | 5 | use hir::{self, ModPath}; |
6 | use ra_syntax::{ | 6 | use ra_syntax::{ |
7 | ast::{self, NameOwner}, | 7 | ast::{self, NameOwner, VisibilityOwner}, |
8 | AstNode, Direction, SmolStr, | 8 | AstNode, Direction, SmolStr, |
9 | SyntaxKind::{PATH, PATH_SEGMENT}, | 9 | SyntaxKind::{PATH, PATH_SEGMENT}, |
10 | SyntaxNode, T, | 10 | SyntaxNode, T, |
@@ -378,6 +378,7 @@ fn best_action_for_target( | |||
378 | let best_action = container | 378 | let best_action = container |
379 | .children() | 379 | .children() |
380 | .filter_map(ast::Use::cast) | 380 | .filter_map(ast::Use::cast) |
381 | .filter(|u| u.visibility().is_none()) | ||
381 | .filter_map(|it| it.use_tree()) | 382 | .filter_map(|it| it.use_tree()) |
382 | .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) | 383 | .map(|u| walk_use_tree_for_best_action(&mut storage, None, u, target)) |
383 | .fold(None, |best, a| match best { | 384 | .fold(None, |best, a| match best { |
diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml index 5f334d04f..fe73dc015 100644 --- a/crates/ra_db/Cargo.toml +++ b/crates/ra_db/Cargo.toml | |||
@@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" | |||
9 | doctest = false | 9 | doctest = false |
10 | 10 | ||
11 | [dependencies] | 11 | [dependencies] |
12 | salsa = "0.15.0" | 12 | salsa = "0.15.2" |
13 | rustc-hash = "1.1.0" | 13 | rustc-hash = "1.1.0" |
14 | 14 | ||
15 | ra_syntax = { path = "../ra_syntax" } | 15 | ra_syntax = { path = "../ra_syntax" } |
diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index a2b9f3e35..07333c453 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs | |||
@@ -13,14 +13,7 @@ pub use hir_expand::db::{ | |||
13 | AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, | 13 | AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, |
14 | MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroQuery, | 14 | MacroArgTextQuery, MacroDefQuery, MacroExpandQuery, ParseMacroQuery, |
15 | }; | 15 | }; |
16 | pub use hir_ty::db::{ | 16 | pub use hir_ty::db::*; |
17 | AssociatedTyDataQuery, AssociatedTyValueQuery, CallableItemSignatureQuery, FieldTypesQuery, | ||
18 | GenericDefaultsQuery, GenericPredicatesForParamQuery, GenericPredicatesQuery, HirDatabase, | ||
19 | HirDatabaseStorage, ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, InferQueryQuery, | ||
20 | InherentImplsInCrateQuery, InternTypeParamIdQuery, ReturnTypeImplTraitsQuery, StructDatumQuery, | ||
21 | TraitDatumQuery, TraitImplsInCrateQuery, TraitImplsInDepsQuery, TraitSolveQuery, TyQuery, | ||
22 | ValueTyQuery, | ||
23 | }; | ||
24 | 17 | ||
25 | #[test] | 18 | #[test] |
26 | fn hir_database_is_object_safe() { | 19 | fn hir_database_is_object_safe() { |
diff --git a/crates/ra_hir_expand/src/proc_macro.rs b/crates/ra_hir_expand/src/proc_macro.rs index 04c026004..2c0ec41d2 100644 --- a/crates/ra_hir_expand/src/proc_macro.rs +++ b/crates/ra_hir_expand/src/proc_macro.rs | |||
@@ -101,7 +101,7 @@ fn remove_derive_attrs(tt: &tt::Subtree) -> Option<tt::Subtree> { | |||
101 | } | 101 | } |
102 | 102 | ||
103 | #[cfg(test)] | 103 | #[cfg(test)] |
104 | mod test { | 104 | mod tests { |
105 | use super::*; | 105 | use super::*; |
106 | use test_utils::assert_eq_text; | 106 | use test_utils::assert_eq_text; |
107 | 107 | ||
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 623ce261a..83397d579 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml | |||
@@ -28,9 +28,9 @@ test_utils = { path = "../test_utils" } | |||
28 | 28 | ||
29 | scoped-tls = "1" | 29 | scoped-tls = "1" |
30 | 30 | ||
31 | chalk-solve = { version = "0.19.0" } | 31 | chalk-solve = { version = "0.21.0" } |
32 | chalk-ir = { version = "0.19.0" } | 32 | chalk-ir = { version = "0.21.0" } |
33 | chalk-recursive = { version = "0.19.0" } | 33 | chalk-recursive = { version = "0.21.0" } |
34 | 34 | ||
35 | [dev-dependencies] | 35 | [dev-dependencies] |
36 | expect = { path = "../expect" } | 36 | expect = { path = "../expect" } |
diff --git a/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs b/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs index 5cc76bdce..61ffbf5d1 100644 --- a/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs +++ b/crates/ra_hir_ty/src/diagnostics/unsafe_check.rs | |||
@@ -6,6 +6,7 @@ use std::sync::Arc; | |||
6 | use hir_def::{ | 6 | use hir_def::{ |
7 | body::Body, | 7 | body::Body, |
8 | expr::{Expr, ExprId, UnaryOp}, | 8 | expr::{Expr, ExprId, UnaryOp}, |
9 | resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, | ||
9 | DefWithBodyId, | 10 | DefWithBodyId, |
10 | }; | 11 | }; |
11 | use hir_expand::diagnostics::DiagnosticSink; | 12 | use hir_expand::diagnostics::DiagnosticSink; |
@@ -70,7 +71,7 @@ pub fn unsafe_expressions( | |||
70 | ) -> Vec<UnsafeExpr> { | 71 | ) -> Vec<UnsafeExpr> { |
71 | let mut unsafe_exprs = vec![]; | 72 | let mut unsafe_exprs = vec![]; |
72 | let body = db.body(def); | 73 | let body = db.body(def); |
73 | walk_unsafe(&mut unsafe_exprs, db, infer, &body, body.body_expr, false); | 74 | walk_unsafe(&mut unsafe_exprs, db, infer, def, &body, body.body_expr, false); |
74 | 75 | ||
75 | unsafe_exprs | 76 | unsafe_exprs |
76 | } | 77 | } |
@@ -79,6 +80,7 @@ fn walk_unsafe( | |||
79 | unsafe_exprs: &mut Vec<UnsafeExpr>, | 80 | unsafe_exprs: &mut Vec<UnsafeExpr>, |
80 | db: &dyn HirDatabase, | 81 | db: &dyn HirDatabase, |
81 | infer: &InferenceResult, | 82 | infer: &InferenceResult, |
83 | def: DefWithBodyId, | ||
82 | body: &Body, | 84 | body: &Body, |
83 | current: ExprId, | 85 | current: ExprId, |
84 | inside_unsafe_block: bool, | 86 | inside_unsafe_block: bool, |
@@ -97,6 +99,15 @@ fn walk_unsafe( | |||
97 | } | 99 | } |
98 | } | 100 | } |
99 | } | 101 | } |
102 | Expr::Path(path) => { | ||
103 | let resolver = resolver_for_expr(db.upcast(), def, current); | ||
104 | let value_or_partial = resolver.resolve_path_in_value_ns(db.upcast(), path.mod_path()); | ||
105 | if let Some(ResolveValueResult::ValueNs(ValueNs::StaticId(id))) = value_or_partial { | ||
106 | if db.static_data(id).mutable { | ||
107 | unsafe_exprs.push(UnsafeExpr { expr: current, inside_unsafe_block }); | ||
108 | } | ||
109 | } | ||
110 | } | ||
100 | Expr::MethodCall { .. } => { | 111 | Expr::MethodCall { .. } => { |
101 | if infer | 112 | if infer |
102 | .method_resolution(current) | 113 | .method_resolution(current) |
@@ -112,13 +123,13 @@ fn walk_unsafe( | |||
112 | } | 123 | } |
113 | } | 124 | } |
114 | Expr::Unsafe { body: child } => { | 125 | Expr::Unsafe { body: child } => { |
115 | return walk_unsafe(unsafe_exprs, db, infer, body, *child, true); | 126 | return walk_unsafe(unsafe_exprs, db, infer, def, body, *child, true); |
116 | } | 127 | } |
117 | _ => {} | 128 | _ => {} |
118 | } | 129 | } |
119 | 130 | ||
120 | expr.walk_child_exprs(|child| { | 131 | expr.walk_child_exprs(|child| { |
121 | walk_unsafe(unsafe_exprs, db, infer, body, child, inside_unsafe_block); | 132 | walk_unsafe(unsafe_exprs, db, infer, def, body, child, inside_unsafe_block); |
122 | }); | 133 | }); |
123 | } | 134 | } |
124 | 135 | ||
@@ -170,4 +181,25 @@ fn main() { | |||
170 | "#, | 181 | "#, |
171 | ); | 182 | ); |
172 | } | 183 | } |
184 | |||
185 | #[test] | ||
186 | fn missing_unsafe_diagnostic_with_static_mut() { | ||
187 | check_diagnostics( | ||
188 | r#" | ||
189 | struct Ty { | ||
190 | a: u8, | ||
191 | } | ||
192 | |||
193 | static mut static_mut: Ty = Ty { a: 0 }; | ||
194 | |||
195 | fn main() { | ||
196 | let x = static_mut.a; | ||
197 | //^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block | ||
198 | unsafe { | ||
199 | let x = static_mut.a; | ||
200 | } | ||
201 | } | ||
202 | "#, | ||
203 | ); | ||
204 | } | ||
173 | } | 205 | } |
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index 28f32a0a4..3d12039a6 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs | |||
@@ -440,6 +440,12 @@ impl<'a> InferenceContext<'a> { | |||
440 | let ty = self.insert_type_vars(ty.subst(&substs)); | 440 | let ty = self.insert_type_vars(ty.subst(&substs)); |
441 | forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) | 441 | forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) |
442 | } | 442 | } |
443 | TypeNs::AdtId(AdtId::UnionId(u)) => { | ||
444 | let substs = Ty::substs_from_path(&ctx, path, u.into(), true); | ||
445 | let ty = self.db.ty(u.into()); | ||
446 | let ty = self.insert_type_vars(ty.subst(&substs)); | ||
447 | forbid_unresolved_segments((ty, Some(u.into())), unresolved) | ||
448 | } | ||
443 | TypeNs::EnumVariantId(var) => { | 449 | TypeNs::EnumVariantId(var) => { |
444 | let substs = Ty::substs_from_path(&ctx, path, var.into(), true); | 450 | let substs = Ty::substs_from_path(&ctx, path, var.into(), true); |
445 | let ty = self.db.ty(var.parent.into()); | 451 | let ty = self.db.ty(var.parent.into()); |
@@ -490,10 +496,7 @@ impl<'a> InferenceContext<'a> { | |||
490 | // FIXME potentially resolve assoc type | 496 | // FIXME potentially resolve assoc type |
491 | (Ty::Unknown, None) | 497 | (Ty::Unknown, None) |
492 | } | 498 | } |
493 | TypeNs::AdtId(AdtId::EnumId(_)) | 499 | TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::TraitId(_) => { |
494 | | TypeNs::AdtId(AdtId::UnionId(_)) | ||
495 | | TypeNs::BuiltinType(_) | ||
496 | | TypeNs::TraitId(_) => { | ||
497 | // FIXME diagnostic | 500 | // FIXME diagnostic |
498 | (Ty::Unknown, None) | 501 | (Ty::Unknown, None) |
499 | } | 502 | } |
diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs index 1eacc6f95..7638f167b 100644 --- a/crates/ra_hir_ty/src/lower.rs +++ b/crates/ra_hir_ty/src/lower.rs | |||
@@ -518,6 +518,7 @@ impl Ty { | |||
518 | let (segment, generic_def) = match resolved { | 518 | let (segment, generic_def) = match resolved { |
519 | ValueTyDefId::FunctionId(it) => (last, Some(it.into())), | 519 | ValueTyDefId::FunctionId(it) => (last, Some(it.into())), |
520 | ValueTyDefId::StructId(it) => (last, Some(it.into())), | 520 | ValueTyDefId::StructId(it) => (last, Some(it.into())), |
521 | ValueTyDefId::UnionId(it) => (last, Some(it.into())), | ||
521 | ValueTyDefId::ConstId(it) => (last, Some(it.into())), | 522 | ValueTyDefId::ConstId(it) => (last, Some(it.into())), |
522 | ValueTyDefId::StaticId(_) => (last, None), | 523 | ValueTyDefId::StaticId(_) => (last, None), |
523 | ValueTyDefId::EnumVariantId(var) => { | 524 | ValueTyDefId::EnumVariantId(var) => { |
@@ -1148,11 +1149,12 @@ impl_from!(BuiltinType, AdtId(StructId, EnumId, UnionId), TypeAliasId for TyDefI | |||
1148 | pub enum ValueTyDefId { | 1149 | pub enum ValueTyDefId { |
1149 | FunctionId(FunctionId), | 1150 | FunctionId(FunctionId), |
1150 | StructId(StructId), | 1151 | StructId(StructId), |
1152 | UnionId(UnionId), | ||
1151 | EnumVariantId(EnumVariantId), | 1153 | EnumVariantId(EnumVariantId), |
1152 | ConstId(ConstId), | 1154 | ConstId(ConstId), |
1153 | StaticId(StaticId), | 1155 | StaticId(StaticId), |
1154 | } | 1156 | } |
1155 | impl_from!(FunctionId, StructId, EnumVariantId, ConstId, StaticId for ValueTyDefId); | 1157 | impl_from!(FunctionId, StructId, UnionId, EnumVariantId, ConstId, StaticId for ValueTyDefId); |
1156 | 1158 | ||
1157 | /// Build the declared type of an item. This depends on the namespace; e.g. for | 1159 | /// Build the declared type of an item. This depends on the namespace; e.g. for |
1158 | /// `struct Foo(usize)`, we have two types: The type of the struct itself, and | 1160 | /// `struct Foo(usize)`, we have two types: The type of the struct itself, and |
@@ -1179,6 +1181,7 @@ pub(crate) fn value_ty_query(db: &dyn HirDatabase, def: ValueTyDefId) -> Binders | |||
1179 | match def { | 1181 | match def { |
1180 | ValueTyDefId::FunctionId(it) => type_for_fn(db, it), | 1182 | ValueTyDefId::FunctionId(it) => type_for_fn(db, it), |
1181 | ValueTyDefId::StructId(it) => type_for_struct_constructor(db, it), | 1183 | ValueTyDefId::StructId(it) => type_for_struct_constructor(db, it), |
1184 | ValueTyDefId::UnionId(it) => type_for_adt(db, it.into()), | ||
1182 | ValueTyDefId::EnumVariantId(it) => type_for_enum_variant_constructor(db, it), | 1185 | ValueTyDefId::EnumVariantId(it) => type_for_enum_variant_constructor(db, it), |
1183 | ValueTyDefId::ConstId(it) => type_for_const(db, it), | 1186 | ValueTyDefId::ConstId(it) => type_for_const(db, it), |
1184 | ValueTyDefId::StaticId(it) => type_for_static(db, it), | 1187 | ValueTyDefId::StaticId(it) => type_for_static(db, it), |
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 3fd7d5cd4..5a7cf9455 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs | |||
@@ -334,16 +334,44 @@ fn infer_union() { | |||
334 | bar: f32, | 334 | bar: f32, |
335 | } | 335 | } |
336 | 336 | ||
337 | fn test() { | ||
338 | let u = MyUnion { foo: 0 }; | ||
339 | unsafe { baz(u); } | ||
340 | let u = MyUnion { bar: 0.0 }; | ||
341 | unsafe { baz(u); } | ||
342 | } | ||
343 | |||
337 | unsafe fn baz(u: MyUnion) { | 344 | unsafe fn baz(u: MyUnion) { |
338 | let inner = u.foo; | 345 | let inner = u.foo; |
346 | let inner = u.bar; | ||
339 | } | 347 | } |
340 | "#, | 348 | "#, |
341 | expect![[r#" | 349 | expect![[r#" |
342 | 61..62 'u': MyUnion | 350 | 57..172 '{ ...); } }': () |
343 | 73..99 '{ ...foo; }': () | 351 | 67..68 'u': MyUnion |
344 | 83..88 'inner': u32 | 352 | 71..89 'MyUnio...o: 0 }': MyUnion |
345 | 91..92 'u': MyUnion | 353 | 86..87 '0': u32 |
346 | 91..96 'u.foo': u32 | 354 | 95..113 'unsafe...(u); }': () |
355 | 102..113 '{ baz(u); }': () | ||
356 | 104..107 'baz': fn baz(MyUnion) | ||
357 | 104..110 'baz(u)': () | ||
358 | 108..109 'u': MyUnion | ||
359 | 122..123 'u': MyUnion | ||
360 | 126..146 'MyUnio... 0.0 }': MyUnion | ||
361 | 141..144 '0.0': f32 | ||
362 | 152..170 'unsafe...(u); }': () | ||
363 | 159..170 '{ baz(u); }': () | ||
364 | 161..164 'baz': fn baz(MyUnion) | ||
365 | 161..167 'baz(u)': () | ||
366 | 165..166 'u': MyUnion | ||
367 | 188..189 'u': MyUnion | ||
368 | 200..249 '{ ...bar; }': () | ||
369 | 210..215 'inner': u32 | ||
370 | 218..219 'u': MyUnion | ||
371 | 218..223 'u.foo': u32 | ||
372 | 233..238 'inner': f32 | ||
373 | 241..242 'u': MyUnion | ||
374 | 241..246 'u.bar': f32 | ||
347 | "#]], | 375 | "#]], |
348 | ); | 376 | ); |
349 | } | 377 | } |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index dd8a7ffd9..73c0b8275 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -78,8 +78,10 @@ pub(crate) fn diagnostics( | |||
78 | } else { | 78 | } else { |
79 | let mut field_list = d.ast(db); | 79 | let mut field_list = d.ast(db); |
80 | for f in d.missed_fields.iter() { | 80 | for f in d.missed_fields.iter() { |
81 | let field = | 81 | let field = make::record_expr_field( |
82 | make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); | 82 | make::name_ref(&f.to_string()), |
83 | Some(make::expr_unit()), | ||
84 | ); | ||
83 | field_list = field_list.append_field(&field); | 85 | field_list = field_list.append_field(&field); |
84 | } | 86 | } |
85 | 87 | ||
@@ -178,9 +180,9 @@ fn missing_struct_field_fix( | |||
178 | if new_field_type.is_unknown() { | 180 | if new_field_type.is_unknown() { |
179 | return None; | 181 | return None; |
180 | } | 182 | } |
181 | let new_field = make::record_field_def( | 183 | let new_field = make::record_field( |
182 | record_expr.field_name()?, | 184 | record_expr.field_name()?, |
183 | make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | 185 | make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), |
184 | ); | 186 | ); |
185 | 187 | ||
186 | let last_field = record_fields.fields().last()?; | 188 | let last_field = record_fields.fields().last()?; |
diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs index 903c34996..0fbc9babd 100644 --- a/crates/ra_ide/src/folding_ranges.rs +++ b/crates/ra_ide/src/folding_ranges.rs | |||
@@ -85,7 +85,8 @@ fn fold_kind(kind: SyntaxKind) -> Option<FoldKind> { | |||
85 | COMMENT => Some(FoldKind::Comment), | 85 | COMMENT => Some(FoldKind::Comment), |
86 | USE => Some(FoldKind::Imports), | 86 | USE => Some(FoldKind::Imports), |
87 | ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), | 87 | ARG_LIST | PARAM_LIST => Some(FoldKind::ArgList), |
88 | RECORD_FIELD_LIST | 88 | ASSOC_ITEM_LIST |
89 | | RECORD_FIELD_LIST | ||
89 | | RECORD_PAT_FIELD_LIST | 90 | | RECORD_PAT_FIELD_LIST |
90 | | RECORD_EXPR_FIELD_LIST | 91 | | RECORD_EXPR_FIELD_LIST |
91 | | ITEM_LIST | 92 | | ITEM_LIST |
@@ -337,6 +338,26 @@ fn main() <fold block>{ | |||
337 | } | 338 | } |
338 | 339 | ||
339 | #[test] | 340 | #[test] |
341 | fn test_folds_structs() { | ||
342 | check( | ||
343 | r#" | ||
344 | struct Foo <fold block>{ | ||
345 | }</fold> | ||
346 | "#, | ||
347 | ); | ||
348 | } | ||
349 | |||
350 | #[test] | ||
351 | fn test_folds_traits() { | ||
352 | check( | ||
353 | r#" | ||
354 | trait Foo <fold block>{ | ||
355 | }</fold> | ||
356 | "#, | ||
357 | ); | ||
358 | } | ||
359 | |||
360 | #[test] | ||
340 | fn test_folds_macros() { | 361 | fn test_folds_macros() { |
341 | check( | 362 | check( |
342 | r#" | 363 | r#" |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index aa48cb412..385e3e64e 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -509,6 +509,37 @@ fn main() { } | |||
509 | } | 509 | } |
510 | 510 | ||
511 | #[test] | 511 | #[test] |
512 | fn hover_shows_fn_doc() { | ||
513 | check( | ||
514 | r#" | ||
515 | /// # Example | ||
516 | /// ``` | ||
517 | /// # use std::path::Path; | ||
518 | /// # | ||
519 | /// foo(Path::new("hello, world!")) | ||
520 | /// ``` | ||
521 | pub fn foo<|>(_: &Path) {} | ||
522 | |||
523 | fn main() { } | ||
524 | "#, | ||
525 | expect![[r#" | ||
526 | *foo* | ||
527 | ```rust | ||
528 | pub fn foo(_: &Path) | ||
529 | ``` | ||
530 | ___ | ||
531 | |||
532 | # Example | ||
533 | ``` | ||
534 | # use std::path::Path; | ||
535 | # | ||
536 | foo(Path::new("hello, world!")) | ||
537 | ``` | ||
538 | "#]], | ||
539 | ); | ||
540 | } | ||
541 | |||
542 | #[test] | ||
512 | fn hover_shows_struct_field_info() { | 543 | fn hover_shows_struct_field_info() { |
513 | // Hovering over the field when instantiating | 544 | // Hovering over the field when instantiating |
514 | check( | 545 | check( |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index a32ae0165..f71b804fe 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -4,7 +4,7 @@ mod injection; | |||
4 | #[cfg(test)] | 4 | #[cfg(test)] |
5 | mod tests; | 5 | mod tests; |
6 | 6 | ||
7 | use hir::{Name, Semantics}; | 7 | use hir::{Name, Semantics, VariantDef}; |
8 | use ra_ide_db::{ | 8 | use ra_ide_db::{ |
9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, |
10 | RootDatabase, | 10 | RootDatabase, |
@@ -455,6 +455,18 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | |||
455 | Some(TextRange::new(range_start, range_end)) | 455 | Some(TextRange::new(range_start, range_end)) |
456 | } | 456 | } |
457 | 457 | ||
458 | fn is_possibly_unsafe(name_ref: &ast::NameRef) -> bool { | ||
459 | name_ref | ||
460 | .syntax() | ||
461 | .parent() | ||
462 | .and_then(|parent| { | ||
463 | ast::FieldExpr::cast(parent.clone()) | ||
464 | .map(|_| true) | ||
465 | .or_else(|| ast::RecordPatField::cast(parent).map(|_| true)) | ||
466 | }) | ||
467 | .unwrap_or(false) | ||
468 | } | ||
469 | |||
458 | fn highlight_element( | 470 | fn highlight_element( |
459 | sema: &Semantics<RootDatabase>, | 471 | sema: &Semantics<RootDatabase>, |
460 | bindings_shadow_count: &mut FxHashMap<Name, u32>, | 472 | bindings_shadow_count: &mut FxHashMap<Name, u32>, |
@@ -484,10 +496,19 @@ fn highlight_element( | |||
484 | 496 | ||
485 | match name_kind { | 497 | match name_kind { |
486 | Some(NameClass::Definition(def)) => { | 498 | Some(NameClass::Definition(def)) => { |
487 | highlight_name(db, def) | HighlightModifier::Definition | 499 | highlight_name(db, def, false) | HighlightModifier::Definition |
500 | } | ||
501 | Some(NameClass::ConstReference(def)) => highlight_name(db, def, false), | ||
502 | Some(NameClass::FieldShorthand { field, .. }) => { | ||
503 | let mut h = HighlightTag::Field.into(); | ||
504 | if let Definition::Field(field) = field { | ||
505 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
506 | h |= HighlightModifier::Unsafe; | ||
507 | } | ||
508 | } | ||
509 | |||
510 | h | ||
488 | } | 511 | } |
489 | Some(NameClass::ConstReference(def)) => highlight_name(db, def), | ||
490 | Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(), | ||
491 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, | 512 | None => highlight_name_by_syntax(name) | HighlightModifier::Definition, |
492 | } | 513 | } |
493 | } | 514 | } |
@@ -498,6 +519,7 @@ fn highlight_element( | |||
498 | } | 519 | } |
499 | NAME_REF => { | 520 | NAME_REF => { |
500 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 521 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
522 | let possibly_unsafe = is_possibly_unsafe(&name_ref); | ||
501 | match classify_name_ref(sema, &name_ref) { | 523 | match classify_name_ref(sema, &name_ref) { |
502 | Some(name_kind) => match name_kind { | 524 | Some(name_kind) => match name_kind { |
503 | NameRefClass::Definition(def) => { | 525 | NameRefClass::Definition(def) => { |
@@ -508,11 +530,13 @@ fn highlight_element( | |||
508 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) | 530 | binding_hash = Some(calc_binding_hash(&name, *shadow_count)) |
509 | } | 531 | } |
510 | }; | 532 | }; |
511 | highlight_name(db, def) | 533 | highlight_name(db, def, possibly_unsafe) |
512 | } | 534 | } |
513 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), | 535 | NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), |
514 | }, | 536 | }, |
515 | None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref), | 537 | None if syntactic_name_ref_highlighting => { |
538 | highlight_name_ref_by_syntax(name_ref, sema) | ||
539 | } | ||
516 | None => HighlightTag::UnresolvedReference.into(), | 540 | None => HighlightTag::UnresolvedReference.into(), |
517 | } | 541 | } |
518 | } | 542 | } |
@@ -652,10 +676,19 @@ fn is_child_of_impl(element: &SyntaxElement) -> bool { | |||
652 | } | 676 | } |
653 | } | 677 | } |
654 | 678 | ||
655 | fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | 679 | fn highlight_name(db: &RootDatabase, def: Definition, possibly_unsafe: bool) -> Highlight { |
656 | match def { | 680 | match def { |
657 | Definition::Macro(_) => HighlightTag::Macro, | 681 | Definition::Macro(_) => HighlightTag::Macro, |
658 | Definition::Field(_) => HighlightTag::Field, | 682 | Definition::Field(field) => { |
683 | let mut h = HighlightTag::Field.into(); | ||
684 | if possibly_unsafe { | ||
685 | if let VariantDef::Union(_) = field.parent_def(db) { | ||
686 | h |= HighlightModifier::Unsafe; | ||
687 | } | ||
688 | } | ||
689 | |||
690 | return h; | ||
691 | } | ||
659 | Definition::ModuleDef(def) => match def { | 692 | Definition::ModuleDef(def) => match def { |
660 | hir::ModuleDef::Module(_) => HighlightTag::Module, | 693 | hir::ModuleDef::Module(_) => HighlightTag::Module, |
661 | hir::ModuleDef::Function(func) => { | 694 | hir::ModuleDef::Function(func) => { |
@@ -677,6 +710,7 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { | |||
677 | let mut h = Highlight::new(HighlightTag::Static); | 710 | let mut h = Highlight::new(HighlightTag::Static); |
678 | if s.is_mut(db) { | 711 | if s.is_mut(db) { |
679 | h |= HighlightModifier::Mutable; | 712 | h |= HighlightModifier::Mutable; |
713 | h |= HighlightModifier::Unsafe; | ||
680 | } | 714 | } |
681 | return h; | 715 | return h; |
682 | } | 716 | } |
@@ -724,7 +758,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { | |||
724 | tag.into() | 758 | tag.into() |
725 | } | 759 | } |
726 | 760 | ||
727 | fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | 761 | fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight { |
728 | let default = HighlightTag::UnresolvedReference; | 762 | let default = HighlightTag::UnresolvedReference; |
729 | 763 | ||
730 | let parent = match name.syntax().parent() { | 764 | let parent = match name.syntax().parent() { |
@@ -734,7 +768,20 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { | |||
734 | 768 | ||
735 | let tag = match parent.kind() { | 769 | let tag = match parent.kind() { |
736 | METHOD_CALL_EXPR => HighlightTag::Function, | 770 | METHOD_CALL_EXPR => HighlightTag::Function, |
737 | FIELD_EXPR => HighlightTag::Field, | 771 | FIELD_EXPR => { |
772 | let h = HighlightTag::Field; | ||
773 | let is_union = ast::FieldExpr::cast(parent) | ||
774 | .and_then(|field_expr| { | ||
775 | let field = sema.resolve_field(&field_expr)?; | ||
776 | Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { | ||
777 | true | ||
778 | } else { | ||
779 | false | ||
780 | }) | ||
781 | }) | ||
782 | .unwrap_or(false); | ||
783 | return if is_union { h | HighlightModifier::Unsafe } else { h.into() }; | ||
784 | } | ||
738 | PATH_SEGMENT => { | 785 | PATH_SEGMENT => { |
739 | let path = match parent.parent().and_then(ast::Path::cast) { | 786 | let path = match parent.parent().and_then(ast::Path::cast) { |
740 | Some(it) => it, | 787 | Some(it) => it, |
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index 87a6e2523..730efff0d 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs | |||
@@ -9,6 +9,9 @@ use crate::{mock_analysis::single_file, FileRange, TextRange}; | |||
9 | fn test_highlighting() { | 9 | fn test_highlighting() { |
10 | check_highlighting( | 10 | check_highlighting( |
11 | r#" | 11 | r#" |
12 | use inner::{self as inner_mod}; | ||
13 | mod inner {} | ||
14 | |||
12 | #[derive(Clone, Debug)] | 15 | #[derive(Clone, Debug)] |
13 | struct Foo { | 16 | struct Foo { |
14 | pub x: i32, | 17 | pub x: i32, |
@@ -272,19 +275,37 @@ fn test_unsafe_highlighting() { | |||
272 | r#" | 275 | r#" |
273 | unsafe fn unsafe_fn() {} | 276 | unsafe fn unsafe_fn() {} |
274 | 277 | ||
278 | union Union { | ||
279 | a: u32, | ||
280 | b: f32, | ||
281 | } | ||
282 | |||
275 | struct HasUnsafeFn; | 283 | struct HasUnsafeFn; |
276 | 284 | ||
277 | impl HasUnsafeFn { | 285 | impl HasUnsafeFn { |
278 | unsafe fn unsafe_method(&self) {} | 286 | unsafe fn unsafe_method(&self) {} |
279 | } | 287 | } |
280 | 288 | ||
289 | struct TypeForStaticMut { | ||
290 | a: u8 | ||
291 | } | ||
292 | |||
293 | static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 }; | ||
294 | |||
281 | fn main() { | 295 | fn main() { |
282 | let x = &5 as *const usize; | 296 | let x = &5 as *const usize; |
297 | let u = Union { b: 0 }; | ||
283 | unsafe { | 298 | unsafe { |
284 | unsafe_fn(); | 299 | unsafe_fn(); |
300 | let b = u.b; | ||
301 | match u { | ||
302 | Union { b: 0 } => (), | ||
303 | Union { a } => (), | ||
304 | } | ||
285 | HasUnsafeFn.unsafe_method(); | 305 | HasUnsafeFn.unsafe_method(); |
286 | let y = *(x); | 306 | let y = *(x); |
287 | let z = -x; | 307 | let z = -x; |
308 | let a = global_mut.a; | ||
288 | } | 309 | } |
289 | } | 310 | } |
290 | "# | 311 | "# |
diff --git a/crates/ra_ide/test_data/highlight_unsafe.html b/crates/ra_ide/test_data/highlight_unsafe.html index b81b6f1c3..79409fe81 100644 --- a/crates/ra_ide/test_data/highlight_unsafe.html +++ b/crates/ra_ide/test_data/highlight_unsafe.html | |||
@@ -37,18 +37,36 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
37 | </style> | 37 | </style> |
38 | <pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> | 38 | <pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> |
39 | 39 | ||
40 | <span class="keyword">union</span> <span class="union declaration">Union</span> <span class="punctuation">{</span> | ||
41 | <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u32</span><span class="punctuation">,</span> | ||
42 | <span class="field declaration">b</span><span class="punctuation">:</span> <span class="builtin_type">f32</span><span class="punctuation">,</span> | ||
43 | <span class="punctuation">}</span> | ||
44 | |||
40 | <span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="punctuation">;</span> | 45 | <span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="punctuation">;</span> |
41 | 46 | ||
42 | <span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="punctuation">{</span> | 47 | <span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="punctuation">{</span> |
43 | <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> | 48 | <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_method</span><span class="punctuation">(</span><span class="operator">&</span><span class="self_keyword">self</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span> |
44 | <span class="punctuation">}</span> | 49 | <span class="punctuation">}</span> |
45 | 50 | ||
51 | <span class="keyword">struct</span> <span class="struct declaration">TypeForStaticMut</span> <span class="punctuation">{</span> | ||
52 | <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u8</span> | ||
53 | <span class="punctuation">}</span> | ||
54 | |||
55 | <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">global_mut</span><span class="punctuation">:</span> <span class="struct">TypeForStaticMut</span> <span class="operator">=</span> <span class="struct">TypeForStaticMut</span> <span class="punctuation">{</span> <span class="field">a</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span> | ||
56 | |||
46 | <span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> | 57 | <span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> |
47 | <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="operator">&</span><span class="numeric_literal">5</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="builtin_type">usize</span><span class="punctuation">;</span> | 58 | <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="operator">&</span><span class="numeric_literal">5</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="builtin_type">usize</span><span class="punctuation">;</span> |
59 | <span class="keyword">let</span> <span class="variable declaration">u</span> <span class="operator">=</span> <span class="union">Union</span> <span class="punctuation">{</span> <span class="field">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span> | ||
48 | <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span> | 60 | <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span> |
49 | <span class="function unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 61 | <span class="function unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
62 | <span class="keyword">let</span> <span class="variable declaration">b</span> <span class="operator">=</span> <span class="variable">u</span><span class="punctuation">.</span><span class="field unsafe">b</span><span class="punctuation">;</span> | ||
63 | <span class="keyword control">match</span> <span class="variable">u</span> <span class="punctuation">{</span> | ||
64 | <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span> <span class="operator">=></span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span> | ||
65 | <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">a</span> <span class="punctuation">}</span> <span class="operator">=></span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span> | ||
66 | <span class="punctuation">}</span> | ||
50 | <span class="struct">HasUnsafeFn</span><span class="punctuation">.</span><span class="function unsafe">unsafe_method</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> | 67 | <span class="struct">HasUnsafeFn</span><span class="punctuation">.</span><span class="function unsafe">unsafe_method</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span> |
51 | <span class="keyword">let</span> <span class="variable declaration">y</span> <span class="operator">=</span> <span class="operator unsafe">*</span><span class="punctuation">(</span><span class="variable">x</span><span class="punctuation">)</span><span class="punctuation">;</span> | 68 | <span class="keyword">let</span> <span class="variable declaration">y</span> <span class="operator">=</span> <span class="operator unsafe">*</span><span class="punctuation">(</span><span class="variable">x</span><span class="punctuation">)</span><span class="punctuation">;</span> |
52 | <span class="keyword">let</span> <span class="variable declaration">z</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="variable">x</span><span class="punctuation">;</span> | 69 | <span class="keyword">let</span> <span class="variable declaration">z</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="variable">x</span><span class="punctuation">;</span> |
70 | <span class="keyword">let</span> <span class="variable declaration">a</span> <span class="operator">=</span> <span class="static mutable unsafe">global_mut</span><span class="punctuation">.</span><span class="field">a</span><span class="punctuation">;</span> | ||
53 | <span class="punctuation">}</span> | 71 | <span class="punctuation">}</span> |
54 | <span class="punctuation">}</span></code></pre> \ No newline at end of file | 72 | <span class="punctuation">}</span></code></pre> \ No newline at end of file |
diff --git a/crates/ra_ide/test_data/highlighting.html b/crates/ra_ide/test_data/highlighting.html index 345a2f023..8e0160eee 100644 --- a/crates/ra_ide/test_data/highlighting.html +++ b/crates/ra_ide/test_data/highlighting.html | |||
@@ -35,7 +35,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
35 | 35 | ||
36 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } | 36 | .unresolved_reference { color: #FC5555; text-decoration: wavy underline; } |
37 | </style> | 37 | </style> |
38 | <pre><code><span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">derive</span><span class="punctuation">(</span><span class="attribute">Clone</span><span class="punctuation">,</span><span class="attribute"> Debug</span><span class="punctuation">)</span><span class="attribute">]</span> | 38 | <pre><code><span class="keyword">use</span> <span class="module">inner</span><span class="operator">::</span><span class="punctuation">{</span><span class="self_keyword">self</span> <span class="keyword">as</span> <span class="module declaration">inner_mod</span><span class="punctuation">}</span><span class="punctuation">;</span> |
39 | <span class="keyword">mod</span> <span class="module declaration">inner</span> <span class="punctuation">{</span><span class="punctuation">}</span> | ||
40 | |||
41 | <span class="attribute">#</span><span class="attribute">[</span><span class="function attribute">derive</span><span class="punctuation">(</span><span class="attribute">Clone</span><span class="punctuation">,</span><span class="attribute"> Debug</span><span class="punctuation">)</span><span class="attribute">]</span> | ||
39 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> | 42 | <span class="keyword">struct</span> <span class="struct declaration">Foo</span> <span class="punctuation">{</span> |
40 | <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> | 43 | <span class="keyword">pub</span> <span class="field declaration">x</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> |
41 | <span class="keyword">pub</span> <span class="field declaration">y</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> | 44 | <span class="keyword">pub</span> <span class="field declaration">y</span><span class="punctuation">:</span> <span class="builtin_type">i32</span><span class="punctuation">,</span> |
@@ -61,7 +64,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
61 | <span class="punctuation">}</span> | 64 | <span class="punctuation">}</span> |
62 | <span class="punctuation">}</span> | 65 | <span class="punctuation">}</span> |
63 | 66 | ||
64 | <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable">STATIC_MUT</span><span class="punctuation">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span> | 67 | <span class="keyword">static</span> <span class="keyword">mut</span> <span class="static declaration mutable unsafe">STATIC_MUT</span><span class="punctuation">:</span> <span class="builtin_type">i32</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="punctuation">;</span> |
65 | 68 | ||
66 | <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation"><</span><span class="lifetime declaration">'a</span><span class="punctuation">,</span> <span class="type_param declaration">T</span><span class="punctuation">></span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="type_param">T</span> <span class="punctuation">{</span> | 69 | <span class="keyword">fn</span> <span class="function declaration">foo</span><span class="punctuation"><</span><span class="lifetime declaration">'a</span><span class="punctuation">,</span> <span class="type_param declaration">T</span><span class="punctuation">></span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-></span> <span class="type_param">T</span> <span class="punctuation">{</span> |
67 | <span class="function">foo</span><span class="operator">::</span><span class="punctuation"><</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">></span><span class="punctuation">(</span><span class="punctuation">)</span> | 70 | <span class="function">foo</span><span class="operator">::</span><span class="punctuation"><</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">></span><span class="punctuation">(</span><span class="punctuation">)</span> |
@@ -94,7 +97,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd | |||
94 | <span class="punctuation">}</span> | 97 | <span class="punctuation">}</span> |
95 | <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span> | 98 | <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span> |
96 | <span class="variable mutable">vec</span><span class="punctuation">.</span><span class="unresolved_reference">set_len</span><span class="punctuation">(</span><span class="numeric_literal">0</span><span class="punctuation">)</span><span class="punctuation">;</span> | 99 | <span class="variable mutable">vec</span><span class="punctuation">.</span><span class="unresolved_reference">set_len</span><span class="punctuation">(</span><span class="numeric_literal">0</span><span class="punctuation">)</span><span class="punctuation">;</span> |
97 | <span class="static mutable">STATIC_MUT</span> <span class="operator">=</span> <span class="numeric_literal">1</span><span class="punctuation">;</span> | 100 | <span class="static mutable unsafe">STATIC_MUT</span> <span class="operator">=</span> <span class="numeric_literal">1</span><span class="punctuation">;</span> |
98 | <span class="punctuation">}</span> | 101 | <span class="punctuation">}</span> |
99 | 102 | ||
100 | <span class="keyword control">for</span> <span class="variable declaration">e</span> <span class="keyword control">in</span> <span class="variable mutable">vec</span> <span class="punctuation">{</span> | 103 | <span class="keyword control">for</span> <span class="variable declaration">e</span> <span class="keyword control">in</span> <span class="variable mutable">vec</span> <span class="punctuation">{</span> |
diff --git a/crates/ra_ide_db/src/change.rs b/crates/ra_ide_db/src/change.rs index 32d9a8d1f..b13df8b85 100644 --- a/crates/ra_ide_db/src/change.rs +++ b/crates/ra_ide_db/src/change.rs | |||
@@ -190,11 +190,24 @@ impl RootDatabase { | |||
190 | let q: $q = Default::default(); | 190 | let q: $q = Default::default(); |
191 | let name = format!("{:?} (deps)", q); | 191 | let name = format!("{:?} (deps)", q); |
192 | acc.push((name, before - after)); | 192 | acc.push((name, before - after)); |
193 | |||
194 | let before = memory_usage().allocated; | ||
195 | $q.in_db(self).purge(); | ||
196 | let after = memory_usage().allocated; | ||
197 | let q: $q = Default::default(); | ||
198 | let name = format!("{:?} (purge)", q); | ||
199 | acc.push((name, before - after)); | ||
193 | )*} | 200 | )*} |
194 | } | 201 | } |
195 | sweep_each_query![ | 202 | sweep_each_query![ |
196 | // SourceDatabase | 203 | // SourceDatabase |
197 | ra_db::ParseQuery | 204 | ra_db::ParseQuery |
205 | ra_db::CrateGraphQuery | ||
206 | |||
207 | // SourceDatabaseExt | ||
208 | ra_db::FileTextQuery | ||
209 | ra_db::FileSourceRootQuery | ||
210 | ra_db::SourceRootQuery | ||
198 | ra_db::SourceRootCratesQuery | 211 | ra_db::SourceRootCratesQuery |
199 | 212 | ||
200 | // AstDatabase | 213 | // AstDatabase |
@@ -242,15 +255,24 @@ impl RootDatabase { | |||
242 | hir::db::TraitImplsInCrateQuery | 255 | hir::db::TraitImplsInCrateQuery |
243 | hir::db::TraitImplsInDepsQuery | 256 | hir::db::TraitImplsInDepsQuery |
244 | hir::db::AssociatedTyDataQuery | 257 | hir::db::AssociatedTyDataQuery |
258 | hir::db::AssociatedTyDataQuery | ||
245 | hir::db::TraitDatumQuery | 259 | hir::db::TraitDatumQuery |
246 | hir::db::StructDatumQuery | 260 | hir::db::StructDatumQuery |
247 | hir::db::ImplDatumQuery | 261 | hir::db::ImplDatumQuery |
262 | hir::db::FnDefDatumQuery | ||
263 | hir::db::ReturnTypeImplTraitsQuery | ||
264 | hir::db::InternCallableDefQuery | ||
265 | hir::db::InternTypeParamIdQuery | ||
266 | hir::db::InternImplTraitIdQuery | ||
267 | hir::db::InternClosureQuery | ||
248 | hir::db::AssociatedTyValueQuery | 268 | hir::db::AssociatedTyValueQuery |
249 | hir::db::TraitSolveQuery | 269 | hir::db::TraitSolveQuery |
250 | hir::db::ReturnTypeImplTraitsQuery | ||
251 | 270 | ||
252 | // SymbolsDatabase | 271 | // SymbolsDatabase |
253 | crate::symbol_index::FileSymbolsQuery | 272 | crate::symbol_index::FileSymbolsQuery |
273 | crate::symbol_index::LibrarySymbolsQuery | ||
274 | crate::symbol_index::LocalRootsQuery | ||
275 | crate::symbol_index::LibraryRootsQuery | ||
254 | 276 | ||
255 | // LineIndexDatabase | 277 | // LineIndexDatabase |
256 | crate::LineIndexQuery | 278 | crate::LineIndexQuery |
diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index 66c048714..b51000b03 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs | |||
@@ -12,7 +12,7 @@ use hir::{ | |||
12 | use ra_prof::profile; | 12 | use ra_prof::profile; |
13 | use ra_syntax::{ | 13 | use ra_syntax::{ |
14 | ast::{self, AstNode}, | 14 | ast::{self, AstNode}, |
15 | match_ast, | 15 | match_ast, SyntaxNode, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | use crate::RootDatabase; | 18 | use crate::RootDatabase; |
@@ -123,8 +123,27 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option | |||
123 | let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?; | 123 | let use_tree = it.syntax().parent().and_then(ast::UseTree::cast)?; |
124 | let path = use_tree.path()?; | 124 | let path = use_tree.path()?; |
125 | let path_segment = path.segment()?; | 125 | let path_segment = path.segment()?; |
126 | let name_ref = path_segment.name_ref()?; | 126 | let name_ref_class = path_segment |
127 | let name_ref_class = classify_name_ref(sema, &name_ref)?; | 127 | .name_ref() |
128 | // The rename might be from a `self` token, so fallback to the name higher | ||
129 | // in the use tree. | ||
130 | .or_else(||{ | ||
131 | if path_segment.self_token().is_none() { | ||
132 | return None; | ||
133 | } | ||
134 | |||
135 | let use_tree = use_tree | ||
136 | .syntax() | ||
137 | .parent() | ||
138 | .as_ref() | ||
139 | // Skip over UseTreeList | ||
140 | .and_then(SyntaxNode::parent) | ||
141 | .and_then(ast::UseTree::cast)?; | ||
142 | let path = use_tree.path()?; | ||
143 | let path_segment = path.segment()?; | ||
144 | path_segment.name_ref() | ||
145 | }) | ||
146 | .and_then(|name_ref| classify_name_ref(sema, &name_ref))?; | ||
128 | 147 | ||
129 | Some(NameClass::Definition(name_ref_class.definition())) | 148 | Some(NameClass::Definition(name_ref_class.definition())) |
130 | }, | 149 | }, |
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs index 73abfecb2..c780b460a 100644 --- a/crates/ra_ssr/src/lib.rs +++ b/crates/ra_ssr/src/lib.rs | |||
@@ -66,12 +66,7 @@ impl<'db> MatchFinder<'db> { | |||
66 | restrict_ranges.retain(|range| !range.range.is_empty()); | 66 | restrict_ranges.retain(|range| !range.range.is_empty()); |
67 | let sema = Semantics::new(db); | 67 | let sema = Semantics::new(db); |
68 | let resolution_scope = resolving::ResolutionScope::new(&sema, lookup_context); | 68 | let resolution_scope = resolving::ResolutionScope::new(&sema, lookup_context); |
69 | MatchFinder { | 69 | MatchFinder { sema, rules: Vec::new(), resolution_scope, restrict_ranges } |
70 | sema: Semantics::new(db), | ||
71 | rules: Vec::new(), | ||
72 | resolution_scope, | ||
73 | restrict_ranges, | ||
74 | } | ||
75 | } | 70 | } |
76 | 71 | ||
77 | /// Constructs an instance using the start of the first file in `db` as the lookup context. | 72 | /// Constructs an instance using the start of the first file in `db` as the lookup context. |
diff --git a/crates/ra_ssr/src/resolving.rs b/crates/ra_ssr/src/resolving.rs index 6f62000f4..df60048eb 100644 --- a/crates/ra_ssr/src/resolving.rs +++ b/crates/ra_ssr/src/resolving.rs | |||
@@ -11,6 +11,7 @@ use test_utils::mark; | |||
11 | pub(crate) struct ResolutionScope<'db> { | 11 | pub(crate) struct ResolutionScope<'db> { |
12 | scope: hir::SemanticsScope<'db>, | 12 | scope: hir::SemanticsScope<'db>, |
13 | hygiene: hir::Hygiene, | 13 | hygiene: hir::Hygiene, |
14 | node: SyntaxNode, | ||
14 | } | 15 | } |
15 | 16 | ||
16 | pub(crate) struct ResolvedRule { | 17 | pub(crate) struct ResolvedRule { |
@@ -25,6 +26,7 @@ pub(crate) struct ResolvedPattern { | |||
25 | // Paths in `node` that we've resolved. | 26 | // Paths in `node` that we've resolved. |
26 | pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>, | 27 | pub(crate) resolved_paths: FxHashMap<SyntaxNode, ResolvedPath>, |
27 | pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>, | 28 | pub(crate) ufcs_function_calls: FxHashMap<SyntaxNode, hir::Function>, |
29 | pub(crate) contains_self: bool, | ||
28 | } | 30 | } |
29 | 31 | ||
30 | pub(crate) struct ResolvedPath { | 32 | pub(crate) struct ResolvedPath { |
@@ -68,6 +70,7 @@ struct Resolver<'a, 'db> { | |||
68 | 70 | ||
69 | impl Resolver<'_, '_> { | 71 | impl Resolver<'_, '_> { |
70 | fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> { | 72 | fn resolve_pattern_tree(&self, pattern: SyntaxNode) -> Result<ResolvedPattern, SsrError> { |
73 | use ra_syntax::{SyntaxElement, T}; | ||
71 | let mut resolved_paths = FxHashMap::default(); | 74 | let mut resolved_paths = FxHashMap::default(); |
72 | self.resolve(pattern.clone(), 0, &mut resolved_paths)?; | 75 | self.resolve(pattern.clone(), 0, &mut resolved_paths)?; |
73 | let ufcs_function_calls = resolved_paths | 76 | let ufcs_function_calls = resolved_paths |
@@ -85,11 +88,17 @@ impl Resolver<'_, '_> { | |||
85 | None | 88 | None |
86 | }) | 89 | }) |
87 | .collect(); | 90 | .collect(); |
91 | let contains_self = | ||
92 | pattern.descendants_with_tokens().any(|node_or_token| match node_or_token { | ||
93 | SyntaxElement::Token(t) => t.kind() == T![self], | ||
94 | _ => false, | ||
95 | }); | ||
88 | Ok(ResolvedPattern { | 96 | Ok(ResolvedPattern { |
89 | node: pattern, | 97 | node: pattern, |
90 | resolved_paths, | 98 | resolved_paths, |
91 | placeholders_by_stand_in: self.placeholders_by_stand_in.clone(), | 99 | placeholders_by_stand_in: self.placeholders_by_stand_in.clone(), |
92 | ufcs_function_calls, | 100 | ufcs_function_calls, |
101 | contains_self, | ||
93 | }) | 102 | }) |
94 | } | 103 | } |
95 | 104 | ||
@@ -101,6 +110,10 @@ impl Resolver<'_, '_> { | |||
101 | ) -> Result<(), SsrError> { | 110 | ) -> Result<(), SsrError> { |
102 | use ra_syntax::ast::AstNode; | 111 | use ra_syntax::ast::AstNode; |
103 | if let Some(path) = ast::Path::cast(node.clone()) { | 112 | if let Some(path) = ast::Path::cast(node.clone()) { |
113 | if is_self(&path) { | ||
114 | // Self cannot be resolved like other paths. | ||
115 | return Ok(()); | ||
116 | } | ||
104 | // Check if this is an appropriate place in the path to resolve. If the path is | 117 | // Check if this is an appropriate place in the path to resolve. If the path is |
105 | // something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains | 118 | // something like `a::B::<i32>::c` then we want to resolve `a::B`. If the path contains |
106 | // a placeholder. e.g. `a::$b::c` then we want to resolve `a`. | 119 | // a placeholder. e.g. `a::$b::c` then we want to resolve `a`. |
@@ -157,9 +170,15 @@ impl<'db> ResolutionScope<'db> { | |||
157 | ResolutionScope { | 170 | ResolutionScope { |
158 | scope, | 171 | scope, |
159 | hygiene: hir::Hygiene::new(sema.db, resolve_context.file_id.into()), | 172 | hygiene: hir::Hygiene::new(sema.db, resolve_context.file_id.into()), |
173 | node, | ||
160 | } | 174 | } |
161 | } | 175 | } |
162 | 176 | ||
177 | /// Returns the function in which SSR was invoked, if any. | ||
178 | pub(crate) fn current_function(&self) -> Option<SyntaxNode> { | ||
179 | self.node.ancestors().find(|node| node.kind() == SyntaxKind::FN).map(|node| node.clone()) | ||
180 | } | ||
181 | |||
163 | fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> { | 182 | fn resolve_path(&self, path: &ast::Path) -> Option<hir::PathResolution> { |
164 | let hir_path = hir::Path::from_src(path.clone(), &self.hygiene)?; | 183 | let hir_path = hir::Path::from_src(path.clone(), &self.hygiene)?; |
165 | // First try resolving the whole path. This will work for things like | 184 | // First try resolving the whole path. This will work for things like |
@@ -186,6 +205,10 @@ impl<'db> ResolutionScope<'db> { | |||
186 | } | 205 | } |
187 | } | 206 | } |
188 | 207 | ||
208 | fn is_self(path: &ast::Path) -> bool { | ||
209 | path.segment().map(|segment| segment.self_token().is_some()).unwrap_or(false) | ||
210 | } | ||
211 | |||
189 | /// Returns a suitable node for resolving paths in the current scope. If we create a scope based on | 212 | /// Returns a suitable node for resolving paths in the current scope. If we create a scope based on |
190 | /// a statement node, then we can't resolve local variables that were defined in the current scope | 213 | /// a statement node, then we can't resolve local variables that were defined in the current scope |
191 | /// (only in parent scopes). So we find another node, ideally a child of the statement where local | 214 | /// (only in parent scopes). So we find another node, ideally a child of the statement where local |
diff --git a/crates/ra_ssr/src/search.rs b/crates/ra_ssr/src/search.rs index 213dc494f..85ffa2ac2 100644 --- a/crates/ra_ssr/src/search.rs +++ b/crates/ra_ssr/src/search.rs | |||
@@ -33,6 +33,15 @@ impl<'db> MatchFinder<'db> { | |||
33 | usage_cache: &mut UsageCache, | 33 | usage_cache: &mut UsageCache, |
34 | matches_out: &mut Vec<Match>, | 34 | matches_out: &mut Vec<Match>, |
35 | ) { | 35 | ) { |
36 | if rule.pattern.contains_self { | ||
37 | // If the pattern contains `self` we restrict the scope of the search to just the | ||
38 | // current method. No other method can reference the same `self`. This makes the | ||
39 | // behavior of `self` consistent with other variables. | ||
40 | if let Some(current_function) = self.resolution_scope.current_function() { | ||
41 | self.slow_scan_node(¤t_function, rule, &None, matches_out); | ||
42 | } | ||
43 | return; | ||
44 | } | ||
36 | if pick_path_for_usages(&rule.pattern).is_none() { | 45 | if pick_path_for_usages(&rule.pattern).is_none() { |
37 | self.slow_scan(rule, matches_out); | 46 | self.slow_scan(rule, matches_out); |
38 | return; | 47 | return; |
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs index 2ae03c64c..d483640df 100644 --- a/crates/ra_ssr/src/tests.rs +++ b/crates/ra_ssr/src/tests.rs | |||
@@ -1044,3 +1044,38 @@ fn replace_nonpath_within_selection() { | |||
1044 | }"#]], | 1044 | }"#]], |
1045 | ); | 1045 | ); |
1046 | } | 1046 | } |
1047 | |||
1048 | #[test] | ||
1049 | fn replace_self() { | ||
1050 | // `foo(self)` occurs twice in the code, however only the first occurrence is the `self` that's | ||
1051 | // in scope where the rule is invoked. | ||
1052 | assert_ssr_transform( | ||
1053 | "foo(self) ==>> bar(self)", | ||
1054 | r#" | ||
1055 | struct S1 {} | ||
1056 | fn foo(_: &S1) {} | ||
1057 | fn bar(_: &S1) {} | ||
1058 | impl S1 { | ||
1059 | fn f1(&self) { | ||
1060 | foo(self)<|> | ||
1061 | } | ||
1062 | fn f2(&self) { | ||
1063 | foo(self) | ||
1064 | } | ||
1065 | } | ||
1066 | "#, | ||
1067 | expect![[r#" | ||
1068 | struct S1 {} | ||
1069 | fn foo(_: &S1) {} | ||
1070 | fn bar(_: &S1) {} | ||
1071 | impl S1 { | ||
1072 | fn f1(&self) { | ||
1073 | bar(self) | ||
1074 | } | ||
1075 | fn f2(&self) { | ||
1076 | foo(self) | ||
1077 | } | ||
1078 | } | ||
1079 | "#]], | ||
1080 | ); | ||
1081 | } | ||
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 667a9294f..5ed123f91 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs | |||
@@ -621,7 +621,7 @@ fn single_node(element: impl Into<SyntaxElement>) -> RangeInclusive<SyntaxElemen | |||
621 | #[test] | 621 | #[test] |
622 | fn test_increase_indent() { | 622 | fn test_increase_indent() { |
623 | let arm_list = { | 623 | let arm_list = { |
624 | let arm = make::match_arm(iter::once(make::placeholder_pat().into()), make::expr_unit()); | 624 | let arm = make::match_arm(iter::once(make::wildcard_pat().into()), make::expr_unit()); |
625 | make::match_arm_list(vec![arm.clone(), arm]) | 625 | make::match_arm_list(vec![arm.clone(), arm]) |
626 | }; | 626 | }; |
627 | assert_eq!( | 627 | assert_eq!( |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 673777015..254a37fe3 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -17,7 +17,7 @@ pub fn name_ref(text: &str) -> ast::NameRef { | |||
17 | ast_from_text(&format!("fn f() {{ {}; }}", text)) | 17 | ast_from_text(&format!("fn f() {{ {}; }}", text)) |
18 | } | 18 | } |
19 | 19 | ||
20 | pub fn type_ref(text: &str) -> ast::Type { | 20 | pub fn ty(text: &str) -> ast::Type { |
21 | ast_from_text(&format!("impl {} for D {{}};", text)) | 21 | ast_from_text(&format!("impl {} for D {{}};", text)) |
22 | } | 22 | } |
23 | 23 | ||
@@ -30,7 +30,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { | |||
30 | pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { | 30 | pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { |
31 | path_from_text(&format!("{}::{}", qual, segment)) | 31 | path_from_text(&format!("{}::{}", qual, segment)) |
32 | } | 32 | } |
33 | fn path_from_text(text: &str) -> ast::Path { | 33 | pub fn path_from_text(text: &str) -> ast::Path { |
34 | ast_from_text(text) | 34 | ast_from_text(text) |
35 | } | 35 | } |
36 | 36 | ||
@@ -60,11 +60,11 @@ pub fn use_tree_list(use_trees: impl IntoIterator<Item = ast::UseTree>) -> ast:: | |||
60 | ast_from_text(&format!("use {{{}}};", use_trees)) | 60 | ast_from_text(&format!("use {{{}}};", use_trees)) |
61 | } | 61 | } |
62 | 62 | ||
63 | pub fn use_item(use_tree: ast::UseTree) -> ast::Use { | 63 | pub fn use_(use_tree: ast::UseTree) -> ast::Use { |
64 | ast_from_text(&format!("use {};", use_tree)) | 64 | ast_from_text(&format!("use {};", use_tree)) |
65 | } | 65 | } |
66 | 66 | ||
67 | pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { | 67 | pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { |
68 | return match expr { | 68 | return match expr { |
69 | Some(expr) => from_text(&format!("{}: {}", name, expr)), | 69 | Some(expr) => from_text(&format!("{}: {}", name, expr)), |
70 | None => from_text(&name.to_string()), | 70 | None => from_text(&name.to_string()), |
@@ -75,7 +75,7 @@ pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordE | |||
75 | } | 75 | } |
76 | } | 76 | } |
77 | 77 | ||
78 | pub fn record_field_def(name: ast::NameRef, ty: ast::Type) -> ast::RecordField { | 78 | pub fn record_field(name: ast::NameRef, ty: ast::Type) -> ast::RecordField { |
79 | ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) | 79 | ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) |
80 | } | 80 | } |
81 | 81 | ||
@@ -148,7 +148,7 @@ pub fn condition(expr: ast::Expr, pattern: Option<ast::Pat>) -> ast::Condition { | |||
148 | } | 148 | } |
149 | } | 149 | } |
150 | 150 | ||
151 | pub fn bind_pat(name: ast::Name) -> ast::IdentPat { | 151 | pub fn ident_pat(name: ast::Name) -> ast::IdentPat { |
152 | return from_text(name.text()); | 152 | return from_text(name.text()); |
153 | 153 | ||
154 | fn from_text(text: &str) -> ast::IdentPat { | 154 | fn from_text(text: &str) -> ast::IdentPat { |
@@ -156,7 +156,7 @@ pub fn bind_pat(name: ast::Name) -> ast::IdentPat { | |||
156 | } | 156 | } |
157 | } | 157 | } |
158 | 158 | ||
159 | pub fn placeholder_pat() -> ast::WildcardPat { | 159 | pub fn wildcard_pat() -> ast::WildcardPat { |
160 | return from_text("_"); | 160 | return from_text("_"); |
161 | 161 | ||
162 | fn from_text(text: &str) -> ast::WildcardPat { | 162 | fn from_text(text: &str) -> ast::WildcardPat { |
@@ -288,7 +288,7 @@ pub fn visibility_pub_crate() -> ast::Visibility { | |||
288 | ast_from_text("pub(crate) struct S") | 288 | ast_from_text("pub(crate) struct S") |
289 | } | 289 | } |
290 | 290 | ||
291 | pub fn fn_def( | 291 | pub fn fn_( |
292 | visibility: Option<ast::Visibility>, | 292 | visibility: Option<ast::Visibility>, |
293 | fn_name: ast::Name, | 293 | fn_name: ast::Name, |
294 | type_params: Option<ast::GenericParamList>, | 294 | type_params: Option<ast::GenericParamList>, |
diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 37d695448..92a743fd8 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs | |||
@@ -76,7 +76,9 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti | |||
76 | token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.to_vec(), | 76 | token_modifiers: semantic_tokens::SUPPORTED_MODIFIERS.to_vec(), |
77 | }, | 77 | }, |
78 | 78 | ||
79 | document_provider: Some(SemanticTokensDocumentProvider::Bool(true)), | 79 | document_provider: Some(SemanticTokensDocumentProvider::Edits { |
80 | edits: Some(true), | ||
81 | }), | ||
80 | range_provider: Some(true), | 82 | range_provider: Some(true), |
81 | work_done_progress_options: Default::default(), | 83 | work_done_progress_options: Default::default(), |
82 | } | 84 | } |
diff --git a/crates/rust-analyzer/src/document.rs b/crates/rust-analyzer/src/document.rs index 43219e633..e882c9865 100644 --- a/crates/rust-analyzer/src/document.rs +++ b/crates/rust-analyzer/src/document.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | //! In-memory document information. | 1 | //! In-memory document information. |
2 | 2 | ||
3 | /// Information about a document that the Language Client | 3 | /// Information about a document that the Language Client |
4 | // knows about. | 4 | /// knows about. |
5 | // Its lifetime is driven by the textDocument/didOpen and textDocument/didClose | 5 | /// Its lifetime is driven by the textDocument/didOpen and textDocument/didClose |
6 | // client notifications. | 6 | /// client notifications. |
7 | #[derive(Debug, Clone)] | 7 | #[derive(Debug, Clone)] |
8 | pub(crate) struct DocumentData { | 8 | pub(crate) struct DocumentData { |
9 | pub version: Option<i64>, | 9 | pub version: Option<i64>, |
diff --git a/crates/rust-analyzer/src/global_state.rs b/crates/rust-analyzer/src/global_state.rs index b2d65a6d1..0e592ac1b 100644 --- a/crates/rust-analyzer/src/global_state.rs +++ b/crates/rust-analyzer/src/global_state.rs | |||
@@ -7,8 +7,8 @@ use std::{sync::Arc, time::Instant}; | |||
7 | 7 | ||
8 | use crossbeam_channel::{unbounded, Receiver, Sender}; | 8 | use crossbeam_channel::{unbounded, Receiver, Sender}; |
9 | use flycheck::FlycheckHandle; | 9 | use flycheck::FlycheckHandle; |
10 | use lsp_types::Url; | 10 | use lsp_types::{SemanticTokens, Url}; |
11 | use parking_lot::RwLock; | 11 | use parking_lot::{Mutex, RwLock}; |
12 | use ra_db::{CrateId, VfsPath}; | 12 | use ra_db::{CrateId, VfsPath}; |
13 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId}; | 13 | use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId}; |
14 | use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; | 14 | use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; |
@@ -71,6 +71,7 @@ pub(crate) struct GlobalState { | |||
71 | pub(crate) analysis_host: AnalysisHost, | 71 | pub(crate) analysis_host: AnalysisHost, |
72 | pub(crate) diagnostics: DiagnosticCollection, | 72 | pub(crate) diagnostics: DiagnosticCollection, |
73 | pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, | 73 | pub(crate) mem_docs: FxHashMap<VfsPath, DocumentData>, |
74 | pub(crate) semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, | ||
74 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, | 75 | pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, |
75 | pub(crate) status: Status, | 76 | pub(crate) status: Status, |
76 | pub(crate) source_root_config: SourceRootConfig, | 77 | pub(crate) source_root_config: SourceRootConfig, |
@@ -86,6 +87,7 @@ pub(crate) struct GlobalStateSnapshot { | |||
86 | pub(crate) check_fixes: CheckFixes, | 87 | pub(crate) check_fixes: CheckFixes, |
87 | pub(crate) latest_requests: Arc<RwLock<LatestRequests>>, | 88 | pub(crate) latest_requests: Arc<RwLock<LatestRequests>>, |
88 | mem_docs: FxHashMap<VfsPath, DocumentData>, | 89 | mem_docs: FxHashMap<VfsPath, DocumentData>, |
90 | pub semantic_tokens_cache: Arc<Mutex<FxHashMap<Url, SemanticTokens>>>, | ||
89 | vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, | 91 | vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>, |
90 | pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, | 92 | pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>, |
91 | } | 93 | } |
@@ -120,6 +122,7 @@ impl GlobalState { | |||
120 | analysis_host, | 122 | analysis_host, |
121 | diagnostics: Default::default(), | 123 | diagnostics: Default::default(), |
122 | mem_docs: FxHashMap::default(), | 124 | mem_docs: FxHashMap::default(), |
125 | semantic_tokens_cache: Arc::new(Default::default()), | ||
123 | vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), | 126 | vfs: Arc::new(RwLock::new((vfs::Vfs::default(), FxHashMap::default()))), |
124 | status: Status::default(), | 127 | status: Status::default(), |
125 | source_root_config: SourceRootConfig::default(), | 128 | source_root_config: SourceRootConfig::default(), |
@@ -186,6 +189,7 @@ impl GlobalState { | |||
186 | latest_requests: Arc::clone(&self.latest_requests), | 189 | latest_requests: Arc::clone(&self.latest_requests), |
187 | check_fixes: Arc::clone(&self.diagnostics.check_fixes), | 190 | check_fixes: Arc::clone(&self.diagnostics.check_fixes), |
188 | mem_docs: self.mem_docs.clone(), | 191 | mem_docs: self.mem_docs.clone(), |
192 | semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache), | ||
189 | } | 193 | } |
190 | } | 194 | } |
191 | 195 | ||
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 6994e611b..895af1dd7 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -13,9 +13,10 @@ use lsp_types::{ | |||
13 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | 13 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, |
14 | CodeActionKind, CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, | 14 | CodeActionKind, CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, |
15 | DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, | 15 | DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, |
16 | Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams, | 16 | Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensEditResult, |
17 | SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, | 17 | SemanticTokensEditsParams, SemanticTokensParams, SemanticTokensRangeParams, |
18 | SymbolTag, TextDocumentIdentifier, Url, WorkspaceEdit, | 18 | SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, SymbolTag, |
19 | TextDocumentIdentifier, Url, WorkspaceEdit, | ||
19 | }; | 20 | }; |
20 | use ra_ide::{ | 21 | use ra_ide::{ |
21 | FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, | 22 | FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query, |
@@ -709,11 +710,6 @@ pub(crate) fn handle_formatting( | |||
709 | } | 710 | } |
710 | }; | 711 | }; |
711 | 712 | ||
712 | if let Ok(path) = params.text_document.uri.to_file_path() { | ||
713 | if let Some(parent) = path.parent() { | ||
714 | rustfmt.current_dir(parent); | ||
715 | } | ||
716 | } | ||
717 | let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; | 713 | let mut rustfmt = rustfmt.stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; |
718 | 714 | ||
719 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; | 715 | rustfmt.stdin.as_mut().unwrap().write_all(file.as_bytes())?; |
@@ -1184,6 +1180,40 @@ pub(crate) fn handle_semantic_tokens( | |||
1184 | 1180 | ||
1185 | let highlights = snap.analysis.highlight(file_id)?; | 1181 | let highlights = snap.analysis.highlight(file_id)?; |
1186 | let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); | 1182 | let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); |
1183 | |||
1184 | // Unconditionally cache the tokens | ||
1185 | snap.semantic_tokens_cache.lock().insert(params.text_document.uri, semantic_tokens.clone()); | ||
1186 | |||
1187 | Ok(Some(semantic_tokens.into())) | ||
1188 | } | ||
1189 | |||
1190 | pub(crate) fn handle_semantic_tokens_edits( | ||
1191 | snap: GlobalStateSnapshot, | ||
1192 | params: SemanticTokensEditsParams, | ||
1193 | ) -> Result<Option<SemanticTokensEditResult>> { | ||
1194 | let _p = profile("handle_semantic_tokens_edits"); | ||
1195 | |||
1196 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | ||
1197 | let text = snap.analysis.file_text(file_id)?; | ||
1198 | let line_index = snap.analysis.file_line_index(file_id)?; | ||
1199 | |||
1200 | let highlights = snap.analysis.highlight(file_id)?; | ||
1201 | |||
1202 | let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); | ||
1203 | |||
1204 | let mut cache = snap.semantic_tokens_cache.lock(); | ||
1205 | let cached_tokens = cache.entry(params.text_document.uri).or_default(); | ||
1206 | |||
1207 | if let Some(prev_id) = &cached_tokens.result_id { | ||
1208 | if *prev_id == params.previous_result_id { | ||
1209 | let edits = to_proto::semantic_token_edits(&cached_tokens, &semantic_tokens); | ||
1210 | *cached_tokens = semantic_tokens; | ||
1211 | return Ok(Some(edits.into())); | ||
1212 | } | ||
1213 | } | ||
1214 | |||
1215 | *cached_tokens = semantic_tokens.clone(); | ||
1216 | |||
1187 | Ok(Some(semantic_tokens.into())) | 1217 | Ok(Some(semantic_tokens.into())) |
1188 | } | 1218 | } |
1189 | 1219 | ||
diff --git a/crates/rust-analyzer/src/lsp_utils.rs b/crates/rust-analyzer/src/lsp_utils.rs index d4cc9dd04..0bc3ff115 100644 --- a/crates/rust-analyzer/src/lsp_utils.rs +++ b/crates/rust-analyzer/src/lsp_utils.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | //! Utilities for LSP-related boilerplate code. | 1 | //! Utilities for LSP-related boilerplate code. |
2 | use std::{borrow::Cow, error::Error, ops::Range}; | 2 | use std::{error::Error, ops::Range}; |
3 | 3 | ||
4 | use lsp_server::Notification; | 4 | use lsp_server::Notification; |
5 | use ra_db::Canceled; | 5 | use ra_db::Canceled; |
@@ -84,8 +84,8 @@ impl GlobalState { | |||
84 | pub(crate) fn apply_document_changes( | 84 | pub(crate) fn apply_document_changes( |
85 | old_text: &mut String, | 85 | old_text: &mut String, |
86 | content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, | 86 | content_changes: Vec<lsp_types::TextDocumentContentChangeEvent>, |
87 | mut line_index: Cow<'_, LineIndex>, | ||
88 | ) { | 87 | ) { |
88 | let mut line_index = LineIndex::new(old_text); | ||
89 | // The changes we got must be applied sequentially, but can cross lines so we | 89 | // The changes we got must be applied sequentially, but can cross lines so we |
90 | // have to keep our line index updated. | 90 | // have to keep our line index updated. |
91 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we | 91 | // Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we |
@@ -110,7 +110,7 @@ pub(crate) fn apply_document_changes( | |||
110 | match change.range { | 110 | match change.range { |
111 | Some(range) => { | 111 | Some(range) => { |
112 | if !index_valid.covers(range.end.line) { | 112 | if !index_valid.covers(range.end.line) { |
113 | line_index = Cow::Owned(LineIndex::new(old_text)); | 113 | line_index = LineIndex::new(&old_text); |
114 | } | 114 | } |
115 | index_valid = IndexValid::UpToLineExclusive(range.start.line); | 115 | index_valid = IndexValid::UpToLineExclusive(range.start.line); |
116 | let range = from_proto::text_range(&line_index, range); | 116 | let range = from_proto::text_range(&line_index, range); |
@@ -145,15 +145,10 @@ mod tests { | |||
145 | }; | 145 | }; |
146 | } | 146 | } |
147 | 147 | ||
148 | fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) { | ||
149 | let line_index = Cow::Owned(LineIndex::new(&text)); | ||
150 | super::apply_document_changes(text, changes, line_index); | ||
151 | } | ||
152 | |||
153 | let mut text = String::new(); | 148 | let mut text = String::new(); |
154 | run(&mut text, vec![]); | 149 | apply_document_changes(&mut text, vec![]); |
155 | assert_eq!(text, ""); | 150 | assert_eq!(text, ""); |
156 | run( | 151 | apply_document_changes( |
157 | &mut text, | 152 | &mut text, |
158 | vec![TextDocumentContentChangeEvent { | 153 | vec![TextDocumentContentChangeEvent { |
159 | range: None, | 154 | range: None, |
@@ -162,36 +157,39 @@ mod tests { | |||
162 | }], | 157 | }], |
163 | ); | 158 | ); |
164 | assert_eq!(text, "the"); | 159 | assert_eq!(text, "the"); |
165 | run(&mut text, c![0, 3; 0, 3 => " quick"]); | 160 | apply_document_changes(&mut text, c![0, 3; 0, 3 => " quick"]); |
166 | assert_eq!(text, "the quick"); | 161 | assert_eq!(text, "the quick"); |
167 | run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); | 162 | apply_document_changes(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]); |
168 | assert_eq!(text, "quick foxes"); | 163 | assert_eq!(text, "quick foxes"); |
169 | run(&mut text, c![0, 11; 0, 11 => "\ndream"]); | 164 | apply_document_changes(&mut text, c![0, 11; 0, 11 => "\ndream"]); |
170 | assert_eq!(text, "quick foxes\ndream"); | 165 | assert_eq!(text, "quick foxes\ndream"); |
171 | run(&mut text, c![1, 0; 1, 0 => "have "]); | 166 | apply_document_changes(&mut text, c![1, 0; 1, 0 => "have "]); |
172 | assert_eq!(text, "quick foxes\nhave dream"); | 167 | assert_eq!(text, "quick foxes\nhave dream"); |
173 | run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]); | 168 | apply_document_changes( |
169 | &mut text, | ||
170 | c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"], | ||
171 | ); | ||
174 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); | 172 | assert_eq!(text, "the quick foxes\nhave quiet dreams\n"); |
175 | run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); | 173 | apply_document_changes(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]); |
176 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); | 174 | assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n"); |
177 | run( | 175 | apply_document_changes( |
178 | &mut text, | 176 | &mut text, |
179 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], | 177 | c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"], |
180 | ); | 178 | ); |
181 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); | 179 | assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n"); |
182 | run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); | 180 | apply_document_changes(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]); |
183 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); | 181 | assert_eq!(text, "the quick \nthey have quiet dreams\n"); |
184 | 182 | ||
185 | text = String::from("❤️"); | 183 | text = String::from("❤️"); |
186 | run(&mut text, c![0, 0; 0, 0 => "a"]); | 184 | apply_document_changes(&mut text, c![0, 0; 0, 0 => "a"]); |
187 | assert_eq!(text, "a❤️"); | 185 | assert_eq!(text, "a❤️"); |
188 | 186 | ||
189 | text = String::from("a\nb"); | 187 | text = String::from("a\nb"); |
190 | run(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); | 188 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "\nțc", 0, 1; 1, 1 => "d"]); |
191 | assert_eq!(text, "adcb"); | 189 | assert_eq!(text, "adcb"); |
192 | 190 | ||
193 | text = String::from("a\nb"); | 191 | text = String::from("a\nb"); |
194 | run(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); | 192 | apply_document_changes(&mut text, c![0, 1; 1, 0 => "ț\nc", 0, 2; 0, 2 => "c"]); |
195 | assert_eq!(text, "ațc\ncb"); | 193 | assert_eq!(text, "ațc\ncb"); |
196 | } | 194 | } |
197 | } | 195 | } |
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 0ace4cb45..438e965e0 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -1,14 +1,13 @@ | |||
1 | //! The main loop of `rust-analyzer` responsible for dispatching LSP | 1 | //! The main loop of `rust-analyzer` responsible for dispatching LSP |
2 | //! requests/replies and notifications back to the client. | 2 | //! requests/replies and notifications back to the client. |
3 | use std::{ | 3 | use std::{ |
4 | borrow::Cow, | ||
5 | env, fmt, panic, | 4 | env, fmt, panic, |
6 | time::{Duration, Instant}, | 5 | time::{Duration, Instant}, |
7 | }; | 6 | }; |
8 | 7 | ||
9 | use crossbeam_channel::{select, Receiver}; | 8 | use crossbeam_channel::{select, Receiver}; |
10 | use lsp_server::{Connection, Notification, Request, Response}; | 9 | use lsp_server::{Connection, Notification, Request, Response}; |
11 | use lsp_types::{notification::Notification as _, DidChangeTextDocumentParams}; | 10 | use lsp_types::notification::Notification as _; |
12 | use ra_db::VfsPath; | 11 | use ra_db::VfsPath; |
13 | use ra_ide::{Canceled, FileId}; | 12 | use ra_ide::{Canceled, FileId}; |
14 | use ra_prof::profile; | 13 | use ra_prof::profile; |
@@ -48,7 +47,7 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | |||
48 | SetThreadPriority(thread, thread_priority_above_normal); | 47 | SetThreadPriority(thread, thread_priority_above_normal); |
49 | } | 48 | } |
50 | 49 | ||
51 | GlobalState::new(connection.sender.clone(), config).run(connection.receiver) | 50 | GlobalState::new(connection.sender, config).run(connection.receiver) |
52 | } | 51 | } |
53 | 52 | ||
54 | enum Event { | 53 | enum Event { |
@@ -387,6 +386,9 @@ impl GlobalState { | |||
387 | handlers::handle_call_hierarchy_outgoing, | 386 | handlers::handle_call_hierarchy_outgoing, |
388 | )? | 387 | )? |
389 | .on::<lsp_types::request::SemanticTokensRequest>(handlers::handle_semantic_tokens)? | 388 | .on::<lsp_types::request::SemanticTokensRequest>(handlers::handle_semantic_tokens)? |
389 | .on::<lsp_types::request::SemanticTokensEditsRequest>( | ||
390 | handlers::handle_semantic_tokens_edits, | ||
391 | )? | ||
390 | .on::<lsp_types::request::SemanticTokensRangeRequest>( | 392 | .on::<lsp_types::request::SemanticTokensRangeRequest>( |
391 | handlers::handle_semantic_tokens_range, | 393 | handlers::handle_semantic_tokens_range, |
392 | )? | 394 | )? |
@@ -422,20 +424,15 @@ impl GlobalState { | |||
422 | })? | 424 | })? |
423 | .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| { | 425 | .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| { |
424 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { | 426 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
425 | let DidChangeTextDocumentParams { text_document, content_changes } = params; | 427 | let doc = this.mem_docs.get_mut(&path).unwrap(); |
426 | let vfs = &mut this.vfs.write().0; | 428 | let vfs = &mut this.vfs.write().0; |
427 | let world = this.snapshot(); | ||
428 | let file_id = vfs.file_id(&path).unwrap(); | 429 | let file_id = vfs.file_id(&path).unwrap(); |
429 | |||
430 | // let file_id = vfs.file_id(&path).unwrap(); | ||
431 | let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); | 430 | let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); |
432 | let line_index = world.analysis.file_line_index(file_id)?; | 431 | apply_document_changes(&mut text, params.content_changes); |
433 | apply_document_changes(&mut text, content_changes, Cow::Borrowed(&line_index)); | ||
434 | 432 | ||
435 | // The version passed in DidChangeTextDocument is the version after all edits are applied | 433 | // The version passed in DidChangeTextDocument is the version after all edits are applied |
436 | // so we should apply it before the vfs is notified. | 434 | // so we should apply it before the vfs is notified. |
437 | let doc = this.mem_docs.get_mut(&path).unwrap(); | 435 | doc.version = params.text_document.version; |
438 | doc.version = text_document.version; | ||
439 | 436 | ||
440 | vfs.set_file_contents(path.clone(), Some(text.into_bytes())); | 437 | vfs.set_file_contents(path.clone(), Some(text.into_bytes())); |
441 | } | 438 | } |
@@ -449,6 +446,8 @@ impl GlobalState { | |||
449 | None => log::error!("orphan DidCloseTextDocument: {}", path), | 446 | None => log::error!("orphan DidCloseTextDocument: {}", path), |
450 | } | 447 | } |
451 | 448 | ||
449 | this.semantic_tokens_cache.lock().remove(¶ms.text_document.uri); | ||
450 | |||
452 | if let Some(path) = path.as_path() { | 451 | if let Some(path) = path.as_path() { |
453 | this.loader.handle.invalidate(path.to_path_buf()); | 452 | this.loader.handle.invalidate(path.to_path_buf()); |
454 | } | 453 | } |
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs index 576bd8adc..afc38fb4e 100644 --- a/crates/rust-analyzer/src/semantic_tokens.rs +++ b/crates/rust-analyzer/src/semantic_tokens.rs | |||
@@ -2,7 +2,10 @@ | |||
2 | 2 | ||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens}; | 5 | use lsp_types::{ |
6 | Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens, | ||
7 | SemanticTokensEdit, | ||
8 | }; | ||
6 | 9 | ||
7 | macro_rules! define_semantic_token_types { | 10 | macro_rules! define_semantic_token_types { |
8 | ($(($ident:ident, $string:literal)),*$(,)?) => { | 11 | ($(($ident:ident, $string:literal)),*$(,)?) => { |
@@ -89,14 +92,18 @@ impl ops::BitOrAssign<SemanticTokenModifier> for ModifierSet { | |||
89 | /// Tokens are encoded relative to each other. | 92 | /// Tokens are encoded relative to each other. |
90 | /// | 93 | /// |
91 | /// This is a direct port of https://github.com/microsoft/vscode-languageserver-node/blob/f425af9de46a0187adb78ec8a46b9b2ce80c5412/server/src/sematicTokens.proposed.ts#L45 | 94 | /// This is a direct port of https://github.com/microsoft/vscode-languageserver-node/blob/f425af9de46a0187adb78ec8a46b9b2ce80c5412/server/src/sematicTokens.proposed.ts#L45 |
92 | #[derive(Default)] | ||
93 | pub(crate) struct SemanticTokensBuilder { | 95 | pub(crate) struct SemanticTokensBuilder { |
96 | id: String, | ||
94 | prev_line: u32, | 97 | prev_line: u32, |
95 | prev_char: u32, | 98 | prev_char: u32, |
96 | data: Vec<SemanticToken>, | 99 | data: Vec<SemanticToken>, |
97 | } | 100 | } |
98 | 101 | ||
99 | impl SemanticTokensBuilder { | 102 | impl SemanticTokensBuilder { |
103 | pub fn new(id: String) -> Self { | ||
104 | SemanticTokensBuilder { id, prev_line: 0, prev_char: 0, data: Default::default() } | ||
105 | } | ||
106 | |||
100 | /// Push a new token onto the builder | 107 | /// Push a new token onto the builder |
101 | pub fn push(&mut self, range: Range, token_index: u32, modifier_bitset: u32) { | 108 | pub fn push(&mut self, range: Range, token_index: u32, modifier_bitset: u32) { |
102 | let mut push_line = range.start.line as u32; | 109 | let mut push_line = range.start.line as u32; |
@@ -127,10 +134,136 @@ impl SemanticTokensBuilder { | |||
127 | } | 134 | } |
128 | 135 | ||
129 | pub fn build(self) -> SemanticTokens { | 136 | pub fn build(self) -> SemanticTokens { |
130 | SemanticTokens { result_id: None, data: self.data } | 137 | SemanticTokens { result_id: Some(self.id), data: self.data } |
138 | } | ||
139 | } | ||
140 | |||
141 | pub fn diff_tokens(old: &[SemanticToken], new: &[SemanticToken]) -> Vec<SemanticTokensEdit> { | ||
142 | let offset = new.iter().zip(old.iter()).take_while(|&(n, p)| n == p).count(); | ||
143 | |||
144 | let (_, old) = old.split_at(offset); | ||
145 | let (_, new) = new.split_at(offset); | ||
146 | |||
147 | let offset_from_end = | ||
148 | new.iter().rev().zip(old.iter().rev()).take_while(|&(n, p)| n == p).count(); | ||
149 | |||
150 | let (old, _) = old.split_at(old.len() - offset_from_end); | ||
151 | let (new, _) = new.split_at(new.len() - offset_from_end); | ||
152 | |||
153 | if old.is_empty() && new.is_empty() { | ||
154 | vec![] | ||
155 | } else { | ||
156 | // The lsp data field is actually a byte-diff but we | ||
157 | // travel in tokens so `start` and `delete_count` are in multiples of the | ||
158 | // serialized size of `SemanticToken`. | ||
159 | vec![SemanticTokensEdit { | ||
160 | start: 5 * offset as u32, | ||
161 | delete_count: 5 * old.len() as u32, | ||
162 | data: Some(new.into()), | ||
163 | }] | ||
131 | } | 164 | } |
132 | } | 165 | } |
133 | 166 | ||
134 | pub fn type_index(type_: SemanticTokenType) -> u32 { | 167 | pub fn type_index(type_: SemanticTokenType) -> u32 { |
135 | SUPPORTED_TYPES.iter().position(|it| *it == type_).unwrap() as u32 | 168 | SUPPORTED_TYPES.iter().position(|it| *it == type_).unwrap() as u32 |
136 | } | 169 | } |
170 | |||
171 | #[cfg(test)] | ||
172 | mod tests { | ||
173 | use super::*; | ||
174 | |||
175 | fn from(t: (u32, u32, u32, u32, u32)) -> SemanticToken { | ||
176 | SemanticToken { | ||
177 | delta_line: t.0, | ||
178 | delta_start: t.1, | ||
179 | length: t.2, | ||
180 | token_type: t.3, | ||
181 | token_modifiers_bitset: t.4, | ||
182 | } | ||
183 | } | ||
184 | |||
185 | #[test] | ||
186 | fn test_diff_insert_at_end() { | ||
187 | let before = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
188 | let after = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10)), from((11, 12, 13, 14, 15))]; | ||
189 | |||
190 | let edits = diff_tokens(&before, &after); | ||
191 | assert_eq!( | ||
192 | edits[0], | ||
193 | SemanticTokensEdit { | ||
194 | start: 10, | ||
195 | delete_count: 0, | ||
196 | data: Some(vec![from((11, 12, 13, 14, 15))]) | ||
197 | } | ||
198 | ); | ||
199 | } | ||
200 | |||
201 | #[test] | ||
202 | fn test_diff_insert_at_beginning() { | ||
203 | let before = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
204 | let after = [from((11, 12, 13, 14, 15)), from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
205 | |||
206 | let edits = diff_tokens(&before, &after); | ||
207 | assert_eq!( | ||
208 | edits[0], | ||
209 | SemanticTokensEdit { | ||
210 | start: 0, | ||
211 | delete_count: 0, | ||
212 | data: Some(vec![from((11, 12, 13, 14, 15))]) | ||
213 | } | ||
214 | ); | ||
215 | } | ||
216 | |||
217 | #[test] | ||
218 | fn test_diff_insert_in_middle() { | ||
219 | let before = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
220 | let after = [ | ||
221 | from((1, 2, 3, 4, 5)), | ||
222 | from((10, 20, 30, 40, 50)), | ||
223 | from((60, 70, 80, 90, 100)), | ||
224 | from((6, 7, 8, 9, 10)), | ||
225 | ]; | ||
226 | |||
227 | let edits = diff_tokens(&before, &after); | ||
228 | assert_eq!( | ||
229 | edits[0], | ||
230 | SemanticTokensEdit { | ||
231 | start: 5, | ||
232 | delete_count: 0, | ||
233 | data: Some(vec![from((10, 20, 30, 40, 50)), from((60, 70, 80, 90, 100))]) | ||
234 | } | ||
235 | ); | ||
236 | } | ||
237 | |||
238 | #[test] | ||
239 | fn test_diff_remove_from_end() { | ||
240 | let before = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10)), from((11, 12, 13, 14, 15))]; | ||
241 | let after = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
242 | |||
243 | let edits = diff_tokens(&before, &after); | ||
244 | assert_eq!(edits[0], SemanticTokensEdit { start: 10, delete_count: 5, data: Some(vec![]) }); | ||
245 | } | ||
246 | |||
247 | #[test] | ||
248 | fn test_diff_remove_from_beginning() { | ||
249 | let before = [from((11, 12, 13, 14, 15)), from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
250 | let after = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
251 | |||
252 | let edits = diff_tokens(&before, &after); | ||
253 | assert_eq!(edits[0], SemanticTokensEdit { start: 0, delete_count: 5, data: Some(vec![]) }); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn test_diff_remove_from_middle() { | ||
258 | let before = [ | ||
259 | from((1, 2, 3, 4, 5)), | ||
260 | from((10, 20, 30, 40, 50)), | ||
261 | from((60, 70, 80, 90, 100)), | ||
262 | from((6, 7, 8, 9, 10)), | ||
263 | ]; | ||
264 | let after = [from((1, 2, 3, 4, 5)), from((6, 7, 8, 9, 10))]; | ||
265 | |||
266 | let edits = diff_tokens(&before, &after); | ||
267 | assert_eq!(edits[0], SemanticTokensEdit { start: 5, delete_count: 10, data: Some(vec![]) }); | ||
268 | } | ||
269 | } | ||
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index fadcc5853..27460db78 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -1,5 +1,8 @@ | |||
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}; | 2 | use std::{ |
3 | path::{self, Path}, | ||
4 | sync::atomic::{AtomicU32, Ordering}, | ||
5 | }; | ||
3 | 6 | ||
4 | use itertools::Itertools; | 7 | use itertools::Itertools; |
5 | use ra_db::{FileId, FileRange}; | 8 | use ra_db::{FileId, FileRange}; |
@@ -303,12 +306,15 @@ pub(crate) fn inlay_int(line_index: &LineIndex, inlay_hint: InlayHint) -> lsp_ex | |||
303 | } | 306 | } |
304 | } | 307 | } |
305 | 308 | ||
309 | static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1); | ||
310 | |||
306 | pub(crate) fn semantic_tokens( | 311 | pub(crate) fn semantic_tokens( |
307 | text: &str, | 312 | text: &str, |
308 | line_index: &LineIndex, | 313 | line_index: &LineIndex, |
309 | highlights: Vec<HighlightedRange>, | 314 | highlights: Vec<HighlightedRange>, |
310 | ) -> lsp_types::SemanticTokens { | 315 | ) -> lsp_types::SemanticTokens { |
311 | let mut builder = semantic_tokens::SemanticTokensBuilder::default(); | 316 | let id = TOKEN_RESULT_COUNTER.fetch_add(1, Ordering::SeqCst).to_string(); |
317 | let mut builder = semantic_tokens::SemanticTokensBuilder::new(id); | ||
312 | 318 | ||
313 | for highlight_range in highlights { | 319 | for highlight_range in highlights { |
314 | let (type_, mods) = semantic_token_type_and_modifiers(highlight_range.highlight); | 320 | let (type_, mods) = semantic_token_type_and_modifiers(highlight_range.highlight); |
@@ -328,6 +334,15 @@ pub(crate) fn semantic_tokens( | |||
328 | builder.build() | 334 | builder.build() |
329 | } | 335 | } |
330 | 336 | ||
337 | pub(crate) fn semantic_token_edits( | ||
338 | previous: &lsp_types::SemanticTokens, | ||
339 | current: &lsp_types::SemanticTokens, | ||
340 | ) -> lsp_types::SemanticTokensEdits { | ||
341 | let result_id = current.result_id.clone(); | ||
342 | let edits = semantic_tokens::diff_tokens(&previous.data, ¤t.data); | ||
343 | lsp_types::SemanticTokensEdits { result_id, edits } | ||
344 | } | ||
345 | |||
331 | fn semantic_token_type_and_modifiers( | 346 | fn semantic_token_type_and_modifiers( |
332 | highlight: Highlight, | 347 | highlight: Highlight, |
333 | ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) { | 348 | ) -> (lsp_types::SemanticTokenType, semantic_tokens::ModifierSet) { |
@@ -740,7 +755,8 @@ pub(crate) fn runnable( | |||
740 | } | 755 | } |
741 | 756 | ||
742 | pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { | 757 | pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { |
743 | lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value: markup.into() } | 758 | let value = crate::markdown::format_docs(markup.as_str()); |
759 | lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } | ||
744 | } | 760 | } |
745 | 761 | ||
746 | #[cfg(test)] | 762 | #[cfg(test)] |
diff --git a/docs/dev/README.md b/docs/dev/README.md index 2896d333e..67813a9c0 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -50,277 +50,85 @@ We use bors-ng to enforce the [not rocket science](https://graydon2.dreamwidth.o | |||
50 | 50 | ||
51 | You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rustfmt on commit. | 51 | You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rustfmt on commit. |
52 | 52 | ||
53 | # Code organization | ||
54 | |||
55 | All Rust code lives in the `crates` top-level directory, and is organized as a | ||
56 | single Cargo workspace. The `editors` top-level directory contains code for | ||
57 | integrating with editors. Currently, it contains the plugin for VS Code (in | ||
58 | TypeScript). The `docs` top-level directory contains both developer and user | ||
59 | documentation. | ||
60 | |||
61 | We have some automation infra in Rust in the `xtask` package. It contains | ||
62 | stuff like formatting checking, code generation and powers `cargo xtask install`. | ||
63 | The latter syntax is achieved with the help of cargo aliases (see `.cargo` | ||
64 | directory). | ||
65 | |||
66 | # Launching rust-analyzer | 53 | # Launching rust-analyzer |
67 | 54 | ||
68 | Debugging the language server can be tricky: LSP is rather chatty, so driving it | 55 | Debugging the language server can be tricky. |
69 | from the command line is not really feasible, driving it via VS Code requires | 56 | LSP is rather chatty, so driving it from the command line is not really feasible, driving it via VS Code requires interacting with two processes. |
70 | interacting with two processes. | ||
71 | 57 | ||
72 | For this reason, the best way to see how rust-analyzer works is to find a | 58 | For this reason, the best way to see how rust-analyzer works is to find a relevant test and execute it. |
73 | relevant test and execute it (VS Code includes an action for running a single | 59 | VS Code & Emacs include an action for running a single test. |
74 | test). | ||
75 | 60 | ||
76 | However, launching a VS Code instance with a locally built language server is | 61 | Launching a VS Code instance with a locally built language server is also possible. |
77 | possible. There's **"Run Extension (Debug Build)"** launch configuration for this. | 62 | There's **"Run Extension (Debug Build)"** launch configuration for this in VS Code. |
78 | 63 | ||
79 | In general, I use one of the following workflows for fixing bugs and | 64 | In general, I use one of the following workflows for fixing bugs and implementing features: |
80 | implementing features. | ||
81 | 65 | ||
82 | If the problem concerns only internal parts of rust-analyzer (i.e. I don't need | 66 | If the problem concerns only internal parts of rust-analyzer (i.e. I don't need to touch the `rust-analyzer` crate or TypeScript code), there is a unit-test for it. |
83 | to touch the `rust-analyzer` crate or TypeScript code), there is a unit-test for it. | 67 | So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and then just do printf-driven development/debugging. |
84 | So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and | 68 | As a sanity check after I'm done, I use `cargo xtask install --server` and **Reload Window** action in VS Code to verify that the thing works as I expect. |
85 | then just do printf-driven development/debugging. As a sanity check after I'm | ||
86 | done, I use `cargo xtask install --server` and **Reload Window** action in VS | ||
87 | Code to sanity check that the thing works as I expect. | ||
88 | 69 | ||
89 | If the problem concerns only the VS Code extension, I use **Run Installed Extension** | 70 | If the problem concerns only the VS Code extension, I use **Run Installed Extension** launch configuration from `launch.json`. |
90 | launch configuration from `launch.json`. Notably, this uses the usual | 71 | Notably, this uses the usual `rust-analyzer` binary from `PATH`. |
91 | `rust-analyzer` binary from `PATH`. For this, it is important to have the following | 72 | For this, it is important to have the following in your `settings.json` file: |
92 | in your `settings.json` file: | ||
93 | ```json | 73 | ```json |
94 | { | 74 | { |
95 | "rust-analyzer.serverPath": "rust-analyzer" | 75 | "rust-analyzer.serverPath": "rust-analyzer" |
96 | } | 76 | } |
97 | ``` | 77 | ``` |
98 | After I am done with the fix, I use `cargo | 78 | After I am done with the fix, I use `cargo xtask install --client-code` to try the new extension for real. |
99 | xtask install --client-code` to try the new extension for real. | ||
100 | |||
101 | If I need to fix something in the `rust-analyzer` crate, I feel sad because it's | ||
102 | on the boundary between the two processes, and working there is slow. I usually | ||
103 | just `cargo xtask install --server` and poke changes from my live environment. | ||
104 | Note that this uses `--release`, which is usually faster overall, because | ||
105 | loading stdlib into debug version of rust-analyzer takes a lot of time. To speed | ||
106 | things up, sometimes I open a temporary hello-world project which has | ||
107 | `"rust-analyzer.withSysroot": false` in `.code/settings.json`. This flag causes | ||
108 | rust-analyzer to skip loading the sysroot, which greatly reduces the amount of | ||
109 | things rust-analyzer needs to do, and makes printf's more useful. Note that you | ||
110 | should only use the `eprint!` family of macros for debugging: stdout is used for LSP | ||
111 | communication, and `print!` would break it. | ||
112 | |||
113 | If I need to fix something simultaneously in the server and in the client, I | ||
114 | feel even more sad. I don't have a specific workflow for this case. | ||
115 | |||
116 | Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats | ||
117 | path/to/some/rust/crate` to run a batch analysis. This is primarily useful for | ||
118 | performance optimizations, or for bug minimization. | ||
119 | |||
120 | # Code Style & Review Process | ||
121 | |||
122 | Our approach to "clean code" is two-fold: | ||
123 | |||
124 | * We generally don't block PRs on style changes. | ||
125 | * At the same time, all code in rust-analyzer is constantly refactored. | ||
126 | |||
127 | It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author. | ||
128 | Sending small cleanup PRs (like renaming a single local variable) is encouraged. | ||
129 | |||
130 | ## Scale of Changes | ||
131 | |||
132 | Everyone knows that it's better to send small & focused pull requests. | ||
133 | The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. | ||
134 | |||
135 | The main things to keep an eye on are the boundaries between various components. | ||
136 | There are three kinds of changes: | ||
137 | |||
138 | 1. Internals of a single component are changed. | ||
139 | Specifically, you don't change any `pub` items. | ||
140 | A good example here would be an addition of a new assist. | ||
141 | |||
142 | 2. API of a component is expanded. | ||
143 | Specifically, you add a new `pub` function which wasn't there before. | ||
144 | A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups. | ||
145 | |||
146 | 3. A new dependency between components is introduced. | ||
147 | Specifically, you add a `pub use` reexport from another crate or you add a new line to the `[dependencies]` section of `Cargo.toml`. | ||
148 | A good example here would be adding reference search capability to the assists crates. | ||
149 | |||
150 | For the first group, the change is generally merged as long as: | ||
151 | |||
152 | * it works for the happy case, | ||
153 | * it has tests, | ||
154 | * it doesn't panic for the unhappy case. | ||
155 | |||
156 | For the second group, the change would be subjected to quite a bit of scrutiny and iteration. | ||
157 | The new API needs to be right (or at least easy to change later). | ||
158 | The actual implementation doesn't matter that much. | ||
159 | It's very important to minimize the amount of changed lines of code for changes of the second kind. | ||
160 | Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind. | ||
161 | In this case, we'll probably ask you to split API changes into a separate PR. | ||
162 | |||
163 | Changes of the third group should be pretty rare, so we don't specify any specific process for them. | ||
164 | That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it! | ||
165 | |||
166 | Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate | ||
167 | https://www.tedinski.com/2018/02/06/system-boundaries.html | ||
168 | |||
169 | ## Crates.io Dependencies | ||
170 | |||
171 | We try to be very conservative with usage of crates.io dependencies. | ||
172 | Don't use small "helper" crates (exception: `itertools` is allowed). | ||
173 | If there's some general reusable bit of code you need, consider adding it to the `stdx` crate. | ||
174 | |||
175 | ## Minimal Tests | ||
176 | |||
177 | Most tests in rust-analyzer start with a snippet of Rust code. | ||
178 | This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed. | ||
179 | There are many benefits to this: | ||
180 | |||
181 | * less to read or to scroll past | ||
182 | * easier to understand what exactly is tested | ||
183 | * less stuff printed during printf-debugging | ||
184 | * less time to run test | ||
185 | |||
186 | It also makes sense to format snippets more compactly (for example, by placing enum defitions like `enum E { Foo, Bar }` on a single line), | ||
187 | as long as they are still readable. | ||
188 | |||
189 | ## Order of Imports | ||
190 | |||
191 | We separate import groups with blank lines | ||
192 | |||
193 | ```rust | ||
194 | mod x; | ||
195 | mod y; | ||
196 | |||
197 | use std::{ ... } | ||
198 | |||
199 | use crate_foo::{ ... } | ||
200 | use crate_bar::{ ... } | ||
201 | |||
202 | use crate::{} | ||
203 | |||
204 | use super::{} // but prefer `use crate::` | ||
205 | ``` | ||
206 | |||
207 | ## Import Style | ||
208 | |||
209 | Items from `hir` and `ast` should be used qualified: | ||
210 | |||
211 | ```rust | ||
212 | // Good | ||
213 | use ra_syntax::ast; | ||
214 | |||
215 | fn frobnicate(func: hir::Function, strukt: ast::StructDef) {} | ||
216 | |||
217 | // Not as good | ||
218 | use hir::Function; | ||
219 | use ra_syntax::ast::StructDef; | ||
220 | |||
221 | fn frobnicate(func: Function, strukt: StructDef) {} | ||
222 | ``` | ||
223 | |||
224 | Avoid local `use MyEnum::*` imports. | ||
225 | |||
226 | Prefer `use crate::foo::bar` to `use super::bar`. | ||
227 | |||
228 | ## Order of Items | ||
229 | |||
230 | Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on. | ||
231 | People read things from top to bottom, so place most important things first. | ||
232 | |||
233 | Specifically, if all items except one are private, always put the non-private item on top. | ||
234 | |||
235 | Put `struct`s and `enum`s first, functions and impls last. | ||
236 | |||
237 | Do | ||
238 | |||
239 | ```rust | ||
240 | // Good | ||
241 | struct Foo { | ||
242 | bars: Vec<Bar> | ||
243 | } | ||
244 | |||
245 | struct Bar; | ||
246 | ``` | ||
247 | |||
248 | rather than | ||
249 | 79 | ||
250 | ```rust | 80 | If I need to fix something in the `rust-analyzer` crate, I feel sad because it's on the boundary between the two processes, and working there is slow. |
251 | // Not as good | 81 | I usually just `cargo xtask install --server` and poke changes from my live environment. |
252 | struct Bar; | 82 | Note that this uses `--release`, which is usually faster overall, because loading stdlib into debug version of rust-analyzer takes a lot of time. |
83 | To speed things up, sometimes I open a temporary hello-world project which has `"rust-analyzer.withSysroot": false` in `.code/settings.json`. | ||
84 | This flag causes rust-analyzer to skip loading the sysroot, which greatly reduces the amount of things rust-analyzer needs to do, and makes printf's more useful. | ||
85 | Note that you should only use the `eprint!` family of macros for debugging: stdout is used for LSP communication, and `print!` would break it. | ||
253 | 86 | ||
254 | struct Foo { | 87 | If I need to fix something simultaneously in the server and in the client, I feel even more sad. |
255 | bars: Vec<Bar> | 88 | I don't have a specific workflow for this case. |
256 | } | ||
257 | ``` | ||
258 | 89 | ||
259 | ## Variable Naming | 90 | Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats path/to/some/rust/crate` to run a batch analysis. |
91 | This is primarily useful for performance optimizations, or for bug minimization. | ||
260 | 92 | ||
261 | We generally use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)). | 93 | ## Parser Tests |
262 | The default name is a lowercased name of the type: `global_state: GlobalState`. | ||
263 | Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`). | ||
264 | The default name for "result of the function" local variable is `res`. | ||
265 | |||
266 | ## Collection types | ||
267 | 94 | ||
268 | We prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | 95 | Tests for the parser (`ra_parser`) live in the `ra_syntax` crate (see `test_data` directory). |
269 | They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount. | 96 | There are two kinds of tests: |
270 | 97 | ||
271 | ## Preconditions | 98 | * Manually written test cases in `parser/ok` and `parser/err` |
99 | * "Inline" tests in `parser/inline` (these are generated) from comments in `ra_parser` crate. | ||
272 | 100 | ||
273 | Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee): | 101 | The purpose of inline tests is not to achieve full coverage by test cases, but to explain to the reader of the code what each particular `if` and `match` is responsible for. |
102 | If you are tempted to add a large inline test, it might be a good idea to leave only the simplest example in place, and move the test to a manual `parser/ok` test. | ||
274 | 103 | ||
275 | ```rust | 104 | To update test data, run with `UPDATE_EXPECT` variable: |
276 | // Good | ||
277 | fn frbonicate(walrus: Walrus) { | ||
278 | ... | ||
279 | } | ||
280 | 105 | ||
281 | // Not as good | 106 | ```bash |
282 | fn frobnicate(walrus: Option<Walrus>) { | 107 | env UPDATE_EXPECT=1 cargo qt |
283 | let walrus = match walrus { | ||
284 | Some(it) => it, | ||
285 | None => return, | ||
286 | }; | ||
287 | ... | ||
288 | } | ||
289 | ``` | 108 | ``` |
290 | 109 | ||
291 | ## Premature Pessimization | 110 | After adding a new inline test you need to run `cargo xtest codegen` and also update the test data as described above. |
292 | |||
293 | While we don't specifically optimize code yet, avoid writing code which is slower than it needs to be. | ||
294 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. | ||
295 | 111 | ||
296 | ```rust | 112 | ## TypeScript Tests |
297 | // Good | ||
298 | use itertools::Itertools; | ||
299 | 113 | ||
300 | let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() { | 114 | If you change files under `editors/code` and would like to run the tests and linter, install npm and run: |
301 | Some(it) => it, | ||
302 | None => return, | ||
303 | } | ||
304 | 115 | ||
305 | // Not as good | 116 | ```bash |
306 | let words = text.split_ascii_whitespace().collect::<Vec<_>>(); | 117 | cd editors/code |
307 | if words.len() != 2 { | 118 | npm ci |
308 | return | 119 | npm run lint |
309 | } | ||
310 | ``` | 120 | ``` |
311 | 121 | ||
312 | ## Documentation | 122 | # Code organization |
313 | |||
314 | For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. | ||
315 | If the line is too long, you want to split the sentence in two :-) | ||
316 | |||
317 | ## Commit Style | ||
318 | 123 | ||
319 | We don't have specific rules around git history hygiene. | 124 | All Rust code lives in the `crates` top-level directory, and is organized as a single Cargo workspace. |
320 | Maintaining clean git history is encouraged, but not enforced. | 125 | The `editors` top-level directory contains code for integrating with editors. |
321 | We use rebase workflow, it's OK to rewrite history during PR review process. | 126 | Currently, it contains the plugin for VS Code (in TypeScript). |
127 | The `docs` top-level directory contains both developer and user documentation. | ||
322 | 128 | ||
323 | Avoid @mentioning people in commit messages and pull request descriptions (they are added to commit message by bors), as such messages create a lot of duplicate notification traffic during rebases. | 129 | We have some automation infra in Rust in the `xtask` package. |
130 | It contains stuff like formatting checking, code generation and powers `cargo xtask install`. | ||
131 | The latter syntax is achieved with the help of cargo aliases (see `.cargo` directory). | ||
324 | 132 | ||
325 | # Architecture Invariants | 133 | # Architecture Invariants |
326 | 134 | ||
@@ -355,35 +163,11 @@ The main IDE crate (`ra_ide`) uses "Plain Old Data" for the API. | |||
355 | Rather than talking in definitions and references, it talks in Strings and textual offsets. | 163 | Rather than talking in definitions and references, it talks in Strings and textual offsets. |
356 | In general, API is centered around UI concerns -- the result of the call is what the user sees in the editor, and not what the compiler sees underneath. | 164 | In general, API is centered around UI concerns -- the result of the call is what the user sees in the editor, and not what the compiler sees underneath. |
357 | The results are 100% Rust specific though. | 165 | The results are 100% Rust specific though. |
166 | Shout outs to LSP developers for popularizing the idea that "UI" is a good place to draw a boundary at. | ||
358 | 167 | ||
359 | ## Parser Tests | 168 | # Code Style & Review Process |
360 | |||
361 | Tests for the parser (`ra_parser`) live in the `ra_syntax` crate (see `test_data` directory). | ||
362 | There are two kinds of tests: | ||
363 | |||
364 | * Manually written test cases in `parser/ok` and `parser/err` | ||
365 | * "Inline" tests in `parser/inline` (these are generated) from comments in `ra_parser` crate. | ||
366 | |||
367 | The purpose of inline tests is not to achieve full coverage by test cases, but to explain to the reader of the code what each particular `if` and `match` is responsible for. | ||
368 | If you are tempted to add a large inline test, it might be a good idea to leave only the simplest example in place, and move the test to a manual `parser/ok` test. | ||
369 | |||
370 | To update test data, run with `UPDATE_EXPECT` variable: | ||
371 | |||
372 | ```bash | ||
373 | env UPDATE_EXPECT=1 cargo qt | ||
374 | ``` | ||
375 | |||
376 | After adding a new inline test you need to run `cargo xtest codegen` and also update the test data as described above. | ||
377 | |||
378 | ## TypeScript Tests | ||
379 | |||
380 | If you change files under `editors/code` and would like to run the tests and linter, install npm and run: | ||
381 | 169 | ||
382 | ```bash | 170 | Do see [./style.md](./style.md). |
383 | cd editors/code | ||
384 | npm ci | ||
385 | npm run lint | ||
386 | ``` | ||
387 | 171 | ||
388 | # Logging | 172 | # Logging |
389 | 173 | ||
@@ -451,3 +235,34 @@ For measuring time of incremental analysis, use either of these: | |||
451 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs | 235 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --highlight ../chalk/chalk-engine/src/logic.rs |
452 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0 | 236 | $ cargo run --release -p rust-analyzer -- analysis-bench ../chalk/ --complete ../chalk/chalk-engine/src/logic.rs:94:0 |
453 | ``` | 237 | ``` |
238 | |||
239 | # Release Process | ||
240 | |||
241 | Release process is handled by `release`, `dist` and `promote` xtasks, `release` being the main one. | ||
242 | |||
243 | `release` assumes that you have checkouts of `rust-analyzer`, `rust-analyzer.github.io`, and `rust-lang/rust` in the same directory: | ||
244 | |||
245 | ``` | ||
246 | ./rust-analyzer | ||
247 | ./rust-analyzer.github.io | ||
248 | ./rust-rust-analyzer # Note the name! | ||
249 | ``` | ||
250 | |||
251 | Additionally, it assumes that remote for `rust-analyzer` is called `upstream` (I use `origin` to point to my fork). | ||
252 | |||
253 | Release steps: | ||
254 | |||
255 | 1. Inside rust-analyzer, run `cargo xtask release`. This will: | ||
256 | * checkout the `release` branch | ||
257 | * reset it to `upstream/nightly` | ||
258 | * push it to `upstream`. This triggers GitHub Actions which: | ||
259 | ** runs `cargo xtask dist` to package binaries and VS Code extension | ||
260 | ** makes a GitHub release | ||
261 | ** pushes VS Code extension to the marketplace | ||
262 | * create new changelog in `rust-analyzer.github.io` | ||
263 | * create `rust-analyzer.github.io/git.log` file with the log of merge commits since last release | ||
264 | 2. While the release is in progress, fill-in the changelog using `git.log` | ||
265 | 3. Commit & push the changelog | ||
266 | 4. Tweet | ||
267 | 5. Inside `rust-analyzer`, run `cargo xtask promote` -- this will create a PR to rust-lang/rust updating rust-analyzer's submodule. | ||
268 | Self-approve the PR. | ||
diff --git a/docs/dev/style.md b/docs/dev/style.md new file mode 100644 index 000000000..1c68f5702 --- /dev/null +++ b/docs/dev/style.md | |||
@@ -0,0 +1,212 @@ | |||
1 | Our approach to "clean code" is two-fold: | ||
2 | |||
3 | * We generally don't block PRs on style changes. | ||
4 | * At the same time, all code in rust-analyzer is constantly refactored. | ||
5 | |||
6 | It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author. | ||
7 | Sending small cleanup PRs (like renaming a single local variable) is encouraged. | ||
8 | |||
9 | # Scale of Changes | ||
10 | |||
11 | Everyone knows that it's better to send small & focused pull requests. | ||
12 | The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. | ||
13 | |||
14 | The main things to keep an eye on are the boundaries between various components. | ||
15 | There are three kinds of changes: | ||
16 | |||
17 | 1. Internals of a single component are changed. | ||
18 | Specifically, you don't change any `pub` items. | ||
19 | A good example here would be an addition of a new assist. | ||
20 | |||
21 | 2. API of a component is expanded. | ||
22 | Specifically, you add a new `pub` function which wasn't there before. | ||
23 | A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups. | ||
24 | |||
25 | 3. A new dependency between components is introduced. | ||
26 | Specifically, you add a `pub use` reexport from another crate or you add a new line to the `[dependencies]` section of `Cargo.toml`. | ||
27 | A good example here would be adding reference search capability to the assists crates. | ||
28 | |||
29 | For the first group, the change is generally merged as long as: | ||
30 | |||
31 | * it works for the happy case, | ||
32 | * it has tests, | ||
33 | * it doesn't panic for the unhappy case. | ||
34 | |||
35 | For the second group, the change would be subjected to quite a bit of scrutiny and iteration. | ||
36 | The new API needs to be right (or at least easy to change later). | ||
37 | The actual implementation doesn't matter that much. | ||
38 | It's very important to minimize the amount of changed lines of code for changes of the second kind. | ||
39 | Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind. | ||
40 | In this case, we'll probably ask you to split API changes into a separate PR. | ||
41 | |||
42 | Changes of the third group should be pretty rare, so we don't specify any specific process for them. | ||
43 | That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it! | ||
44 | |||
45 | Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate | ||
46 | https://www.tedinski.com/2018/02/06/system-boundaries.html | ||
47 | |||
48 | # Crates.io Dependencies | ||
49 | |||
50 | We try to be very conservative with usage of crates.io dependencies. | ||
51 | Don't use small "helper" crates (exception: `itertools` is allowed). | ||
52 | If there's some general reusable bit of code you need, consider adding it to the `stdx` crate. | ||
53 | |||
54 | # Minimal Tests | ||
55 | |||
56 | Most tests in rust-analyzer start with a snippet of Rust code. | ||
57 | This snippets should be minimal -- if you copy-paste a snippet of real code into the tests, make sure to remove everything which could be removed. | ||
58 | There are many benefits to this: | ||
59 | |||
60 | * less to read or to scroll past | ||
61 | * easier to understand what exactly is tested | ||
62 | * less stuff printed during printf-debugging | ||
63 | * less time to run test | ||
64 | |||
65 | It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line), | ||
66 | as long as they are still readable. | ||
67 | |||
68 | ## Order of Imports | ||
69 | |||
70 | Separate import groups with blank lines. | ||
71 | Use one `use` per crate. | ||
72 | |||
73 | ```rust | ||
74 | mod x; | ||
75 | mod y; | ||
76 | |||
77 | // First std. | ||
78 | use std::{ ... } | ||
79 | |||
80 | // Second, external crates (both crates.io crates and other rust-analyzer crates). | ||
81 | use crate_foo::{ ... } | ||
82 | use crate_bar::{ ... } | ||
83 | |||
84 | // Then current crate. | ||
85 | use crate::{} | ||
86 | |||
87 | // Finally, parent and child modules, but prefer `use crate::`. | ||
88 | use super::{} | ||
89 | ``` | ||
90 | |||
91 | Module declarations come before the imports. | ||
92 | Order them in "suggested reading order" for a person new to the code base. | ||
93 | |||
94 | ## Import Style | ||
95 | |||
96 | Qualify items from `hir` and `ast`. | ||
97 | |||
98 | ```rust | ||
99 | // Good | ||
100 | use ra_syntax::ast; | ||
101 | |||
102 | fn frobnicate(func: hir::Function, strukt: ast::StructDef) {} | ||
103 | |||
104 | // Not as good | ||
105 | use hir::Function; | ||
106 | use ra_syntax::ast::StructDef; | ||
107 | |||
108 | fn frobnicate(func: Function, strukt: StructDef) {} | ||
109 | ``` | ||
110 | |||
111 | Avoid local `use MyEnum::*` imports. | ||
112 | |||
113 | Prefer `use crate::foo::bar` to `use super::bar`. | ||
114 | |||
115 | ## Order of Items | ||
116 | |||
117 | Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on. | ||
118 | People read things from top to bottom, so place most important things first. | ||
119 | |||
120 | Specifically, if all items except one are private, always put the non-private item on top. | ||
121 | |||
122 | Put `struct`s and `enum`s first, functions and impls last. | ||
123 | |||
124 | Do | ||
125 | |||
126 | ```rust | ||
127 | // Good | ||
128 | struct Foo { | ||
129 | bars: Vec<Bar> | ||
130 | } | ||
131 | |||
132 | struct Bar; | ||
133 | ``` | ||
134 | |||
135 | rather than | ||
136 | |||
137 | ```rust | ||
138 | // Not as good | ||
139 | struct Bar; | ||
140 | |||
141 | struct Foo { | ||
142 | bars: Vec<Bar> | ||
143 | } | ||
144 | ``` | ||
145 | |||
146 | ## Variable Naming | ||
147 | |||
148 | Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)). | ||
149 | The default name is a lowercased name of the type: `global_state: GlobalState`. | ||
150 | Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`). | ||
151 | The default name for "result of the function" local variable is `res`. | ||
152 | The default name for "I don't really care about the name" variable is `it`. | ||
153 | |||
154 | ## Collection types | ||
155 | |||
156 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | ||
157 | They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount. | ||
158 | |||
159 | ## Preconditions | ||
160 | |||
161 | Express function preconditions in types and force the caller to provide them (rather than checking in callee): | ||
162 | |||
163 | ```rust | ||
164 | // Good | ||
165 | fn frbonicate(walrus: Walrus) { | ||
166 | ... | ||
167 | } | ||
168 | |||
169 | // Not as good | ||
170 | fn frobnicate(walrus: Option<Walrus>) { | ||
171 | let walrus = match walrus { | ||
172 | Some(it) => it, | ||
173 | None => return, | ||
174 | }; | ||
175 | ... | ||
176 | } | ||
177 | ``` | ||
178 | |||
179 | ## Premature Pessimization | ||
180 | |||
181 | Avoid writing code which is slower than it needs to be. | ||
182 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. | ||
183 | |||
184 | ```rust | ||
185 | // Good | ||
186 | use itertools::Itertools; | ||
187 | |||
188 | let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() { | ||
189 | Some(it) => it, | ||
190 | None => return, | ||
191 | } | ||
192 | |||
193 | // Not as good | ||
194 | let words = text.split_ascii_whitespace().collect::<Vec<_>>(); | ||
195 | if words.len() != 2 { | ||
196 | return | ||
197 | } | ||
198 | ``` | ||
199 | |||
200 | ## Documentation | ||
201 | |||
202 | For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. | ||
203 | If the line is too long, you want to split the sentence in two :-) | ||
204 | |||
205 | ## Commit Style | ||
206 | |||
207 | We don't have specific rules around git history hygiene. | ||
208 | Maintaining clean git history is encouraged, but not enforced. | ||
209 | Use rebase workflow, it's OK to rewrite history during PR review process. | ||
210 | |||
211 | Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors). | ||
212 | Such messages create a lot of duplicate notification traffic during rebases. | ||
diff --git a/editors/code/package.json b/editors/code/package.json index 1adf055d0..ee5f96bf3 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -607,7 +607,7 @@ | |||
607 | "items": { | 607 | "items": { |
608 | "type": "string" | 608 | "type": "string" |
609 | }, | 609 | }, |
610 | "description": "List of warnings warnings that should be displayed with hint severity.\nThe warnings will be indicated by faded text or three dots in code and will not show up in the problems panel.", | 610 | "description": "List of warnings that should be displayed with hint severity.\nThe warnings will be indicated by faded text or three dots in code and will not show up in the problems panel.", |
611 | "default": [] | 611 | "default": [] |
612 | } | 612 | } |
613 | } | 613 | } |