aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock36
-rw-r--r--crates/ra_assists/src/assist_context.rs4
-rw-r--r--crates/ra_assists/src/ast_transform.rs2
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs6
-rw-r--r--crates/ra_assists/src/handlers/expand_glob_import.rs391
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs5
-rw-r--r--crates/ra_assists/src/handlers/generate_function.rs2
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs2
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs2
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs4
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs42
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs4
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/tests/generated.rs27
-rw-r--r--crates/ra_assists/src/utils.rs4
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs3
-rw-r--r--crates/ra_db/Cargo.toml2
-rw-r--r--crates/ra_hir/src/db.rs9
-rw-r--r--crates/ra_hir_expand/src/proc_macro.rs2
-rw-r--r--crates/ra_hir_ty/Cargo.toml6
-rw-r--r--crates/ra_hir_ty/src/diagnostics/unsafe_check.rs38
-rw-r--r--crates/ra_hir_ty/src/infer.rs11
-rw-r--r--crates/ra_hir_ty/src/lower.rs5
-rw-r--r--crates/ra_hir_ty/src/tests/simple.rs38
-rw-r--r--crates/ra_ide/src/diagnostics.rs10
-rw-r--r--crates/ra_ide/src/folding_ranges.rs23
-rw-r--r--crates/ra_ide/src/hover.rs31
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs67
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tests.rs21
-rw-r--r--crates/ra_ide/test_data/highlight_unsafe.html18
-rw-r--r--crates/ra_ide/test_data/highlighting.html9
-rw-r--r--crates/ra_ide_db/src/change.rs24
-rw-r--r--crates/ra_ide_db/src/defs.rs25
-rw-r--r--crates/ra_ssr/src/lib.rs7
-rw-r--r--crates/ra_ssr/src/resolving.rs23
-rw-r--r--crates/ra_ssr/src/search.rs9
-rw-r--r--crates/ra_ssr/src/tests.rs35
-rw-r--r--crates/ra_syntax/src/ast/edit.rs2
-rw-r--r--crates/ra_syntax/src/ast/make.rs16
-rw-r--r--crates/rust-analyzer/src/caps.rs4
-rw-r--r--crates/rust-analyzer/src/document.rs6
-rw-r--r--crates/rust-analyzer/src/global_state.rs8
-rw-r--r--crates/rust-analyzer/src/handlers.rs46
-rw-r--r--crates/rust-analyzer/src/lsp_utils.rs40
-rw-r--r--crates/rust-analyzer/src/main_loop.rs21
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs139
-rw-r--r--crates/rust-analyzer/src/to_proto.rs22
-rw-r--r--docs/dev/README.md351
-rw-r--r--docs/dev/style.md212
-rw-r--r--editors/code/package.json2
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]]
127name = "chalk-derive" 127name = "chalk-derive"
128version = "0.19.0" 128version = "0.21.0"
129source = "registry+https://github.com/rust-lang/crates.io-index" 129source = "registry+https://github.com/rust-lang/crates.io-index"
130checksum = "654c611946ba2629c5028cb7708687af975faf2c29d731824cb294c873df4697" 130checksum = "c1df0dbb57d74b4acd20f20fa66ab2acd09776b79eaeb9d8f947b2f3e01c40bf"
131dependencies = [ 131dependencies = [
132 "proc-macro2", 132 "proc-macro2",
133 "quote", 133 "quote",
@@ -137,9 +137,9 @@ dependencies = [
137 137
138[[package]] 138[[package]]
139name = "chalk-ir" 139name = "chalk-ir"
140version = "0.19.0" 140version = "0.21.0"
141source = "registry+https://github.com/rust-lang/crates.io-index" 141source = "registry+https://github.com/rust-lang/crates.io-index"
142checksum = "0a5341fbc654ca886b73b804a36aebf0e621057ccc1a68e9815b5b39b3ac9ae8" 142checksum = "44361a25dbdb1dc428f56ad7a3c21ba9ca12f3225c26a47919ff6fcb10a583d4"
143dependencies = [ 143dependencies = [
144 "chalk-derive", 144 "chalk-derive",
145 "lazy_static", 145 "lazy_static",
@@ -147,9 +147,9 @@ dependencies = [
147 147
148[[package]] 148[[package]]
149name = "chalk-recursive" 149name = "chalk-recursive"
150version = "0.19.0" 150version = "0.21.0"
151source = "registry+https://github.com/rust-lang/crates.io-index" 151source = "registry+https://github.com/rust-lang/crates.io-index"
152checksum = "4484807b155b5a411e6135d330295f9ba5042e2920b8712c6574ca6ea91e9ee5" 152checksum = "dd89556b98de156d5eaf21077d297cd2198628f10f2df140798ea3a5dd84bc86"
153dependencies = [ 153dependencies = [
154 "chalk-derive", 154 "chalk-derive",
155 "chalk-ir", 155 "chalk-ir",
@@ -160,9 +160,9 @@ dependencies = [
160 160
161[[package]] 161[[package]]
162name = "chalk-solve" 162name = "chalk-solve"
163version = "0.19.0" 163version = "0.21.0"
164source = "registry+https://github.com/rust-lang/crates.io-index" 164source = "registry+https://github.com/rust-lang/crates.io-index"
165checksum = "281f82facd2538997fbe52132b1941ed213d266748215c31d15f62a8664429ad" 165checksum = "a886da37a0dc457057d86f78f026f7a09c6d8088aa13f4f4127fdb8dc80119a3"
166dependencies = [ 166dependencies = [
167 "chalk-derive", 167 "chalk-derive",
168 "chalk-ir", 168 "chalk-ir",
@@ -318,9 +318,9 @@ dependencies = [
318 318
319[[package]] 319[[package]]
320name = "filetime" 320name = "filetime"
321version = "0.2.11" 321version = "0.2.12"
322source = "registry+https://github.com/rust-lang/crates.io-index" 322source = "registry+https://github.com/rust-lang/crates.io-index"
323checksum = "e500da2fab70bdc43f8f0e0b350a227f31c72311c56aba48f01d5cd62bb0345b" 323checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e"
324dependencies = [ 324dependencies = [
325 "cfg-if", 325 "cfg-if",
326 "libc", 326 "libc",
@@ -1371,9 +1371,9 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
1371 1371
1372[[package]] 1372[[package]]
1373name = "salsa" 1373name = "salsa"
1374version = "0.15.1" 1374version = "0.15.2"
1375source = "registry+https://github.com/rust-lang/crates.io-index" 1375source = "registry+https://github.com/rust-lang/crates.io-index"
1376checksum = "d4cdc109fcc9e9450c7ef47fb7474e99bffd51799da03ed0a6c7f0e2cb3848a6" 1376checksum = "9ab29056d4fb4048a5f0d169c9b6e5526160c9ec37aded5a6879c2c9c445a8e4"
1377dependencies = [ 1377dependencies = [
1378 "crossbeam-utils", 1378 "crossbeam-utils",
1379 "indexmap", 1379 "indexmap",
@@ -1388,9 +1388,9 @@ dependencies = [
1388 1388
1389[[package]] 1389[[package]]
1390name = "salsa-macros" 1390name = "salsa-macros"
1391version = "0.15.0" 1391version = "0.15.2"
1392source = "registry+https://github.com/rust-lang/crates.io-index" 1392source = "registry+https://github.com/rust-lang/crates.io-index"
1393checksum = "2c280ac85b15ac214b86ac4b407626a48e6a1c4f90769a582fec74aa57942b9f" 1393checksum = "a1c3aec007c63c4ed4cd7a018529fb0b5575c4562575fc6a40d6cd2ae0b792ef"
1394dependencies = [ 1394dependencies = [
1395 "heck", 1395 "heck",
1396 "proc-macro2", 1396 "proc-macro2",
@@ -1533,9 +1533,9 @@ version = "0.1.0"
1533 1533
1534[[package]] 1534[[package]]
1535name = "syn" 1535name = "syn"
1536version = "1.0.36" 1536version = "1.0.38"
1537source = "registry+https://github.com/rust-lang/crates.io-index" 1537source = "registry+https://github.com/rust-lang/crates.io-index"
1538checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" 1538checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4"
1539dependencies = [ 1539dependencies = [
1540 "proc-macro2", 1540 "proc-macro2",
1541 "quote", 1541 "quote",
@@ -1644,9 +1644,9 @@ dependencies = [
1644 1644
1645[[package]] 1645[[package]]
1646name = "tracing-core" 1646name = "tracing-core"
1647version = "0.1.12" 1647version = "0.1.13"
1648source = "registry+https://github.com/rust-lang/crates.io-index" 1648source = "registry+https://github.com/rust-lang/crates.io-index"
1649checksum = "b2734b5a028fa697686f16c6d18c2c6a3c7e41513f9a213abb6754c4acb3c8d7" 1649checksum = "d593f98af59ebc017c0648f0117525db358745a8894a8d684e185ba3f45954f9"
1650dependencies = [ 1650dependencies = [
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 @@
1use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
2use ra_ide_db::{
3 defs::{classify_name_ref, Definition, NameRefClass},
4 RootDatabase,
5};
6use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T};
7
8use crate::{
9 assist_context::{AssistBuilder, AssistContext, Assists},
10 AssistId, AssistKind,
11};
12
13use 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// ```
40pub(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
63fn 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)]
68enum Def {
69 ModuleDef(ModuleDef),
70 MacroDef(MacroDef),
71}
72
73impl 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
82fn 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
110fn 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
144fn 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)]
184mod 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"
194mod foo {
195 pub struct Bar;
196 pub struct Baz;
197 pub struct Qux;
198
199 pub fn f() {}
200}
201
202use foo::*<|>;
203
204fn qux(bar: Bar, baz: Baz) {
205 f();
206}
207",
208 r"
209mod foo {
210 pub struct Bar;
211 pub struct Baz;
212 pub struct Qux;
213
214 pub fn f() {}
215}
216
217use foo::{Baz, Bar, f};
218
219fn 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"
231mod foo {
232 pub struct Bar;
233 pub struct Baz;
234 pub struct Qux;
235
236 pub fn f() {}
237}
238
239use foo::{*<|>, f};
240
241fn qux(bar: Bar, baz: Baz) {
242 f();
243}
244",
245 r"
246mod foo {
247 pub struct Bar;
248 pub struct Baz;
249 pub struct Qux;
250
251 pub fn f() {}
252}
253
254use foo::{Baz, Bar, f};
255
256fn 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"
268mod 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
282use foo::{bar::{*<|>, f}, baz::*};
283
284fn qux(bar: Bar, baz: Baz) {
285 f();
286 g();
287}
288",
289 r"
290mod 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
304use foo::{bar::{Baz, Bar, f}, baz::*};
305
306fn 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]
321macro_rules! bar {
322 () => ()
323}
324
325pub fn baz() {}
326
327//- /main.rs crate:main deps:foo
328use foo::*<|>;
329
330fn main() {
331 bar!();
332 baz();
333}
334",
335 r"
336use foo::{bar, baz};
337
338fn 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
352pub trait Tr {
353 fn method(&self) {}
354}
355impl Tr for () {}
356
357//- /main.rs crate:main deps:foo
358use foo::*<|>;
359
360fn main() {
361 ().method();
362}
363",
364 r"
365use foo::Tr;
366
367fn 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)]
176mod test { 176mod 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"
652pub use std::fmt;
653
654impl std::io<|> for Foo {
655}
656 ",
657 r"
658use std::io;
659
660pub use std::fmt;
661
662impl 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"
673pub(crate) use std::fmt;
674
675impl std::io<|> for Foo {
676}
677 ",
678 r"
679use std::io;
680
681pub(crate) use std::fmt;
682
683impl 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]
232fn doctest_expand_glob_import() {
233 check_doc_test(
234 "expand_glob_import",
235 r#####"
236mod foo {
237 pub struct Bar;
238 pub struct Baz;
239}
240
241use foo::*<|>;
242
243fn qux(bar: Bar, baz: Baz) {}
244"#####,
245 r#####"
246mod foo {
247 pub struct Bar;
248 pub struct Baz;
249}
250
251use foo::{Baz, Bar};
252
253fn qux(bar: Bar, baz: Baz) {}
254"#####,
255 )
256}
257
258#[test]
232fn doctest_extract_struct_from_enum_variant() { 259fn 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
5use hir::{self, ModPath}; 5use hir::{self, ModPath};
6use ra_syntax::{ 6use 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"
9doctest = false 9doctest = false
10 10
11[dependencies] 11[dependencies]
12salsa = "0.15.0" 12salsa = "0.15.2"
13rustc-hash = "1.1.0" 13rustc-hash = "1.1.0"
14 14
15ra_syntax = { path = "../ra_syntax" } 15ra_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};
16pub use hir_ty::db::{ 16pub 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]
26fn hir_database_is_object_safe() { 19fn 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)]
104mod test { 104mod 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
29scoped-tls = "1" 29scoped-tls = "1"
30 30
31chalk-solve = { version = "0.19.0" } 31chalk-solve = { version = "0.21.0" }
32chalk-ir = { version = "0.19.0" } 32chalk-ir = { version = "0.21.0" }
33chalk-recursive = { version = "0.19.0" } 33chalk-recursive = { version = "0.21.0" }
34 34
35[dev-dependencies] 35[dev-dependencies]
36expect = { path = "../expect" } 36expect = { 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;
6use hir_def::{ 6use 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};
11use hir_expand::diagnostics::DiagnosticSink; 12use 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#"
189struct Ty {
190 a: u8,
191}
192
193static mut static_mut: Ty = Ty { a: 0 };
194
195fn 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
1148pub enum ValueTyDefId { 1149pub 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}
1155impl_from!(FunctionId, StructId, EnumVariantId, ConstId, StaticId for ValueTyDefId); 1157impl_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#"
344struct Foo <fold block>{
345}</fold>
346"#,
347 );
348 }
349
350 #[test]
351 fn test_folds_traits() {
352 check(
353 r#"
354trait 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/// ```
521pub fn foo<|>(_: &Path) {}
522
523fn 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)]
5mod tests; 5mod tests;
6 6
7use hir::{Name, Semantics}; 7use hir::{Name, Semantics, VariantDef};
8use ra_ide_db::{ 8use 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
458fn 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
458fn highlight_element( 470fn 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
655fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { 679fn 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
727fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight { 761fn 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};
9fn test_highlighting() { 9fn test_highlighting() {
10 check_highlighting( 10 check_highlighting(
11 r#" 11 r#"
12use inner::{self as inner_mod};
13mod inner {}
14
12#[derive(Clone, Debug)] 15#[derive(Clone, Debug)]
13struct Foo { 16struct Foo {
14 pub x: i32, 17 pub x: i32,
@@ -272,19 +275,37 @@ fn test_unsafe_highlighting() {
272 r#" 275 r#"
273unsafe fn unsafe_fn() {} 276unsafe fn unsafe_fn() {}
274 277
278union Union {
279 a: u32,
280 b: f32,
281}
282
275struct HasUnsafeFn; 283struct HasUnsafeFn;
276 284
277impl HasUnsafeFn { 285impl HasUnsafeFn {
278 unsafe fn unsafe_method(&self) {} 286 unsafe fn unsafe_method(&self) {}
279} 287}
280 288
289struct TypeForStaticMut {
290 a: u8
291}
292
293static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 };
294
281fn main() { 295fn 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">=&gt;</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">=&gt;</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">&lt;</span><span class="lifetime declaration">'a</span><span class="punctuation">,</span> <span class="type_param declaration">T</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</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">&lt;</span><span class="lifetime declaration">'a</span><span class="punctuation">,</span> <span class="type_param declaration">T</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="type_param">T</span> <span class="punctuation">{</span>
67 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</span><span class="punctuation">(</span><span class="punctuation">)</span> 70 <span class="function">foo</span><span class="operator">::</span><span class="punctuation">&lt;</span><span class="lifetime">'a</span><span class="punctuation">,</span> <span class="builtin_type">i32</span><span class="punctuation">&gt;</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::{
12use ra_prof::profile; 12use ra_prof::profile;
13use ra_syntax::{ 13use ra_syntax::{
14 ast::{self, AstNode}, 14 ast::{self, AstNode},
15 match_ast, 15 match_ast, SyntaxNode,
16}; 16};
17 17
18use crate::RootDatabase; 18use 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;
11pub(crate) struct ResolutionScope<'db> { 11pub(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
16pub(crate) struct ResolvedRule { 17pub(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
30pub(crate) struct ResolvedPath { 32pub(crate) struct ResolvedPath {
@@ -68,6 +70,7 @@ struct Resolver<'a, 'db> {
68 70
69impl Resolver<'_, '_> { 71impl 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
208fn 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(&current_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]
1049fn 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]
622fn test_increase_indent() { 622fn 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
20pub fn type_ref(text: &str) -> ast::Type { 20pub 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 {
30pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { 30pub 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}
33fn path_from_text(text: &str) -> ast::Path { 33pub 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
63pub fn use_item(use_tree: ast::UseTree) -> ast::Use { 63pub 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
67pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { 67pub 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
78pub fn record_field_def(name: ast::NameRef, ty: ast::Type) -> ast::RecordField { 78pub 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
151pub fn bind_pat(name: ast::Name) -> ast::IdentPat { 151pub 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
159pub fn placeholder_pat() -> ast::WildcardPat { 159pub 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
291pub fn fn_def( 291pub 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)]
8pub(crate) struct DocumentData { 8pub(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
8use crossbeam_channel::{unbounded, Receiver, Sender}; 8use crossbeam_channel::{unbounded, Receiver, Sender};
9use flycheck::FlycheckHandle; 9use flycheck::FlycheckHandle;
10use lsp_types::Url; 10use lsp_types::{SemanticTokens, Url};
11use parking_lot::RwLock; 11use parking_lot::{Mutex, RwLock};
12use ra_db::{CrateId, VfsPath}; 12use ra_db::{CrateId, VfsPath};
13use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId}; 13use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FileId};
14use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target}; 14use 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};
20use ra_ide::{ 21use 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
1190pub(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, &params.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.
2use std::{borrow::Cow, error::Error, ops::Range}; 2use std::{error::Error, ops::Range};
3 3
4use lsp_server::Notification; 4use lsp_server::Notification;
5use ra_db::Canceled; 5use ra_db::Canceled;
@@ -84,8 +84,8 @@ impl GlobalState {
84pub(crate) fn apply_document_changes( 84pub(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.
3use std::{ 3use std::{
4 borrow::Cow,
5 env, fmt, panic, 4 env, fmt, panic,
6 time::{Duration, Instant}, 5 time::{Duration, Instant},
7}; 6};
8 7
9use crossbeam_channel::{select, Receiver}; 8use crossbeam_channel::{select, Receiver};
10use lsp_server::{Connection, Notification, Request, Response}; 9use lsp_server::{Connection, Notification, Request, Response};
11use lsp_types::{notification::Notification as _, DidChangeTextDocumentParams}; 10use lsp_types::notification::Notification as _;
12use ra_db::VfsPath; 11use ra_db::VfsPath;
13use ra_ide::{Canceled, FileId}; 12use ra_ide::{Canceled, FileId};
14use ra_prof::profile; 13use 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
54enum Event { 53enum 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(&params.text_document.uri) { 426 if let Ok(path) = from_proto::vfs_path(&params.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(&params.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
3use std::ops; 3use std::ops;
4 4
5use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens}; 5use lsp_types::{
6 Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens,
7 SemanticTokensEdit,
8};
6 9
7macro_rules! define_semantic_token_types { 10macro_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)]
93pub(crate) struct SemanticTokensBuilder { 95pub(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
99impl SemanticTokensBuilder { 102impl 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
141pub 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
134pub fn type_index(type_: SemanticTokenType) -> u32 { 167pub 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)]
172mod 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.
2use std::path::{self, Path}; 2use std::{
3 path::{self, Path},
4 sync::atomic::{AtomicU32, Ordering},
5};
3 6
4use itertools::Itertools; 7use itertools::Itertools;
5use ra_db::{FileId, FileRange}; 8use 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
309static TOKEN_RESULT_COUNTER: AtomicU32 = AtomicU32::new(1);
310
306pub(crate) fn semantic_tokens( 311pub(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
337pub(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, &current.data);
343 lsp_types::SemanticTokensEdits { result_id, edits }
344}
345
331fn semantic_token_type_and_modifiers( 346fn 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
742pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { 757pub(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
51You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rustfmt on commit. 51You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rustfmt on commit.
52 52
53# Code organization
54
55All Rust code lives in the `crates` top-level directory, and is organized as a
56single Cargo workspace. The `editors` top-level directory contains code for
57integrating with editors. Currently, it contains the plugin for VS Code (in
58TypeScript). The `docs` top-level directory contains both developer and user
59documentation.
60
61We have some automation infra in Rust in the `xtask` package. It contains
62stuff like formatting checking, code generation and powers `cargo xtask install`.
63The latter syntax is achieved with the help of cargo aliases (see `.cargo`
64directory).
65
66# Launching rust-analyzer 53# Launching rust-analyzer
67 54
68Debugging the language server can be tricky: LSP is rather chatty, so driving it 55Debugging the language server can be tricky.
69from the command line is not really feasible, driving it via VS Code requires 56LSP is rather chatty, so driving it from the command line is not really feasible, driving it via VS Code requires interacting with two processes.
70interacting with two processes.
71 57
72For this reason, the best way to see how rust-analyzer works is to find a 58For this reason, the best way to see how rust-analyzer works is to find a relevant test and execute it.
73relevant test and execute it (VS Code includes an action for running a single 59VS Code & Emacs include an action for running a single test.
74test).
75 60
76However, launching a VS Code instance with a locally built language server is 61Launching a VS Code instance with a locally built language server is also possible.
77possible. There's **"Run Extension (Debug Build)"** launch configuration for this. 62There's **"Run Extension (Debug Build)"** launch configuration for this in VS Code.
78 63
79In general, I use one of the following workflows for fixing bugs and 64In general, I use one of the following workflows for fixing bugs and implementing features:
80implementing features.
81 65
82If the problem concerns only internal parts of rust-analyzer (i.e. I don't need 66If 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.
83to touch the `rust-analyzer` crate or TypeScript code), there is a unit-test for it. 67So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and then just do printf-driven development/debugging.
84So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and 68As 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.
85then just do printf-driven development/debugging. As a sanity check after I'm
86done, I use `cargo xtask install --server` and **Reload Window** action in VS
87Code to sanity check that the thing works as I expect.
88 69
89If the problem concerns only the VS Code extension, I use **Run Installed Extension** 70If the problem concerns only the VS Code extension, I use **Run Installed Extension** launch configuration from `launch.json`.
90launch configuration from `launch.json`. Notably, this uses the usual 71Notably, this uses the usual `rust-analyzer` binary from `PATH`.
91`rust-analyzer` binary from `PATH`. For this, it is important to have the following 72For this, it is important to have the following in your `settings.json` file:
92in 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```
98After I am done with the fix, I use `cargo 78After I am done with the fix, I use `cargo xtask install --client-code` to try the new extension for real.
99xtask install --client-code` to try the new extension for real.
100
101If I need to fix something in the `rust-analyzer` crate, I feel sad because it's
102on the boundary between the two processes, and working there is slow. I usually
103just `cargo xtask install --server` and poke changes from my live environment.
104Note that this uses `--release`, which is usually faster overall, because
105loading stdlib into debug version of rust-analyzer takes a lot of time. To speed
106things up, sometimes I open a temporary hello-world project which has
107`"rust-analyzer.withSysroot": false` in `.code/settings.json`. This flag causes
108rust-analyzer to skip loading the sysroot, which greatly reduces the amount of
109things rust-analyzer needs to do, and makes printf's more useful. Note that you
110should only use the `eprint!` family of macros for debugging: stdout is used for LSP
111communication, and `print!` would break it.
112
113If I need to fix something simultaneously in the server and in the client, I
114feel even more sad. I don't have a specific workflow for this case.
115
116Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats
117path/to/some/rust/crate` to run a batch analysis. This is primarily useful for
118performance optimizations, or for bug minimization.
119
120# Code Style & Review Process
121
122Our 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
127It is explicitly OK for a reviewer to flag only some nits in the PR, and then send a follow-up cleanup PR for things which are easier to explain by example, cc-ing the original author.
128Sending small cleanup PRs (like renaming a single local variable) is encouraged.
129
130## Scale of Changes
131
132Everyone knows that it's better to send small & focused pull requests.
133The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
134
135The main things to keep an eye on are the boundaries between various components.
136There are three kinds of changes:
137
1381. 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
1422. 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
1463. A new dependency between components is introduced.
147 Specifically, you add a `pub use` reexport from another crate or you add a new line to the `[dependencies]` section of `Cargo.toml`.
148 A good example here would be adding reference search capability to the assists crates.
149
150For 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
156For the second group, the change would be subjected to quite a bit of scrutiny and iteration.
157The new API needs to be right (or at least easy to change later).
158The actual implementation doesn't matter that much.
159It's very important to minimize the amount of changed lines of code for changes of the second kind.
160Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind.
161In this case, we'll probably ask you to split API changes into a separate PR.
162
163Changes of the third group should be pretty rare, so we don't specify any specific process for them.
164That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it!
165
166Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
167https://www.tedinski.com/2018/02/06/system-boundaries.html
168
169## Crates.io Dependencies
170
171We try to be very conservative with usage of crates.io dependencies.
172Don't use small "helper" crates (exception: `itertools` is allowed).
173If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
174
175## Minimal Tests
176
177Most tests in rust-analyzer start with a snippet of Rust code.
178This 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.
179There 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
186It also makes sense to format snippets more compactly (for example, by placing enum defitions like `enum E { Foo, Bar }` on a single line),
187as long as they are still readable.
188
189## Order of Imports
190
191We separate import groups with blank lines
192
193```rust
194mod x;
195mod y;
196
197use std::{ ... }
198
199use crate_foo::{ ... }
200use crate_bar::{ ... }
201
202use crate::{}
203
204use super::{} // but prefer `use crate::`
205```
206
207## Import Style
208
209Items from `hir` and `ast` should be used qualified:
210
211```rust
212// Good
213use ra_syntax::ast;
214
215fn frobnicate(func: hir::Function, strukt: ast::StructDef) {}
216
217// Not as good
218use hir::Function;
219use ra_syntax::ast::StructDef;
220
221fn frobnicate(func: Function, strukt: StructDef) {}
222```
223
224Avoid local `use MyEnum::*` imports.
225
226Prefer `use crate::foo::bar` to `use super::bar`.
227
228## Order of Items
229
230Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on.
231People read things from top to bottom, so place most important things first.
232
233Specifically, if all items except one are private, always put the non-private item on top.
234
235Put `struct`s and `enum`s first, functions and impls last.
236
237Do
238
239```rust
240// Good
241struct Foo {
242 bars: Vec<Bar>
243}
244
245struct Bar;
246```
247
248rather than
249 79
250```rust 80If 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 81I usually just `cargo xtask install --server` and poke changes from my live environment.
252struct Bar; 82Note that this uses `--release`, which is usually faster overall, because loading stdlib into debug version of rust-analyzer takes a lot of time.
83To speed things up, sometimes I open a temporary hello-world project which has `"rust-analyzer.withSysroot": false` in `.code/settings.json`.
84This 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.
85Note 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
254struct Foo { 87If I need to fix something simultaneously in the server and in the client, I feel even more sad.
255 bars: Vec<Bar> 88I don't have a specific workflow for this case.
256}
257```
258 89
259## Variable Naming 90Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats path/to/some/rust/crate` to run a batch analysis.
91This is primarily useful for performance optimizations, or for bug minimization.
260 92
261We 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
262The default name is a lowercased name of the type: `global_state: GlobalState`.
263Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
264The default name for "result of the function" local variable is `res`.
265
266## Collection types
267 94
268We prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. 95Tests for the parser (`ra_parser`) live in the `ra_syntax` crate (see `test_data` directory).
269They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount. 96There 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
273Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee): 101The 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.
102If 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 104To update test data, run with `UPDATE_EXPECT` variable:
276// Good
277fn frbonicate(walrus: Walrus) {
278 ...
279}
280 105
281// Not as good 106```bash
282fn frobnicate(walrus: Option<Walrus>) { 107env 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 110After adding a new inline test you need to run `cargo xtest codegen` and also update the test data as described above.
292
293While we don't specifically optimize code yet, avoid writing code which is slower than it needs to be.
294Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly.
295 111
296```rust 112## TypeScript Tests
297// Good
298use itertools::Itertools;
299 113
300let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() { 114If 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
306let words = text.split_ascii_whitespace().collect::<Vec<_>>(); 117cd editors/code
307if words.len() != 2 { 118npm ci
308 return 119npm run lint
309}
310``` 120```
311 121
312## Documentation 122# Code organization
313
314For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
315If the line is too long, you want to split the sentence in two :-)
316
317## Commit Style
318 123
319We don't have specific rules around git history hygiene. 124All Rust code lives in the `crates` top-level directory, and is organized as a single Cargo workspace.
320Maintaining clean git history is encouraged, but not enforced. 125The `editors` top-level directory contains code for integrating with editors.
321We use rebase workflow, it's OK to rewrite history during PR review process. 126Currently, it contains the plugin for VS Code (in TypeScript).
127The `docs` top-level directory contains both developer and user documentation.
322 128
323Avoid @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. 129We have some automation infra in Rust in the `xtask` package.
130It contains stuff like formatting checking, code generation and powers `cargo xtask install`.
131The 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.
355Rather than talking in definitions and references, it talks in Strings and textual offsets. 163Rather than talking in definitions and references, it talks in Strings and textual offsets.
356In 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. 164In 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.
357The results are 100% Rust specific though. 165The results are 100% Rust specific though.
166Shout 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
361Tests for the parser (`ra_parser`) live in the `ra_syntax` crate (see `test_data` directory).
362There 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
367The 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.
368If 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
370To update test data, run with `UPDATE_EXPECT` variable:
371
372```bash
373env UPDATE_EXPECT=1 cargo qt
374```
375
376After 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
380If you change files under `editors/code` and would like to run the tests and linter, install npm and run:
381 169
382```bash 170Do see [./style.md](./style.md).
383cd editors/code
384npm ci
385npm 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
241Release 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
251Additionally, it assumes that remote for `rust-analyzer` is called `upstream` (I use `origin` to point to my fork).
252
253Release steps:
254
2551. 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
2642. While the release is in progress, fill-in the changelog using `git.log`
2653. Commit & push the changelog
2664. Tweet
2675. 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 @@
1Our 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
6It 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.
7Sending small cleanup PRs (like renaming a single local variable) is encouraged.
8
9# Scale of Changes
10
11Everyone knows that it's better to send small & focused pull requests.
12The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs.
13
14The main things to keep an eye on are the boundaries between various components.
15There are three kinds of changes:
16
171. 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
212. 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
253. 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
29For 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
35For the second group, the change would be subjected to quite a bit of scrutiny and iteration.
36The new API needs to be right (or at least easy to change later).
37The actual implementation doesn't matter that much.
38It's very important to minimize the amount of changed lines of code for changes of the second kind.
39Often, you start doing a change of the first kind, only to realise that you need to elevate to a change of the second kind.
40In this case, we'll probably ask you to split API changes into a separate PR.
41
42Changes of the third group should be pretty rare, so we don't specify any specific process for them.
43That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it!
44
45Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate
46https://www.tedinski.com/2018/02/06/system-boundaries.html
47
48# Crates.io Dependencies
49
50We try to be very conservative with usage of crates.io dependencies.
51Don't use small "helper" crates (exception: `itertools` is allowed).
52If there's some general reusable bit of code you need, consider adding it to the `stdx` crate.
53
54# Minimal Tests
55
56Most tests in rust-analyzer start with a snippet of Rust code.
57This 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.
58There 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
65It also makes sense to format snippets more compactly (for example, by placing enum definitions like `enum E { Foo, Bar }` on a single line),
66as long as they are still readable.
67
68## Order of Imports
69
70Separate import groups with blank lines.
71Use one `use` per crate.
72
73```rust
74mod x;
75mod y;
76
77// First std.
78use std::{ ... }
79
80// Second, external crates (both crates.io crates and other rust-analyzer crates).
81use crate_foo::{ ... }
82use crate_bar::{ ... }
83
84// Then current crate.
85use crate::{}
86
87// Finally, parent and child modules, but prefer `use crate::`.
88use super::{}
89```
90
91Module declarations come before the imports.
92Order them in "suggested reading order" for a person new to the code base.
93
94## Import Style
95
96Qualify items from `hir` and `ast`.
97
98```rust
99// Good
100use ra_syntax::ast;
101
102fn frobnicate(func: hir::Function, strukt: ast::StructDef) {}
103
104// Not as good
105use hir::Function;
106use ra_syntax::ast::StructDef;
107
108fn frobnicate(func: Function, strukt: StructDef) {}
109```
110
111Avoid local `use MyEnum::*` imports.
112
113Prefer `use crate::foo::bar` to `use super::bar`.
114
115## Order of Items
116
117Optimize for the reader who sees the file for the first time, and wants to get a general idea about what's going on.
118People read things from top to bottom, so place most important things first.
119
120Specifically, if all items except one are private, always put the non-private item on top.
121
122Put `struct`s and `enum`s first, functions and impls last.
123
124Do
125
126```rust
127// Good
128struct Foo {
129 bars: Vec<Bar>
130}
131
132struct Bar;
133```
134
135rather than
136
137```rust
138// Not as good
139struct Bar;
140
141struct Foo {
142 bars: Vec<Bar>
143}
144```
145
146## Variable Naming
147
148Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
149The default name is a lowercased name of the type: `global_state: GlobalState`.
150Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
151The default name for "result of the function" local variable is `res`.
152The default name for "I don't really care about the name" variable is `it`.
153
154## Collection types
155
156Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`.
157They use a hasher that's slightly faster and using them consistently will reduce code size by some small amount.
158
159## Preconditions
160
161Express function preconditions in types and force the caller to provide them (rather than checking in callee):
162
163```rust
164// Good
165fn frbonicate(walrus: Walrus) {
166 ...
167}
168
169// Not as good
170fn 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
181Avoid writing code which is slower than it needs to be.
182Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly.
183
184```rust
185// Good
186use itertools::Itertools;
187
188let (first_word, second_word) = match text.split_ascii_whitespace().collect_tuple() {
189 Some(it) => it,
190 None => return,
191}
192
193// Not as good
194let words = text.split_ascii_whitespace().collect::<Vec<_>>();
195if words.len() != 2 {
196 return
197}
198```
199
200## Documentation
201
202For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines.
203If the line is too long, you want to split the sentence in two :-)
204
205## Commit Style
206
207We don't have specific rules around git history hygiene.
208Maintaining clean git history is encouraged, but not enforced.
209Use rebase workflow, it's OK to rewrite history during PR review process.
210
211Avoid @mentioning people in commit messages and pull request descriptions(they are added to commit message by bors).
212Such 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 }