aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/base_db/src/input.rs1
-rw-r--r--crates/flycheck/Cargo.toml1
-rw-r--r--crates/flycheck/src/lib.rs68
-rw-r--r--crates/hir_def/src/body/tests.rs8
-rw-r--r--crates/hir_def/src/body/tests/block.rs24
-rw-r--r--crates/hir_def/src/item_tree.rs7
-rw-r--r--crates/hir_def/src/nameres.rs14
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/ide/src/prime_caches.rs3
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs182
-rw-r--r--crates/ide/src/syntax_highlighting/tags.rs51
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html12
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html12
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_injection.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html6
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html32
-rw-r--r--crates/ide_assists/src/ast_transform.rs33
-rw-r--r--crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs516
-rw-r--r--crates/ide_assists/src/handlers/extract_function.rs36
-rw-r--r--crates/ide_assists/src/handlers/fill_match_arms.rs222
-rw-r--r--crates/ide_assists/src/handlers/inline_local_variable.rs223
-rw-r--r--crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs73
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs41
-rw-r--r--crates/ide_assists/src/utils.rs3
-rw-r--r--crates/ide_db/src/search.rs58
-rw-r--r--crates/parser/src/grammar/patterns.rs6
-rw-r--r--crates/proc_macro_api/Cargo.toml7
-rw-r--r--crates/proc_macro_api/src/lib.rs1
-rw-r--r--crates/project_model/src/build_data.rs134
-rw-r--r--crates/rust-analyzer/src/config.rs9
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs1
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs56
-rw-r--r--crates/rust-analyzer/src/reload.rs1
-rw-r--r--crates/stdx/Cargo.toml5
-rw-r--r--crates/stdx/src/lib.rs27
-rw-r--r--crates/stdx/src/process.rs238
-rw-r--r--crates/syntax/Cargo.toml2
-rw-r--r--crates/syntax/src/algo.rs19
-rw-r--r--crates/syntax/src/ast/make.rs34
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast58
-rw-r--r--crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs1
42 files changed, 1817 insertions, 418 deletions
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index 07628935f..0ef77ef5d 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -239,6 +239,7 @@ impl CrateGraph {
239 name: CrateName, 239 name: CrateName,
240 to: CrateId, 240 to: CrateId,
241 ) -> Result<(), CyclicDependenciesError> { 241 ) -> Result<(), CyclicDependenciesError> {
242 let _p = profile::span("add_dep");
242 if self.dfs_find(from, to, &mut FxHashSet::default()) { 243 if self.dfs_find(from, to, &mut FxHashSet::default()) {
243 return Err(CyclicDependenciesError { 244 return Err(CyclicDependenciesError {
244 from: (from, self[from].display_name.clone()), 245 from: (from, self[from].display_name.clone()),
diff --git a/crates/flycheck/Cargo.toml b/crates/flycheck/Cargo.toml
index 2a1a21b28..18b9ce7df 100644
--- a/crates/flycheck/Cargo.toml
+++ b/crates/flycheck/Cargo.toml
@@ -13,6 +13,7 @@ doctest = false
13crossbeam-channel = "0.5.0" 13crossbeam-channel = "0.5.0"
14log = "0.4.8" 14log = "0.4.8"
15cargo_metadata = "0.13" 15cargo_metadata = "0.13"
16serde = { version = "1.0.106", features = ["derive"] }
16serde_json = "1.0.48" 17serde_json = "1.0.48"
17jod-thread = "0.1.1" 18jod-thread = "0.1.1"
18 19
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs
index e2a59497a..1682d8bde 100644
--- a/crates/flycheck/src/lib.rs
+++ b/crates/flycheck/src/lib.rs
@@ -4,13 +4,14 @@
4 4
5use std::{ 5use std::{
6 fmt, 6 fmt,
7 io::{self, BufReader}, 7 io::{self, BufRead, BufReader},
8 path::PathBuf, 8 path::PathBuf,
9 process::{self, Command, Stdio}, 9 process::{self, Command, Stdio},
10 time::Duration, 10 time::Duration,
11}; 11};
12 12
13use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; 13use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
14use serde::Deserialize;
14use stdx::JodChild; 15use stdx::JodChild;
15 16
16pub use cargo_metadata::diagnostic::{ 17pub use cargo_metadata::diagnostic::{
@@ -128,7 +129,7 @@ struct FlycheckActor {
128 129
129enum Event { 130enum Event {
130 Restart(Restart), 131 Restart(Restart),
131 CheckEvent(Option<cargo_metadata::Message>), 132 CheckEvent(Option<CargoMessage>),
132} 133}
133 134
134impl FlycheckActor { 135impl FlycheckActor {
@@ -180,21 +181,16 @@ impl FlycheckActor {
180 self.progress(Progress::DidFinish(res)); 181 self.progress(Progress::DidFinish(res));
181 } 182 }
182 Event::CheckEvent(Some(message)) => match message { 183 Event::CheckEvent(Some(message)) => match message {
183 cargo_metadata::Message::CompilerArtifact(msg) => { 184 CargoMessage::CompilerArtifact(msg) => {
184 self.progress(Progress::DidCheckCrate(msg.target.name)); 185 self.progress(Progress::DidCheckCrate(msg.target.name));
185 } 186 }
186 187
187 cargo_metadata::Message::CompilerMessage(msg) => { 188 CargoMessage::Diagnostic(msg) => {
188 self.send(Message::AddDiagnostic { 189 self.send(Message::AddDiagnostic {
189 workspace_root: self.workspace_root.clone(), 190 workspace_root: self.workspace_root.clone(),
190 diagnostic: msg.message, 191 diagnostic: msg,
191 }); 192 });
192 } 193 }
193
194 cargo_metadata::Message::BuildScriptExecuted(_)
195 | cargo_metadata::Message::BuildFinished(_)
196 | cargo_metadata::Message::TextLine(_)
197 | _ => {}
198 }, 194 },
199 } 195 }
200 } 196 }
@@ -261,7 +257,7 @@ struct CargoHandle {
261 child: JodChild, 257 child: JodChild,
262 #[allow(unused)] 258 #[allow(unused)]
263 thread: jod_thread::JoinHandle<io::Result<bool>>, 259 thread: jod_thread::JoinHandle<io::Result<bool>>,
264 receiver: Receiver<cargo_metadata::Message>, 260 receiver: Receiver<CargoMessage>,
265} 261}
266 262
267impl CargoHandle { 263impl CargoHandle {
@@ -294,14 +290,11 @@ impl CargoHandle {
294 290
295struct CargoActor { 291struct CargoActor {
296 child_stdout: process::ChildStdout, 292 child_stdout: process::ChildStdout,
297 sender: Sender<cargo_metadata::Message>, 293 sender: Sender<CargoMessage>,
298} 294}
299 295
300impl CargoActor { 296impl CargoActor {
301 fn new( 297 fn new(child_stdout: process::ChildStdout, sender: Sender<CargoMessage>) -> CargoActor {
302 child_stdout: process::ChildStdout,
303 sender: Sender<cargo_metadata::Message>,
304 ) -> CargoActor {
305 CargoActor { child_stdout, sender } 298 CargoActor { child_stdout, sender }
306 } 299 }
307 fn run(self) -> io::Result<bool> { 300 fn run(self) -> io::Result<bool> {
@@ -315,7 +308,7 @@ impl CargoActor {
315 // erroneus output. 308 // erroneus output.
316 let stdout = BufReader::new(self.child_stdout); 309 let stdout = BufReader::new(self.child_stdout);
317 let mut read_at_least_one_message = false; 310 let mut read_at_least_one_message = false;
318 for message in cargo_metadata::Message::parse_stream(stdout) { 311 for message in stdout.lines() {
319 let message = match message { 312 let message = match message {
320 Ok(message) => message, 313 Ok(message) => message,
321 Err(err) => { 314 Err(err) => {
@@ -326,13 +319,44 @@ impl CargoActor {
326 319
327 read_at_least_one_message = true; 320 read_at_least_one_message = true;
328 321
329 // Skip certain kinds of messages to only spend time on what's useful 322 // Try to deserialize a message from Cargo or Rustc.
330 match &message { 323 let mut deserializer = serde_json::Deserializer::from_str(&message);
331 cargo_metadata::Message::CompilerArtifact(artifact) if artifact.fresh => (), 324 deserializer.disable_recursion_limit();
332 cargo_metadata::Message::BuildScriptExecuted(_) => (), 325 if let Ok(message) = JsonMessage::deserialize(&mut deserializer) {
333 _ => self.sender.send(message).unwrap(), 326 match message {
327 // Skip certain kinds of messages to only spend time on what's useful
328 JsonMessage::Cargo(message) => match message {
329 cargo_metadata::Message::CompilerArtifact(artifact) if !artifact.fresh => {
330 self.sender.send(CargoMessage::CompilerArtifact(artifact)).unwrap()
331 }
332 cargo_metadata::Message::CompilerMessage(msg) => {
333 self.sender.send(CargoMessage::Diagnostic(msg.message)).unwrap()
334 }
335
336 cargo_metadata::Message::CompilerArtifact(_)
337 | cargo_metadata::Message::BuildScriptExecuted(_)
338 | cargo_metadata::Message::BuildFinished(_)
339 | cargo_metadata::Message::TextLine(_)
340 | _ => (),
341 },
342 JsonMessage::Rustc(message) => {
343 self.sender.send(CargoMessage::Diagnostic(message)).unwrap()
344 }
345 }
334 } 346 }
335 } 347 }
336 Ok(read_at_least_one_message) 348 Ok(read_at_least_one_message)
337 } 349 }
338} 350}
351
352enum CargoMessage {
353 CompilerArtifact(cargo_metadata::Artifact),
354 Diagnostic(Diagnostic),
355}
356
357#[derive(Deserialize)]
358#[serde(untagged)]
359enum JsonMessage {
360 Cargo(cargo_metadata::Message),
361 Rustc(Diagnostic),
362}
diff --git a/crates/hir_def/src/body/tests.rs b/crates/hir_def/src/body/tests.rs
index 63f5fe88d..3e8f16306 100644
--- a/crates/hir_def/src/body/tests.rs
+++ b/crates/hir_def/src/body/tests.rs
@@ -40,6 +40,14 @@ fn block_def_map_at(ra_fixture: &str) -> String {
40 module.def_map(&db).dump(&db) 40 module.def_map(&db).dump(&db)
41} 41}
42 42
43fn check_block_scopes_at(ra_fixture: &str, expect: Expect) {
44 let (db, position) = crate::test_db::TestDB::with_position(ra_fixture);
45
46 let module = db.module_at_position(position);
47 let actual = module.def_map(&db).dump_block_scopes(&db);
48 expect.assert_eq(&actual);
49}
50
43fn check_at(ra_fixture: &str, expect: Expect) { 51fn check_at(ra_fixture: &str, expect: Expect) {
44 let actual = block_def_map_at(ra_fixture); 52 let actual = block_def_map_at(ra_fixture);
45 expect.assert_eq(&actual); 53 expect.assert_eq(&actual);
diff --git a/crates/hir_def/src/body/tests/block.rs b/crates/hir_def/src/body/tests/block.rs
index 3b6ba4cde..bc3d0f138 100644
--- a/crates/hir_def/src/body/tests/block.rs
+++ b/crates/hir_def/src/body/tests/block.rs
@@ -134,6 +134,30 @@ struct Struct {}
134} 134}
135 135
136#[test] 136#[test]
137fn nested_module_scoping() {
138 check_block_scopes_at(
139 r#"
140fn f() {
141 mod module {
142 struct Struct {}
143 fn f() {
144 use self::Struct;
145 $0
146 }
147 }
148}
149 "#,
150 expect![[r#"
151 BlockId(1) in ModuleId { krate: CrateId(0), block: Some(BlockId(0)), local_id: Idx::<ModuleData>(0) }
152 BlockId(0) in ModuleId { krate: CrateId(0), block: None, local_id: Idx::<ModuleData>(0) }
153 crate scope
154 "#]],
155 );
156 // FIXME: The module nesting here is wrong!
157 // The first block map should be located in module #1 (`mod module`), not #0 (BlockId(0) root module)
158}
159
160#[test]
137fn legacy_macro_items() { 161fn legacy_macro_items() {
138 // Checks that legacy-scoped `macro_rules!` from parent namespaces are resolved and expanded 162 // Checks that legacy-scoped `macro_rules!` from parent namespaces are resolved and expanded
139 // correctly. 163 // correctly.
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 16a94a058..eaeca01bd 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -196,13 +196,6 @@ impl ItemTree {
196 self.raw_attrs(of).clone().filter(db, krate) 196 self.raw_attrs(of).clone().filter(db, krate)
197 } 197 }
198 198
199 pub fn all_inner_items(&self) -> impl Iterator<Item = ModItem> + '_ {
200 match &self.data {
201 Some(data) => Some(data.inner_items.values().flatten().copied()).into_iter().flatten(),
202 None => None.into_iter().flatten(),
203 }
204 }
205
206 pub fn inner_items_of_block(&self, block: FileAstId<ast::BlockExpr>) -> &[ModItem] { 199 pub fn inner_items_of_block(&self, block: FileAstId<ast::BlockExpr>) -> &[ModItem] {
207 match &self.data { 200 match &self.data {
208 Some(data) => data.inner_items.get(&block).map(|it| &**it).unwrap_or(&[]), 201 Some(data) => data.inner_items.get(&block).map(|it| &**it).unwrap_or(&[]),
diff --git a/crates/hir_def/src/nameres.rs b/crates/hir_def/src/nameres.rs
index 542f190a1..ba027c44a 100644
--- a/crates/hir_def/src/nameres.rs
+++ b/crates/hir_def/src/nameres.rs
@@ -410,6 +410,20 @@ impl DefMap {
410 } 410 }
411 } 411 }
412 412
413 pub fn dump_block_scopes(&self, db: &dyn DefDatabase) -> String {
414 let mut buf = String::new();
415 let mut arc;
416 let mut current_map = self;
417 while let Some(block) = &current_map.block {
418 format_to!(buf, "{:?} in {:?}\n", block.block, block.parent);
419 arc = block.parent.def_map(db);
420 current_map = &*arc;
421 }
422
423 format_to!(buf, "crate scope\n");
424 buf
425 }
426
413 fn shrink_to_fit(&mut self) { 427 fn shrink_to_fit(&mut self) {
414 // Exhaustive match to require handling new fields. 428 // Exhaustive match to require handling new fields.
415 let Self { 429 let Self {
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index abc0e7532..66b3418f2 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -18,9 +18,9 @@ ena = "0.14.0"
18log = "0.4.8" 18log = "0.4.8"
19rustc-hash = "1.1.0" 19rustc-hash = "1.1.0"
20scoped-tls = "1" 20scoped-tls = "1"
21chalk-solve = { version = "0.60", default-features = false } 21chalk-solve = { version = "0.64", default-features = false }
22chalk-ir = "0.60" 22chalk-ir = "0.64"
23chalk-recursive = "0.60" 23chalk-recursive = "0.64"
24la-arena = { version = "0.2.0", path = "../../lib/arena" } 24la-arena = { version = "0.2.0", path = "../../lib/arena" }
25 25
26stdx = { path = "../stdx", version = "0.0.0" } 26stdx = { path = "../stdx", version = "0.0.0" }
diff --git a/crates/ide/src/prime_caches.rs b/crates/ide/src/prime_caches.rs
index ea0acfaa0..03597f507 100644
--- a/crates/ide/src/prime_caches.rs
+++ b/crates/ide/src/prime_caches.rs
@@ -27,6 +27,7 @@ pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress)
27 let topo = &graph.crates_in_topological_order(); 27 let topo = &graph.crates_in_topological_order();
28 28
29 cb(PrimeCachesProgress::Started); 29 cb(PrimeCachesProgress::Started);
30 let _d = stdx::defer(|| cb(PrimeCachesProgress::Finished));
30 31
31 // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that. 32 // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that.
32 // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks 33 // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks
@@ -41,6 +42,4 @@ pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress)
41 }); 42 });
42 db.crate_def_map(*krate); 43 db.crate_def_map(*krate);
43 } 44 }
44
45 cb(PrimeCachesProgress::Finished);
46} 45}
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
index 18552459b..8731699dc 100644
--- a/crates/ide/src/syntax_highlighting/highlight.rs
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -1,6 +1,6 @@
1//! Computes color for a single element. 1//! Computes color for a single element.
2 2
3use hir::{AsAssocItem, AssocItemContainer, Semantics, VariantDef}; 3use hir::{AsAssocItem, Semantics};
4use ide_db::{ 4use ide_db::{
5 defs::{Definition, NameClass, NameRefClass}, 5 defs::{Definition, NameClass, NameRefClass},
6 RootDatabase, SymbolKind, 6 RootDatabase, SymbolKind,
@@ -45,28 +45,26 @@ pub(super) fn element(
45 }; 45 };
46 46
47 match name_kind { 47 match name_kind {
48 Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(), 48 Some(NameClass::ExternCrate(_)) => SymbolKind::Module.into(),
49 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, 49 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
50 Some(NameClass::ConstReference(def)) => highlight_def(db, def), 50 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
51 Some(NameClass::PatFieldShorthand { field_ref, .. }) => { 51 Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
52 let mut h = HlTag::Symbol(SymbolKind::Field).into(); 52 let mut h = HlTag::Symbol(SymbolKind::Field).into();
53 if let Definition::Field(field) = field_ref { 53 if let Definition::Field(field) = field_ref {
54 if let VariantDef::Union(_) = field.parent_def(db) { 54 if let hir::VariantDef::Union(_) = field.parent_def(db) {
55 h |= HlMod::Unsafe; 55 h |= HlMod::Unsafe;
56 } 56 }
57 } 57 }
58
59 h 58 h
60 } 59 }
61 None => highlight_name_by_syntax(name) | HlMod::Definition, 60 None => highlight_name_by_syntax(name) | HlMod::Definition,
62 } 61 }
63 } 62 }
64
65 // Highlight references like the definitions they resolve to 63 // Highlight references like the definitions they resolve to
66 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => { 64 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
67 // even though we track whether we are in an attribute or not we still need this special case 65 // even though we track whether we are in an attribute or not we still need this special case
68 // as otherwise we would emit unresolved references for name refs inside attributes 66 // as otherwise we would emit unresolved references for name refs inside attributes
69 Highlight::from(HlTag::Symbol(SymbolKind::Function)) 67 SymbolKind::Function.into()
70 } 68 }
71 NAME_REF => { 69 NAME_REF => {
72 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 70 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
@@ -74,7 +72,7 @@ pub(super) fn element(
74 let is_self = name_ref.self_token().is_some(); 72 let is_self = name_ref.self_token().is_some();
75 let h = match NameRefClass::classify(sema, &name_ref) { 73 let h = match NameRefClass::classify(sema, &name_ref) {
76 Some(name_kind) => match name_kind { 74 Some(name_kind) => match name_kind {
77 NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(), 75 NameRefClass::ExternCrate(_) => SymbolKind::Module.into(),
78 NameRefClass::Definition(def) => { 76 NameRefClass::Definition(def) => {
79 if let Definition::Local(local) = &def { 77 if let Definition::Local(local) = &def {
80 if let Some(name) = local.name(db) { 78 if let Some(name) = local.name(db) {
@@ -95,7 +93,7 @@ pub(super) fn element(
95 if let Some(parent) = name_ref.syntax().parent() { 93 if let Some(parent) = name_ref.syntax().parent() {
96 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { 94 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
97 if let Definition::Field(field) = def { 95 if let Definition::Field(field) = def {
98 if let VariantDef::Union(_) = field.parent_def(db) { 96 if let hir::VariantDef::Union(_) = field.parent_def(db) {
99 h |= HlMod::Unsafe; 97 h |= HlMod::Unsafe;
100 } 98 }
101 } 99 }
@@ -104,9 +102,7 @@ pub(super) fn element(
104 102
105 h 103 h
106 } 104 }
107 NameRefClass::FieldShorthand { .. } => { 105 NameRefClass::FieldShorthand { .. } => SymbolKind::Field.into(),
108 HlTag::Symbol(SymbolKind::Field).into()
109 }
110 }, 106 },
111 None if syntactic_name_ref_highlighting => { 107 None if syntactic_name_ref_highlighting => {
112 highlight_name_ref_by_syntax(name_ref, sema) 108 highlight_name_ref_by_syntax(name_ref, sema)
@@ -114,7 +110,7 @@ pub(super) fn element(
114 None => HlTag::UnresolvedReference.into(), 110 None => HlTag::UnresolvedReference.into(),
115 }; 111 };
116 if h.tag == HlTag::Symbol(SymbolKind::Module) && is_self { 112 if h.tag == HlTag::Symbol(SymbolKind::Module) && is_self {
117 HlTag::Symbol(SymbolKind::SelfParam).into() 113 SymbolKind::SelfParam.into()
118 } else { 114 } else {
119 h 115 h
120 } 116 }
@@ -135,7 +131,7 @@ pub(super) fn element(
135 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), 131 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
136 BYTE => HlTag::ByteLiteral.into(), 132 BYTE => HlTag::ByteLiteral.into(),
137 CHAR => HlTag::CharLiteral.into(), 133 CHAR => HlTag::CharLiteral.into(),
138 QUESTION => Highlight::new(HlTag::Operator(HlOperator::Other)) | HlMod::ControlFlow, 134 QUESTION => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow,
139 LIFETIME => { 135 LIFETIME => {
140 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap(); 136 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap();
141 137
@@ -143,44 +139,31 @@ pub(super) fn element(
143 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition, 139 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
144 None => match NameRefClass::classify_lifetime(sema, &lifetime) { 140 None => match NameRefClass::classify_lifetime(sema, &lifetime) {
145 Some(NameRefClass::Definition(def)) => highlight_def(db, def), 141 Some(NameRefClass::Definition(def)) => highlight_def(db, def),
146 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), 142 _ => SymbolKind::LifetimeParam.into(),
147 }, 143 },
148 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition, 144 _ => Highlight::from(SymbolKind::LifetimeParam) | HlMod::Definition,
149 } 145 }
150 } 146 }
151 p if p.is_punct() => match p { 147 p if p.is_punct() => match p {
152 T![&] if element.parent().and_then(ast::BinExpr::cast).is_some() => { 148 T![&] if parent_matches::<ast::BinExpr>(&element) => HlOperator::Bitwise.into(),
153 HlTag::Operator(HlOperator::Bitwise).into()
154 }
155 T![&] => { 149 T![&] => {
156 let h = HlTag::Operator(HlOperator::Other).into(); 150 let h = HlTag::Operator(HlOperator::Other).into();
157 let is_unsafe = element 151 let is_unsafe = element
158 .parent() 152 .parent()
159 .and_then(ast::RefExpr::cast) 153 .and_then(ast::RefExpr::cast)
160 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)) 154 .map_or(false, |ref_expr| sema.is_unsafe_ref_expr(&ref_expr));
161 .unwrap_or(false);
162 if is_unsafe { 155 if is_unsafe {
163 h | HlMod::Unsafe 156 h | HlMod::Unsafe
164 } else { 157 } else {
165 h 158 h
166 } 159 }
167 } 160 }
168 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => { 161 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlOperator::Other.into(),
169 HlTag::Operator(HlOperator::Other).into() 162 T![!] if parent_matches::<ast::MacroCall>(&element) => SymbolKind::Macro.into(),
170 } 163 T![!] if parent_matches::<ast::NeverType>(&element) => HlTag::BuiltinType.into(),
171 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => { 164 T![!] if parent_matches::<ast::PrefixExpr>(&element) => HlOperator::Logical.into(),
172 HlTag::Symbol(SymbolKind::Macro).into() 165 T![*] if parent_matches::<ast::PtrType>(&element) => HlTag::Keyword.into(),
173 } 166 T![*] if parent_matches::<ast::PrefixExpr>(&element) => {
174 T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => {
175 HlTag::BuiltinType.into()
176 }
177 T![!] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
178 HlTag::Operator(HlOperator::Logical).into()
179 }
180 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
181 HlTag::Keyword.into()
182 }
183 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
184 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; 167 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
185 168
186 let expr = prefix_expr.expr()?; 169 let expr = prefix_expr.expr()?;
@@ -188,12 +171,12 @@ pub(super) fn element(
188 if ty.is_raw_ptr() { 171 if ty.is_raw_ptr() {
189 HlTag::Operator(HlOperator::Other) | HlMod::Unsafe 172 HlTag::Operator(HlOperator::Other) | HlMod::Unsafe
190 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() { 173 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
191 HlTag::Operator(HlOperator::Other).into() 174 HlOperator::Other.into()
192 } else { 175 } else {
193 HlTag::Punctuation(HlPunct::Other).into() 176 HlPunct::Other.into()
194 } 177 }
195 } 178 }
196 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { 179 T![-] if parent_matches::<ast::PrefixExpr>(&element) => {
197 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; 180 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
198 181
199 let expr = prefix_expr.expr()?; 182 let expr = prefix_expr.expr()?;
@@ -203,41 +186,31 @@ pub(super) fn element(
203 } 186 }
204 .into() 187 .into()
205 } 188 }
206 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => { 189 _ if parent_matches::<ast::PrefixExpr>(&element) => HlOperator::Other.into(),
207 HlTag::Operator(HlOperator::Other).into()
208 }
209 T![+] | T![-] | T![*] | T![/] | T![+=] | T![-=] | T![*=] | T![/=] 190 T![+] | T![-] | T![*] | T![/] | T![+=] | T![-=] | T![*=] | T![/=]
210 if element.parent().and_then(ast::BinExpr::cast).is_some() => 191 if parent_matches::<ast::BinExpr>(&element) =>
211 { 192 {
212 HlTag::Operator(HlOperator::Arithmetic).into() 193 HlOperator::Arithmetic.into()
213 } 194 }
214 T![|] | T![&] | T![!] | T![^] | T![|=] | T![&=] | T![^=] 195 T![|] | T![&] | T![!] | T![^] | T![|=] | T![&=] | T![^=]
215 if element.parent().and_then(ast::BinExpr::cast).is_some() => 196 if parent_matches::<ast::BinExpr>(&element) =>
216 { 197 {
217 HlTag::Operator(HlOperator::Bitwise).into() 198 HlOperator::Bitwise.into()
218 } 199 }
219 T![&&] | T![||] if element.parent().and_then(ast::BinExpr::cast).is_some() => { 200 T![&&] | T![||] if parent_matches::<ast::BinExpr>(&element) => {
220 HlTag::Operator(HlOperator::Logical).into() 201 HlOperator::Logical.into()
221 } 202 }
222 T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=] 203 T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=]
223 if element.parent().and_then(ast::BinExpr::cast).is_some() => 204 if parent_matches::<ast::BinExpr>(&element) =>
224 { 205 {
225 HlTag::Operator(HlOperator::Comparison).into() 206 HlOperator::Comparison.into()
226 }
227 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => {
228 HlTag::Operator(HlOperator::Other).into()
229 } 207 }
230 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => { 208 _ if parent_matches::<ast::BinExpr>(&element) => HlOperator::Other.into(),
231 HlTag::Operator(HlOperator::Other).into() 209 _ if parent_matches::<ast::RangeExpr>(&element) => HlOperator::Other.into(),
232 } 210 _ if parent_matches::<ast::RangePat>(&element) => HlOperator::Other.into(),
233 _ if element.parent().and_then(ast::RangePat::cast).is_some() => { 211 _ if parent_matches::<ast::RestPat>(&element) => HlOperator::Other.into(),
234 HlTag::Operator(HlOperator::Other).into() 212 _ if parent_matches::<ast::Attr>(&element) => HlTag::Attribute.into(),
235 } 213 kind => match kind {
236 _ if element.parent().and_then(ast::RestPat::cast).is_some() => {
237 HlTag::Operator(HlOperator::Other).into()
238 }
239 _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(),
240 kind => HlTag::Punctuation(match kind {
241 T!['['] | T![']'] => HlPunct::Bracket, 214 T!['['] | T![']'] => HlPunct::Bracket,
242 T!['{'] | T!['}'] => HlPunct::Brace, 215 T!['{'] | T!['}'] => HlPunct::Brace,
243 T!['('] | T![')'] => HlPunct::Parenthesis, 216 T!['('] | T![')'] => HlPunct::Parenthesis,
@@ -247,22 +220,24 @@ pub(super) fn element(
247 T![;] => HlPunct::Semi, 220 T![;] => HlPunct::Semi,
248 T![.] => HlPunct::Dot, 221 T![.] => HlPunct::Dot,
249 _ => HlPunct::Other, 222 _ => HlPunct::Other,
250 }) 223 }
251 .into(), 224 .into(),
252 }, 225 },
253 226
254 k if k.is_keyword() => { 227 k if k.is_keyword() => {
255 let h = Highlight::new(HlTag::Keyword); 228 let h = Highlight::new(HlTag::Keyword);
256 match k { 229 match k {
257 T![break] 230 T![await]
231 | T![break]
258 | T![continue] 232 | T![continue]
259 | T![else] 233 | T![else]
260 | T![if] 234 | T![if]
235 | T![in]
261 | T![loop] 236 | T![loop]
262 | T![match] 237 | T![match]
263 | T![return] 238 | T![return]
264 | T![while] 239 | T![while]
265 | T![in] => h | HlMod::ControlFlow, 240 | T![yield] => h | HlMod::ControlFlow,
266 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow, 241 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow,
267 T![unsafe] => h | HlMod::Unsafe, 242 T![unsafe] => h | HlMod::Unsafe,
268 T![true] | T![false] => HlTag::BoolLiteral.into(), 243 T![true] | T![false] => HlTag::BoolLiteral.into(),
@@ -301,7 +276,6 @@ pub(super) fn element(
301 hash((name, shadow_count)) 276 hash((name, shadow_count))
302 } 277 }
303} 278}
304
305fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight { 279fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
306 match def { 280 match def {
307 Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro), 281 Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro),
@@ -317,12 +291,12 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
317 } 291 }
318 292
319 match item.container(db) { 293 match item.container(db) {
320 AssocItemContainer::Impl(i) => { 294 hir::AssocItemContainer::Impl(i) => {
321 if i.trait_(db).is_some() { 295 if i.trait_(db).is_some() {
322 h |= HlMod::Trait; 296 h |= HlMod::Trait;
323 } 297 }
324 } 298 }
325 AssocItemContainer::Trait(_t) => { 299 hir::AssocItemContainer::Trait(_t) => {
326 h |= HlMod::Trait; 300 h |= HlMod::Trait;
327 } 301 }
328 } 302 }
@@ -342,12 +316,12 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
342 if let Some(item) = konst.as_assoc_item(db) { 316 if let Some(item) = konst.as_assoc_item(db) {
343 h |= HlMod::Associated; 317 h |= HlMod::Associated;
344 match item.container(db) { 318 match item.container(db) {
345 AssocItemContainer::Impl(i) => { 319 hir::AssocItemContainer::Impl(i) => {
346 if i.trait_(db).is_some() { 320 if i.trait_(db).is_some() {
347 h |= HlMod::Trait; 321 h |= HlMod::Trait;
348 } 322 }
349 } 323 }
350 AssocItemContainer::Trait(_t) => { 324 hir::AssocItemContainer::Trait(_t) => {
351 h |= HlMod::Trait; 325 h |= HlMod::Trait;
352 } 326 }
353 } 327 }
@@ -361,12 +335,12 @@ fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
361 if let Some(item) = type_.as_assoc_item(db) { 335 if let Some(item) = type_.as_assoc_item(db) {
362 h |= HlMod::Associated; 336 h |= HlMod::Associated;
363 match item.container(db) { 337 match item.container(db) {
364 AssocItemContainer::Impl(i) => { 338 hir::AssocItemContainer::Impl(i) => {
365 if i.trait_(db).is_some() { 339 if i.trait_(db).is_some() {
366 h |= HlMod::Trait; 340 h |= HlMod::Trait;
367 } 341 }
368 } 342 }
369 AssocItemContainer::Trait(_t) => { 343 hir::AssocItemContainer::Trait(_t) => {
370 h |= HlMod::Trait; 344 h |= HlMod::Trait;
371 } 345 }
372 } 346 }
@@ -425,7 +399,7 @@ fn highlight_method_call(
425 method_call: &ast::MethodCallExpr, 399 method_call: &ast::MethodCallExpr,
426) -> Option<Highlight> { 400) -> Option<Highlight> {
427 let func = sema.resolve_method_call(&method_call)?; 401 let func = sema.resolve_method_call(&method_call)?;
428 let mut h = HlTag::Symbol(SymbolKind::Function).into(); 402 let mut h = SymbolKind::Function.into();
429 h |= HlMod::Associated; 403 h |= HlMod::Associated;
430 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { 404 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
431 h |= HlMod::Unsafe; 405 h |= HlMod::Unsafe;
@@ -461,20 +435,20 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
461 }; 435 };
462 436
463 let tag = match parent.kind() { 437 let tag = match parent.kind() {
464 STRUCT => HlTag::Symbol(SymbolKind::Struct), 438 STRUCT => SymbolKind::Struct,
465 ENUM => HlTag::Symbol(SymbolKind::Enum), 439 ENUM => SymbolKind::Enum,
466 VARIANT => HlTag::Symbol(SymbolKind::Variant), 440 VARIANT => SymbolKind::Variant,
467 UNION => HlTag::Symbol(SymbolKind::Union), 441 UNION => SymbolKind::Union,
468 TRAIT => HlTag::Symbol(SymbolKind::Trait), 442 TRAIT => SymbolKind::Trait,
469 TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias), 443 TYPE_ALIAS => SymbolKind::TypeAlias,
470 TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam), 444 TYPE_PARAM => SymbolKind::TypeParam,
471 RECORD_FIELD => HlTag::Symbol(SymbolKind::Field), 445 RECORD_FIELD => SymbolKind::Field,
472 MODULE => HlTag::Symbol(SymbolKind::Module), 446 MODULE => SymbolKind::Module,
473 FN => HlTag::Symbol(SymbolKind::Function), 447 FN => SymbolKind::Function,
474 CONST => HlTag::Symbol(SymbolKind::Const), 448 CONST => SymbolKind::Const,
475 STATIC => HlTag::Symbol(SymbolKind::Static), 449 STATIC => SymbolKind::Static,
476 IDENT_PAT => HlTag::Symbol(SymbolKind::Local), 450 IDENT_PAT => SymbolKind::Local,
477 _ => default, 451 _ => return default.into(),
478 }; 452 };
479 453
480 tag.into() 454 tag.into()
@@ -492,20 +466,15 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
492 METHOD_CALL_EXPR => { 466 METHOD_CALL_EXPR => {
493 return ast::MethodCallExpr::cast(parent) 467 return ast::MethodCallExpr::cast(parent)
494 .and_then(|it| highlight_method_call(sema, &it)) 468 .and_then(|it| highlight_method_call(sema, &it))
495 .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into()); 469 .unwrap_or_else(|| SymbolKind::Function.into());
496 } 470 }
497 FIELD_EXPR => { 471 FIELD_EXPR => {
498 let h = HlTag::Symbol(SymbolKind::Field); 472 let h = HlTag::Symbol(SymbolKind::Field);
499 let is_union = ast::FieldExpr::cast(parent) 473 let is_union = ast::FieldExpr::cast(parent)
500 .and_then(|field_expr| { 474 .and_then(|field_expr| sema.resolve_field(&field_expr))
501 let field = sema.resolve_field(&field_expr)?; 475 .map_or(false, |field| {
502 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) { 476 matches!(field.parent_def(sema.db), hir::VariantDef::Union(_))
503 true 477 });
504 } else {
505 false
506 })
507 })
508 .unwrap_or(false);
509 if is_union { 478 if is_union {
510 h | HlMod::Unsafe 479 h | HlMod::Unsafe
511 } else { 480 } else {
@@ -522,9 +491,9 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
522 _ => { 491 _ => {
523 // within path, decide whether it is module or adt by checking for uppercase name 492 // within path, decide whether it is module or adt by checking for uppercase name
524 return if name.text().chars().next().unwrap_or_default().is_uppercase() { 493 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
525 HlTag::Symbol(SymbolKind::Struct) 494 SymbolKind::Struct
526 } else { 495 } else {
527 HlTag::Symbol(SymbolKind::Module) 496 SymbolKind::Module
528 } 497 }
529 .into(); 498 .into();
530 } 499 }
@@ -535,11 +504,11 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
535 }; 504 };
536 505
537 match parent.kind() { 506 match parent.kind() {
538 CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(), 507 CALL_EXPR => SymbolKind::Function.into(),
539 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { 508 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
540 HlTag::Symbol(SymbolKind::Struct) 509 SymbolKind::Struct
541 } else { 510 } else {
542 HlTag::Symbol(SymbolKind::Const) 511 SymbolKind::Const
543 } 512 }
544 .into(), 513 .into(),
545 } 514 }
@@ -574,6 +543,11 @@ fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[Sy
574 kinds.len() == 0 543 kinds.len() == 0
575} 544}
576 545
546#[inline]
547fn parent_matches<N: AstNode>(element: &SyntaxElement) -> bool {
548 element.parent().map_or(false, |it| N::can_cast(it.kind()))
549}
550
577fn is_child_of_impl(element: &SyntaxElement) -> bool { 551fn is_child_of_impl(element: &SyntaxElement) -> bool {
578 match element.parent() { 552 match element.parent() {
579 Some(e) => e.kind() == IMPL, 553 Some(e) => e.kind() == IMPL,
diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs
index e58392d67..a304b3250 100644
--- a/crates/ide/src/syntax_highlighting/tags.rs
+++ b/crates/ide/src/syntax_highlighting/tags.rs
@@ -40,28 +40,33 @@ pub enum HlTag {
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
41#[repr(u8)] 41#[repr(u8)]
42pub enum HlMod { 42pub enum HlMod {
43 /// Used for items in traits and impls.
44 Associated = 0,
43 /// Used to differentiate individual elements within attributes. 45 /// Used to differentiate individual elements within attributes.
44 Attribute = 0, 46 Attribute,
47 /// Callable item or value.
48 Callable,
49 /// Value that is being consumed in a function call
50 Consuming,
45 /// Used with keywords like `if` and `break`. 51 /// Used with keywords like `if` and `break`.
46 ControlFlow, 52 ControlFlow,
47 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 53 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
48 /// not. 54 /// not.
49 Definition, 55 Definition,
56 /// Doc-strings like this one.
50 Documentation, 57 Documentation,
58 /// Highlighting injection like rust code in doc strings or ra_fixture.
51 Injected, 59 Injected,
52 Mutable,
53 Consuming,
54 Callable,
55 /// Used for associated functions
56 Static,
57 /// Used for items in impls&traits.
58 Associated,
59 /// Used for intra doc links in doc injection. 60 /// Used for intra doc links in doc injection.
60 IntraDocLink, 61 IntraDocLink,
62 /// Mutable binding.
63 Mutable,
64 /// Used for associated functions.
65 Static,
61 /// Used for items in traits and trait impls. 66 /// Used for items in traits and trait impls.
62 Trait, 67 Trait,
63 68 // Keep this last!
64 /// Keep this last! 69 /// Used for unsafe functions, mutable statics, union accesses and unsafe operations.
65 Unsafe, 70 Unsafe,
66} 71}
67 72
@@ -169,17 +174,17 @@ impl fmt::Display for HlTag {
169 174
170impl HlMod { 175impl HlMod {
171 const ALL: &'static [HlMod; HlMod::Unsafe as u8 as usize + 1] = &[ 176 const ALL: &'static [HlMod; HlMod::Unsafe as u8 as usize + 1] = &[
177 HlMod::Associated,
172 HlMod::Attribute, 178 HlMod::Attribute,
179 HlMod::Callable,
180 HlMod::Consuming,
173 HlMod::ControlFlow, 181 HlMod::ControlFlow,
174 HlMod::Definition, 182 HlMod::Definition,
175 HlMod::Documentation, 183 HlMod::Documentation,
176 HlMod::IntraDocLink,
177 HlMod::Injected, 184 HlMod::Injected,
185 HlMod::IntraDocLink,
178 HlMod::Mutable, 186 HlMod::Mutable,
179 HlMod::Consuming,
180 HlMod::Callable,
181 HlMod::Static, 187 HlMod::Static,
182 HlMod::Associated,
183 HlMod::Trait, 188 HlMod::Trait,
184 HlMod::Unsafe, 189 HlMod::Unsafe,
185 ]; 190 ];
@@ -229,6 +234,24 @@ impl From<HlTag> for Highlight {
229 } 234 }
230} 235}
231 236
237impl From<HlOperator> for Highlight {
238 fn from(op: HlOperator) -> Highlight {
239 Highlight::new(HlTag::Operator(op))
240 }
241}
242
243impl From<HlPunct> for Highlight {
244 fn from(punct: HlPunct) -> Highlight {
245 Highlight::new(HlTag::Punctuation(punct))
246 }
247}
248
249impl From<SymbolKind> for Highlight {
250 fn from(sym: SymbolKind) -> Highlight {
251 Highlight::new(HlTag::Symbol(sym))
252 }
253}
254
232impl Highlight { 255impl Highlight {
233 pub(crate) fn new(tag: HlTag) -> Highlight { 256 pub(crate) fn new(tag: HlTag) -> Highlight {
234 Highlight { tag, mods: HlMods::default() } 257 Highlight { tag, mods: HlMods::default() }
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
index 8cde3906c..a0ea1db34 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_assoc_functions.html
@@ -42,17 +42,17 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
42<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="brace">{</span><span class="brace">}</span> 42<span class="keyword">struct</span> <span class="struct declaration">foo</span> <span class="brace">{</span><span class="brace">}</span>
43 43
44<span class="keyword">impl</span> <span class="struct">foo</span> <span class="brace">{</span> 44<span class="keyword">impl</span> <span class="struct">foo</span> <span class="brace">{</span>
45 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 45 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration static">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
46 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 46 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
47<span class="brace">}</span> 47<span class="brace">}</span>
48 48
49<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span> 49<span class="keyword">trait</span> <span class="trait declaration">t</span> <span class="brace">{</span>
50 <span class="keyword">fn</span> <span class="function declaration static associated trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 50 <span class="keyword">fn</span> <span class="function associated declaration static trait">t_is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
51 <span class="keyword">fn</span> <span class="function declaration associated trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 51 <span class="keyword">fn</span> <span class="function associated declaration trait">t_is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
52<span class="brace">}</span> 52<span class="brace">}</span>
53 53
54<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span> 54<span class="keyword">impl</span> <span class="trait">t</span> <span class="keyword">for</span> <span class="struct">foo</span> <span class="brace">{</span>
55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration static associated trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 55 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration static trait">is_static</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
56 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 56 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration trait">is_not_static</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
57<span class="brace">}</span> 57<span class="brace">}</span>
58 </code></pre> \ No newline at end of file 58 </code></pre> \ No newline at end of file
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
index 6ee6d85fb..638f42c2f 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_doctest.html
@@ -50,7 +50,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
50 <span class="comment">// KILLER WHALE</span> 50 <span class="comment">// KILLER WHALE</span>
51 <span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="semicolon injected">;</span> 51 <span class="comment documentation">/// </span><span class="string_literal injected"> Ishmael."</span><span class="semicolon injected">;</span>
52 <span class="comment documentation">/// ```</span> 52 <span class="comment documentation">/// ```</span>
53 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant declaration associated">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="semicolon">;</span> 53 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="constant associated declaration">bar</span><span class="colon">:</span> <span class="builtin_type">bool</span> <span class="operator">=</span> <span class="bool_literal">true</span><span class="semicolon">;</span>
54 54
55 <span class="comment documentation">/// Constructs a new `Foo`.</span> 55 <span class="comment documentation">/// Constructs a new `Foo`.</span>
56 <span class="comment documentation">///</span> 56 <span class="comment documentation">///</span>
@@ -60,7 +60,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
60 <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="parenthesis attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute attribute injected">]</span> 60 <span class="comment documentation">/// #</span><span class="none injected"> </span><span class="attribute attribute injected">#</span><span class="attribute attribute injected">!</span><span class="attribute attribute injected">[</span><span class="function attribute injected">allow</span><span class="parenthesis attribute injected">(</span><span class="attribute attribute injected">unused_mut</span><span class="parenthesis attribute injected">)</span><span class="attribute attribute injected">]</span>
61 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span> 61 <span class="comment documentation">/// </span><span class="keyword injected">let</span><span class="none injected"> </span><span class="keyword injected">mut</span><span class="none injected"> </span><span class="variable declaration injected mutable">foo</span><span class="colon injected">:</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="none injected"> </span><span class="operator injected">=</span><span class="none injected"> </span><span class="struct injected">Foo</span><span class="operator injected">::</span><span class="function injected">new</span><span class="parenthesis injected">(</span><span class="parenthesis injected">)</span><span class="semicolon injected">;</span>
62 <span class="comment documentation">/// ```</span> 62 <span class="comment documentation">/// ```</span>
63 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function declaration static associated">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="brace">{</span> 63 <span class="keyword">pub</span> <span class="keyword">const</span> <span class="keyword">fn</span> <span class="function associated declaration static">new</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="struct">Foo</span> <span class="brace">{</span>
64 <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">bar</span><span class="colon">:</span> <span class="bool_literal">true</span> <span class="brace">}</span> 64 <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">bar</span><span class="colon">:</span> <span class="bool_literal">true</span> <span class="brace">}</span>
65 <span class="brace">}</span> 65 <span class="brace">}</span>
66 66
@@ -94,15 +94,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
94 <span class="comment documentation">/// ```sh</span> 94 <span class="comment documentation">/// ```sh</span>
95 <span class="comment documentation">/// echo 1</span> 95 <span class="comment documentation">/// echo 1</span>
96 <span class="comment documentation">/// ```</span> 96 <span class="comment documentation">/// ```</span>
97 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function declaration associated">foo</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="brace">{</span> 97 <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function associated declaration">foo</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">bool</span> <span class="brace">{</span>
98 <span class="bool_literal">true</span> 98 <span class="bool_literal">true</span>
99 <span class="brace">}</span> 99 <span class="brace">}</span>
100<span class="brace">}</span> 100<span class="brace">}</span>
101 101
102<span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span> 102<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Foo`](Foo)</span><span class="comment documentation"> is a struct</span>
103<span class="comment documentation">/// This function is &gt; </span><span class="function documentation intra_doc_link injected">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</span> 103<span class="comment documentation">/// This function is &gt; </span><span class="function documentation injected intra_doc_link">[`all_the_links`](all_the_links)</span><span class="comment documentation"> &lt;</span>
104<span class="comment documentation">/// [`noop`](noop) is a macro below</span> 104<span class="comment documentation">/// [`noop`](noop) is a macro below</span>
105<span class="comment documentation">/// </span><span class="struct documentation intra_doc_link injected">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation intra_doc_link injected">[`module`]</span> 105<span class="comment documentation">/// </span><span class="struct documentation injected intra_doc_link">[`Item`]</span><span class="comment documentation"> is a struct in the module </span><span class="module documentation injected intra_doc_link">[`module`]</span>
106<span class="comment documentation">///</span> 106<span class="comment documentation">///</span>
107<span class="comment documentation">/// [`Item`]: module::Item</span> 107<span class="comment documentation">/// [`Item`]: module::Item</span>
108<span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span> 108<span class="comment documentation">/// [mix_and_match]: ThisShouldntResolve</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
index 7c6694a27..6202a03ce 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_injection.html
@@ -42,7 +42,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
42<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 42<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
43 <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span> 43 <span class="function">fixture</span><span class="parenthesis">(</span><span class="string_literal">r#"</span>
44 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span> 44 <span class="keyword">trait</span> <span class="trait declaration">Foo</span> <span class="brace">{</span>
45 <span class="keyword">fn</span> <span class="function declaration static associated trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 45 <span class="keyword">fn</span> <span class="function associated declaration static trait">foo</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
46 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span> 46 <span class="macro">println!</span><span class="parenthesis">(</span><span class="string_literal">"2 + 2 = {}"</span><span class="comma">,</span> <span class="numeric_literal">4</span><span class="parenthesis">)</span><span class="semicolon">;</span>
47 <span class="brace">}</span> 47 <span class="brace">}</span>
48 <span class="brace">}</span><span class="string_literal">"#</span> 48 <span class="brace">}</span><span class="string_literal">"#</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
index 72910421d..68165bdbf 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_unsafe.html
@@ -47,7 +47,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
47<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="semicolon">;</span> 47<span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="semicolon">;</span>
48 48
49<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="brace">{</span> 49<span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="brace">{</span>
50 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration associated unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 50 <span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function associated declaration unsafe">unsafe_method</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
51<span class="brace">}</span> 51<span class="brace">}</span>
52 52
53<span class="keyword">struct</span> <span class="struct declaration">TypeForStaticMut</span> <span class="brace">{</span> 53<span class="keyword">struct</span> <span class="struct declaration">TypeForStaticMut</span> <span class="brace">{</span>
@@ -62,11 +62,11 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62<span class="brace">}</span> 62<span class="brace">}</span>
63 63
64<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span> 64<span class="keyword">trait</span> <span class="trait declaration">DoTheAutoref</span> <span class="brace">{</span>
65 <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span> 65 <span class="keyword">fn</span> <span class="function associated declaration trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span><span class="semicolon">;</span>
66<span class="brace">}</span> 66<span class="brace">}</span>
67 67
68<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span> 68<span class="keyword">impl</span> <span class="trait">DoTheAutoref</span> <span class="keyword">for</span> <span class="builtin_type">u16</span> <span class="brace">{</span>
69 <span class="keyword">fn</span> <span class="function declaration associated trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span> 69 <span class="keyword">fn</span> <span class="function associated declaration trait">calls_autoref</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="brace">{</span><span class="brace">}</span>
70<span class="brace">}</span> 70<span class="brace">}</span>
71 71
72<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span> 72<span class="keyword">fn</span> <span class="function declaration">main</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="brace">{</span>
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index c43bcb691..4319e8b50 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -67,25 +67,25 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
67<span class="brace">}</span> 67<span class="brace">}</span>
68 68
69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span> 69<span class="keyword">trait</span> <span class="trait declaration">Bar</span> <span class="brace">{</span>
70 <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span> 70 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span><span class="semicolon">;</span>
71<span class="brace">}</span> 71<span class="brace">}</span>
72 72
73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span> 73<span class="keyword">impl</span> <span class="trait">Bar</span> <span class="keyword">for</span> <span class="struct">Foo</span> <span class="brace">{</span>
74 <span class="keyword">fn</span> <span class="function declaration associated trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 74 <span class="keyword">fn</span> <span class="function associated declaration trait">bar</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 75 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
76 <span class="brace">}</span> 76 <span class="brace">}</span>
77<span class="brace">}</span> 77<span class="brace">}</span>
78 78
79<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span> 79<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="brace">{</span>
80 <span class="keyword">fn</span> <span class="function declaration associated">baz</span><span class="parenthesis">(</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">Foo</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 80 <span class="keyword">fn</span> <span class="function associated declaration">baz</span><span class="parenthesis">(</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">Foo</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
81 <span class="value_param">f</span><span class="operator">.</span><span class="function consuming associated">baz</span><span class="parenthesis">(</span><span class="self_keyword mutable consuming">self</span><span class="parenthesis">)</span> 81 <span class="value_param">f</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="self_keyword consuming mutable">self</span><span class="parenthesis">)</span>
82 <span class="brace">}</span> 82 <span class="brace">}</span>
83 83
84 <span class="keyword">fn</span> <span class="function declaration associated">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span> 84 <span class="keyword">fn</span> <span class="function associated declaration">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
85 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span> 85 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
86 <span class="brace">}</span> 86 <span class="brace">}</span>
87 87
88 <span class="keyword">fn</span> <span class="function declaration associated">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span> 88 <span class="keyword">fn</span> <span class="function associated declaration">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="brace">{</span>
89 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 89 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
90 <span class="brace">}</span> 90 <span class="brace">}</span>
91<span class="brace">}</span> 91<span class="brace">}</span>
@@ -96,15 +96,15 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
96<span class="brace">}</span> 96<span class="brace">}</span>
97 97
98<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="brace">{</span> 98<span class="keyword">impl</span> <span class="struct">FooCopy</span> <span class="brace">{</span>
99 <span class="keyword">fn</span> <span class="function declaration associated">baz</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">FooCopy</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span> 99 <span class="keyword">fn</span> <span class="function associated declaration">baz</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">f</span><span class="colon">:</span> <span class="struct">FooCopy</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span>
100 <span class="value_param">f</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="self_keyword">self</span><span class="parenthesis">)</span> 100 <span class="value_param">f</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="self_keyword">self</span><span class="parenthesis">)</span>
101 <span class="brace">}</span> 101 <span class="brace">}</span>
102 102
103 <span class="keyword">fn</span> <span class="function declaration associated">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span> 103 <span class="keyword">fn</span> <span class="function associated declaration">qux</span><span class="parenthesis">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword declaration mutable">self</span><span class="parenthesis">)</span> <span class="brace">{</span>
104 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span> 104 <span class="self_keyword mutable">self</span><span class="operator">.</span><span class="field">x</span> <span class="operator">=</span> <span class="numeric_literal">0</span><span class="semicolon">;</span>
105 <span class="brace">}</span> 105 <span class="brace">}</span>
106 106
107 <span class="keyword">fn</span> <span class="function declaration associated">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span> 107 <span class="keyword">fn</span> <span class="function associated declaration">quop</span><span class="parenthesis">(</span><span class="operator">&</span><span class="self_keyword declaration">self</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">u32</span> <span class="brace">{</span>
108 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span> 108 <span class="self_keyword">self</span><span class="operator">.</span><span class="field">x</span>
109 <span class="brace">}</span> 109 <span class="brace">}</span>
110<span class="brace">}</span> 110<span class="brace">}</span>
@@ -128,7 +128,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
128<span class="brace">}</span> 128<span class="brace">}</span>
129 129
130<span class="keyword">use</span> <span class="module">ops</span><span class="operator">::</span><span class="trait">Fn</span><span class="semicolon">;</span> 130<span class="keyword">use</span> <span class="module">ops</span><span class="operator">::</span><span class="trait">Fn</span><span class="semicolon">;</span>
131<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param declaration callable">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span> 131<span class="keyword">fn</span> <span class="function declaration">baz</span><span class="angle">&lt;</span><span class="type_param declaration">F</span><span class="colon">:</span> <span class="trait">Fn</span><span class="parenthesis">(</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="parenthesis">(</span><span class="parenthesis">)</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="value_param callable declaration">f</span><span class="colon">:</span> <span class="type_param">F</span><span class="parenthesis">)</span> <span class="brace">{</span>
132 <span class="value_param callable">f</span><span class="parenthesis">(</span><span class="parenthesis">)</span> 132 <span class="value_param callable">f</span><span class="parenthesis">(</span><span class="parenthesis">)</span>
133<span class="brace">}</span> 133<span class="brace">}</span>
134 134
@@ -199,16 +199,16 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
199 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span> 199 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">foo</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
200 <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span> 200 <span class="keyword">let</span> <span class="variable declaration">foo2</span> <span class="operator">=</span> <span class="struct">Foo</span> <span class="brace">{</span> <span class="field">x</span><span class="comma">,</span> <span class="field">y</span><span class="colon">:</span> <span class="variable mutable">x</span> <span class="brace">}</span><span class="semicolon">;</span>
201 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 201 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
202 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function mutable associated">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 202 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated mutable">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
203 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function consuming associated">baz</span><span class="parenthesis">(</span><span class="variable consuming">foo2</span><span class="parenthesis">)</span><span class="semicolon">;</span> 203 <span class="variable mutable">foo</span><span class="operator">.</span><span class="function associated consuming">baz</span><span class="parenthesis">(</span><span class="variable consuming">foo2</span><span class="parenthesis">)</span><span class="semicolon">;</span>
204 204
205 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="brace">{</span> <span class="field">x</span> <span class="brace">}</span><span class="semicolon">;</span> 205 <span class="keyword">let</span> <span class="keyword">mut</span> <span class="variable declaration mutable">copy</span> <span class="operator">=</span> <span class="struct">FooCopy</span> <span class="brace">{</span> <span class="field">x</span> <span class="brace">}</span><span class="semicolon">;</span>
206 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 206 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">quop</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
207 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function mutable associated">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span> 207 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated mutable">qux</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="semicolon">;</span>
208 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="variable mutable">copy</span><span class="parenthesis">)</span><span class="semicolon">;</span> 208 <span class="variable mutable">copy</span><span class="operator">.</span><span class="function associated">baz</span><span class="parenthesis">(</span><span class="variable mutable">copy</span><span class="parenthesis">)</span><span class="semicolon">;</span>
209 209
210 <span class="keyword">let</span> <span class="variable declaration callable">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span> 210 <span class="keyword">let</span> <span class="variable callable declaration">a</span> <span class="operator">=</span> <span class="punctuation">|</span><span class="value_param declaration">x</span><span class="punctuation">|</span> <span class="value_param">x</span><span class="semicolon">;</span>
211 <span class="keyword">let</span> <span class="variable declaration callable">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated">baz</span><span class="semicolon">;</span> 211 <span class="keyword">let</span> <span class="variable callable declaration">bar</span> <span class="operator">=</span> <span class="struct">Foo</span><span class="operator">::</span><span class="function associated">baz</span><span class="semicolon">;</span>
212 212
213 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span> 213 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="numeric_literal">42</span><span class="semicolon">;</span>
214 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span> 214 <span class="keyword">let</span> <span class="variable declaration">baz</span> <span class="operator">=</span> <span class="operator">-</span><span class="variable">baz</span><span class="semicolon">;</span>
@@ -228,7 +228,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
228<span class="keyword">use</span> <span class="enum">Option</span><span class="operator">::</span><span class="punctuation">*</span><span class="semicolon">;</span> 228<span class="keyword">use</span> <span class="enum">Option</span><span class="operator">::</span><span class="punctuation">*</span><span class="semicolon">;</span>
229 229
230<span class="keyword">impl</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">T</span><span class="angle">&gt;</span> <span class="brace">{</span> 230<span class="keyword">impl</span><span class="angle">&lt;</span><span class="type_param declaration">T</span><span class="angle">&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">T</span><span class="angle">&gt;</span> <span class="brace">{</span>
231 <span class="keyword">fn</span> <span class="function declaration associated">and</span><span class="angle">&lt;</span><span class="type_param declaration">U</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">other</span><span class="colon">:</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">U</span><span class="angle">&gt;</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="comma">,</span> <span class="type_param">U</span><span class="parenthesis">)</span><span class="angle">&gt;</span> <span class="brace">{</span> 231 <span class="keyword">fn</span> <span class="function associated declaration">and</span><span class="angle">&lt;</span><span class="type_param declaration">U</span><span class="angle">&gt;</span><span class="parenthesis">(</span><span class="self_keyword declaration">self</span><span class="comma">,</span> <span class="value_param declaration">other</span><span class="colon">:</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="type_param">U</span><span class="angle">&gt;</span><span class="parenthesis">)</span> <span class="operator">-&gt;</span> <span class="enum">Option</span><span class="angle">&lt;</span><span class="parenthesis">(</span><span class="type_param">T</span><span class="comma">,</span> <span class="type_param">U</span><span class="parenthesis">)</span><span class="angle">&gt;</span> <span class="brace">{</span>
232 <span class="keyword control">match</span> <span class="value_param">other</span> <span class="brace">{</span> 232 <span class="keyword control">match</span> <span class="value_param">other</span> <span class="brace">{</span>
233 <span class="enum_variant">None</span> <span class="operator">=&gt;</span> <span class="macro">unimplemented!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span> 233 <span class="enum_variant">None</span> <span class="operator">=&gt;</span> <span class="macro">unimplemented!</span><span class="parenthesis">(</span><span class="parenthesis">)</span><span class="comma">,</span>
234 <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="comma">,</span> 234 <span class="variable declaration">Nope</span> <span class="operator">=&gt;</span> <span class="variable">Nope</span><span class="comma">,</span>
diff --git a/crates/ide_assists/src/ast_transform.rs b/crates/ide_assists/src/ast_transform.rs
index 4a3ed7783..e5ae718c9 100644
--- a/crates/ide_assists/src/ast_transform.rs
+++ b/crates/ide_assists/src/ast_transform.rs
@@ -3,20 +3,27 @@ use hir::{HirDisplay, PathResolution, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast; 3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
5use syntax::{ 5use syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode}, 6 ast::{self, AstNode},
8 SyntaxNode, 7 ted, SyntaxNode,
9}; 8};
10 9
11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N { 10pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: &N) {
12 SyntaxRewriter::from_fn(|element| match element { 11 let mut skip_to = None;
13 syntax::SyntaxElement::Node(n) => { 12 for event in node.syntax().preorder() {
14 let replacement = transformer.get_substitution(&n, transformer)?; 13 match event {
15 Some(replacement.into()) 14 syntax::WalkEvent::Enter(node) if skip_to.is_none() => {
15 skip_to = transformer.get_substitution(&node, transformer).zip(Some(node));
16 }
17 syntax::WalkEvent::Enter(_) => (),
18 syntax::WalkEvent::Leave(node) => match &skip_to {
19 Some((replacement, skip_target)) if *skip_target == node => {
20 ted::replace(node, replacement.clone_for_update());
21 skip_to.take();
22 }
23 _ => (),
24 },
16 } 25 }
17 _ => None, 26 }
18 })
19 .rewrite_ast(&node)
20} 27}
21 28
22/// `AstTransform` helps with applying bulk transformations to syntax nodes. 29/// `AstTransform` helps with applying bulk transformations to syntax nodes.
@@ -191,11 +198,9 @@ impl<'a> AstTransform<'a> for QualifyPaths<'a> {
191 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?; 198 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192 let mut path = mod_path_to_ast(&found_path); 199 let mut path = mod_path_to_ast(&found_path);
193 200
194 let type_args = p 201 let type_args = p.segment().and_then(|s| s.generic_arg_list());
195 .segment()
196 .and_then(|s| s.generic_arg_list())
197 .map(|arg_list| apply(recur, arg_list));
198 if let Some(type_args) = type_args { 202 if let Some(type_args) = type_args {
203 apply(recur, &type_args);
199 let last_segment = path.segment().unwrap(); 204 let last_segment = path.segment().unwrap();
200 path = path.with_segment(last_segment.with_generic_args(type_args)) 205 path = path.with_segment(last_segment.with_generic_args(type_args))
201 } 206 }
diff --git a/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
new file mode 100644
index 000000000..b5b5ada5e
--- /dev/null
+++ b/crates/ide_assists/src/handlers/convert_tuple_struct_to_named_struct.rs
@@ -0,0 +1,516 @@
1use ide_db::defs::{Definition, NameRefClass};
2use syntax::{
3 ast::{self, AstNode, GenericParamsOwner, VisibilityOwner},
4 match_ast, SyntaxNode,
5};
6
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: convert_tuple_struct_to_named_struct
10//
11// Converts tuple struct to struct with named fields.
12//
13// ```
14// struct Point$0(f32, f32);
15//
16// impl Point {
17// pub fn new(x: f32, y: f32) -> Self {
18// Point(x, y)
19// }
20//
21// pub fn x(&self) -> f32 {
22// self.0
23// }
24//
25// pub fn y(&self) -> f32 {
26// self.1
27// }
28// }
29// ```
30// ->
31// ```
32// struct Point { field1: f32, field2: f32 }
33//
34// impl Point {
35// pub fn new(x: f32, y: f32) -> Self {
36// Point { field1: x, field2: y }
37// }
38//
39// pub fn x(&self) -> f32 {
40// self.field1
41// }
42//
43// pub fn y(&self) -> f32 {
44// self.field2
45// }
46// }
47// ```
48pub(crate) fn convert_tuple_struct_to_named_struct(
49 acc: &mut Assists,
50 ctx: &AssistContext,
51) -> Option<()> {
52 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
53 let tuple_fields = match strukt.field_list()? {
54 ast::FieldList::TupleFieldList(it) => it,
55 ast::FieldList::RecordFieldList(_) => return None,
56 };
57 let strukt_def = ctx.sema.to_def(&strukt)?;
58
59 let target = strukt.syntax().text_range();
60 acc.add(
61 AssistId("convert_tuple_struct_to_named_struct", AssistKind::RefactorRewrite),
62 "Convert to named struct",
63 target,
64 |edit| {
65 let names = generate_names(tuple_fields.fields());
66 edit_field_references(ctx, edit, tuple_fields.fields(), &names);
67 edit_struct_references(ctx, edit, strukt_def, &names);
68 edit_struct_def(ctx, edit, &strukt, tuple_fields, names);
69 },
70 )
71}
72
73fn edit_struct_def(
74 ctx: &AssistContext,
75 edit: &mut AssistBuilder,
76 strukt: &ast::Struct,
77 tuple_fields: ast::TupleFieldList,
78 names: Vec<ast::Name>,
79) {
80 let record_fields = tuple_fields
81 .fields()
82 .zip(names)
83 .filter_map(|(f, name)| Some(ast::make::record_field(f.visibility(), name, f.ty()?)));
84 let record_fields = ast::make::record_field_list(record_fields);
85 let tuple_fields_text_range = tuple_fields.syntax().text_range();
86
87 edit.edit_file(ctx.frange.file_id);
88
89 if let Some(w) = strukt.where_clause() {
90 edit.delete(w.syntax().text_range());
91 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
92 edit.insert(tuple_fields_text_range.start(), w.syntax().text());
93 edit.insert(tuple_fields_text_range.start(), ",");
94 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_newline().text());
95 } else {
96 edit.insert(tuple_fields_text_range.start(), ast::make::tokens::single_space().text());
97 }
98
99 edit.replace(tuple_fields_text_range, record_fields.to_string());
100 strukt.semicolon_token().map(|t| edit.delete(t.text_range()));
101}
102
103fn edit_struct_references(
104 ctx: &AssistContext,
105 edit: &mut AssistBuilder,
106 strukt: hir::Struct,
107 names: &[ast::Name],
108) {
109 let strukt_def = Definition::ModuleDef(hir::ModuleDef::Adt(hir::Adt::Struct(strukt)));
110 let usages = strukt_def.usages(&ctx.sema).include_self_kw_refs(true).all();
111
112 let edit_node = |edit: &mut AssistBuilder, node: SyntaxNode| -> Option<()> {
113 match_ast! {
114 match node {
115 ast::TupleStructPat(tuple_struct_pat) => {
116 edit.replace(
117 tuple_struct_pat.syntax().text_range(),
118 ast::make::record_pat_with_fields(
119 tuple_struct_pat.path()?,
120 ast::make::record_pat_field_list(tuple_struct_pat.fields().zip(names).map(
121 |(pat, name)| {
122 ast::make::record_pat_field(
123 ast::make::name_ref(&name.to_string()),
124 pat,
125 )
126 },
127 )),
128 )
129 .to_string(),
130 );
131 },
132 // for tuple struct creations like Foo(42)
133 ast::CallExpr(call_expr) => {
134 let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
135
136 // this also includes method calls like Foo::new(42), we should skip them
137 if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
138 match NameRefClass::classify(&ctx.sema, &name_ref) {
139 Some(NameRefClass::Definition(Definition::SelfType(_))) => {},
140 Some(NameRefClass::Definition(def)) if def == strukt_def => {},
141 _ => return None,
142 };
143 }
144
145 let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
146
147 edit.replace(
148 call_expr.syntax().text_range(),
149 ast::make::record_expr(
150 path,
151 ast::make::record_expr_field_list(arg_list.args().zip(names).map(
152 |(expr, name)| {
153 ast::make::record_expr_field(
154 ast::make::name_ref(&name.to_string()),
155 Some(expr),
156 )
157 },
158 )),
159 )
160 .to_string(),
161 );
162 },
163 _ => return None,
164 }
165 }
166 Some(())
167 };
168
169 for (file_id, refs) in usages {
170 edit.edit_file(file_id);
171 for r in refs {
172 for node in r.name.syntax().ancestors() {
173 if edit_node(edit, node).is_some() {
174 break;
175 }
176 }
177 }
178 }
179}
180
181fn edit_field_references(
182 ctx: &AssistContext,
183 edit: &mut AssistBuilder,
184 fields: impl Iterator<Item = ast::TupleField>,
185 names: &[ast::Name],
186) {
187 for (field, name) in fields.zip(names) {
188 let field = match ctx.sema.to_def(&field) {
189 Some(it) => it,
190 None => continue,
191 };
192 let def = Definition::Field(field);
193 let usages = def.usages(&ctx.sema).all();
194 for (file_id, refs) in usages {
195 edit.edit_file(file_id);
196 for r in refs {
197 if let Some(name_ref) = r.name.as_name_ref() {
198 edit.replace(name_ref.syntax().text_range(), name.text());
199 }
200 }
201 }
202 }
203}
204
205fn generate_names(fields: impl Iterator<Item = ast::TupleField>) -> Vec<ast::Name> {
206 fields.enumerate().map(|(i, _)| ast::make::name(&format!("field{}", i + 1))).collect()
207}
208
209#[cfg(test)]
210mod tests {
211 use crate::tests::{check_assist, check_assist_not_applicable};
212
213 use super::*;
214
215 #[test]
216 fn not_applicable_other_than_tuple_struct() {
217 check_assist_not_applicable(
218 convert_tuple_struct_to_named_struct,
219 r#"struct Foo$0 { bar: u32 };"#,
220 );
221 check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#);
222 }
223
224 #[test]
225 fn convert_simple_struct() {
226 check_assist(
227 convert_tuple_struct_to_named_struct,
228 r#"
229struct Inner;
230struct A$0(Inner);
231
232impl A {
233 fn new(inner: Inner) -> A {
234 A(inner)
235 }
236
237 fn new_with_default() -> A {
238 A::new(Inner)
239 }
240
241 fn into_inner(self) -> Inner {
242 self.0
243 }
244}"#,
245 r#"
246struct Inner;
247struct A { field1: Inner }
248
249impl A {
250 fn new(inner: Inner) -> A {
251 A { field1: inner }
252 }
253
254 fn new_with_default() -> A {
255 A::new(Inner)
256 }
257
258 fn into_inner(self) -> Inner {
259 self.field1
260 }
261}"#,
262 );
263 }
264
265 #[test]
266 fn convert_struct_referenced_via_self_kw() {
267 check_assist(
268 convert_tuple_struct_to_named_struct,
269 r#"
270struct Inner;
271struct A$0(Inner);
272
273impl A {
274 fn new(inner: Inner) -> Self {
275 Self(inner)
276 }
277
278 fn new_with_default() -> Self {
279 Self::new(Inner)
280 }
281
282 fn into_inner(self) -> Inner {
283 self.0
284 }
285}"#,
286 r#"
287struct Inner;
288struct A { field1: Inner }
289
290impl A {
291 fn new(inner: Inner) -> Self {
292 Self { field1: inner }
293 }
294
295 fn new_with_default() -> Self {
296 Self::new(Inner)
297 }
298
299 fn into_inner(self) -> Inner {
300 self.field1
301 }
302}"#,
303 );
304 }
305
306 #[test]
307 fn convert_destructured_struct() {
308 check_assist(
309 convert_tuple_struct_to_named_struct,
310 r#"
311struct Inner;
312struct A$0(Inner);
313
314impl A {
315 fn into_inner(self) -> Inner {
316 let A(first) = self;
317 first
318 }
319
320 fn into_inner_via_self(self) -> Inner {
321 let Self(first) = self;
322 first
323 }
324}"#,
325 r#"
326struct Inner;
327struct A { field1: Inner }
328
329impl A {
330 fn into_inner(self) -> Inner {
331 let A { field1: first } = self;
332 first
333 }
334
335 fn into_inner_via_self(self) -> Inner {
336 let Self { field1: first } = self;
337 first
338 }
339}"#,
340 );
341 }
342
343 #[test]
344 fn convert_struct_with_visibility() {
345 check_assist(
346 convert_tuple_struct_to_named_struct,
347 r#"
348struct A$0(pub u32, pub(crate) u64);
349
350impl A {
351 fn new() -> A {
352 A(42, 42)
353 }
354
355 fn into_first(self) -> u32 {
356 self.0
357 }
358
359 fn into_second(self) -> u64 {
360 self.1
361 }
362}"#,
363 r#"
364struct A { pub field1: u32, pub(crate) field2: u64 }
365
366impl A {
367 fn new() -> A {
368 A { field1: 42, field2: 42 }
369 }
370
371 fn into_first(self) -> u32 {
372 self.field1
373 }
374
375 fn into_second(self) -> u64 {
376 self.field2
377 }
378}"#,
379 );
380 }
381
382 #[test]
383 fn convert_struct_with_wrapped_references() {
384 check_assist(
385 convert_tuple_struct_to_named_struct,
386 r#"
387struct Inner$0(u32);
388struct Outer(Inner);
389
390impl Outer {
391 fn new() -> Self {
392 Self(Inner(42))
393 }
394
395 fn into_inner(self) -> u32 {
396 (self.0).0
397 }
398
399 fn into_inner_destructed(self) -> u32 {
400 let Outer(Inner(x)) = self;
401 x
402 }
403}"#,
404 r#"
405struct Inner { field1: u32 }
406struct Outer(Inner);
407
408impl Outer {
409 fn new() -> Self {
410 Self(Inner { field1: 42 })
411 }
412
413 fn into_inner(self) -> u32 {
414 (self.0).field1
415 }
416
417 fn into_inner_destructed(self) -> u32 {
418 let Outer(Inner { field1: x }) = self;
419 x
420 }
421}"#,
422 );
423
424 check_assist(
425 convert_tuple_struct_to_named_struct,
426 r#"
427struct Inner(u32);
428struct Outer$0(Inner);
429
430impl Outer {
431 fn new() -> Self {
432 Self(Inner(42))
433 }
434
435 fn into_inner(self) -> u32 {
436 (self.0).0
437 }
438
439 fn into_inner_destructed(self) -> u32 {
440 let Outer(Inner(x)) = self;
441 x
442 }
443}"#,
444 r#"
445struct Inner(u32);
446struct Outer { field1: Inner }
447
448impl Outer {
449 fn new() -> Self {
450 Self { field1: Inner(42) }
451 }
452
453 fn into_inner(self) -> u32 {
454 (self.field1).0
455 }
456
457 fn into_inner_destructed(self) -> u32 {
458 let Outer { field1: Inner(x) } = self;
459 x
460 }
461}"#,
462 );
463 }
464
465 #[test]
466 fn convert_struct_with_multi_file_references() {
467 check_assist(
468 convert_tuple_struct_to_named_struct,
469 r#"
470//- /main.rs
471struct Inner;
472struct A$0(Inner);
473
474mod foo;
475
476//- /foo.rs
477use crate::{A, Inner};
478fn f() {
479 let a = A(Inner);
480}
481"#,
482 r#"
483//- /main.rs
484struct Inner;
485struct A { field1: Inner }
486
487mod foo;
488
489//- /foo.rs
490use crate::{A, Inner};
491fn f() {
492 let a = A { field1: Inner };
493}
494"#,
495 );
496 }
497
498 #[test]
499 fn convert_struct_with_where_clause() {
500 check_assist(
501 convert_tuple_struct_to_named_struct,
502 r#"
503struct Wrap$0<T>(T)
504where
505 T: Display;
506"#,
507 r#"
508struct Wrap<T>
509where
510 T: Display,
511{ field1: T }
512
513"#,
514 );
515 }
516}
diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs
index 78a57fbdc..5f80a40c8 100644
--- a/crates/ide_assists/src/handlers/extract_function.rs
+++ b/crates/ide_assists/src/handlers/extract_function.rs
@@ -1227,9 +1227,19 @@ fn make_body(
1227 FunctionBody::Expr(expr) => { 1227 FunctionBody::Expr(expr) => {
1228 let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax()); 1228 let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax());
1229 let expr = ast::Expr::cast(expr).unwrap(); 1229 let expr = ast::Expr::cast(expr).unwrap();
1230 let expr = expr.dedent(old_indent).indent(IndentLevel(1)); 1230 match expr {
1231 ast::Expr::BlockExpr(block) => {
1232 // If the extracted expression is itself a block, there is no need to wrap it inside another block.
1233 let block = block.dedent(old_indent);
1234 // Recreate the block for formatting consistency with other extracted functions.
1235 make::block_expr(block.statements(), block.tail_expr())
1236 }
1237 _ => {
1238 let expr = expr.dedent(old_indent).indent(IndentLevel(1));
1231 1239
1232 make::block_expr(Vec::new(), Some(expr)) 1240 make::block_expr(Vec::new(), Some(expr))
1241 }
1242 }
1233 } 1243 }
1234 FunctionBody::Span { parent, text_range } => { 1244 FunctionBody::Span { parent, text_range } => {
1235 let mut elements: Vec<_> = parent 1245 let mut elements: Vec<_> = parent
@@ -1544,7 +1554,7 @@ fn foo() {
1544} 1554}
1545 1555
1546fn $0fun_name() -> i32 { 1556fn $0fun_name() -> i32 {
1547 { 1 + 1 } 1557 1 + 1
1548}"#, 1558}"#,
1549 ); 1559 );
1550 } 1560 }
@@ -2526,17 +2536,15 @@ fn foo() {
2526} 2536}
2527 2537
2528fn $0fun_name(n: &mut i32) { 2538fn $0fun_name(n: &mut i32) {
2529 { 2539 *n += *n;
2530 *n += *n; 2540 bar(*n);
2531 bar(*n); 2541 bar(*n+1);
2532 bar(*n+1); 2542 bar(*n**n);
2533 bar(*n**n); 2543 bar(&*n);
2534 bar(&*n); 2544 n.inc();
2535 n.inc(); 2545 let v = n;
2536 let v = n; 2546 *v = v.succ();
2537 *v = v.succ(); 2547 n.succ();
2538 n.succ();
2539 }
2540}", 2548}",
2541 ); 2549 );
2542 } 2550 }
diff --git a/crates/ide_assists/src/handlers/fill_match_arms.rs b/crates/ide_assists/src/handlers/fill_match_arms.rs
index a30c4d04e..be927cc1c 100644
--- a/crates/ide_assists/src/handlers/fill_match_arms.rs
+++ b/crates/ide_assists/src/handlers/fill_match_arms.rs
@@ -53,7 +53,7 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
53 .iter() 53 .iter()
54 .filter_map(ast::MatchArm::pat) 54 .filter_map(ast::MatchArm::pat)
55 .flat_map(|pat| match pat { 55 .flat_map(|pat| match pat {
56 // Special casee OrPat as separate top-level pats 56 // Special case OrPat as separate top-level pats
57 Pat::OrPat(or_pat) => Either::Left(or_pat.pats()), 57 Pat::OrPat(or_pat) => Either::Left(or_pat.pats()),
58 _ => Either::Right(iter::once(pat)), 58 _ => Either::Right(iter::once(pat)),
59 }) 59 })
@@ -72,7 +72,11 @@ pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<
72 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat)) 72 .filter(|variant_pat| is_variant_missing(&top_lvl_pats, variant_pat))
73 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block())) 73 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
74 .collect::<Vec<_>>(); 74 .collect::<Vec<_>>();
75 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() { 75 if Some(enum_def)
76 == FamousDefs(&ctx.sema, Some(module.krate()))
77 .core_option_Option()
78 .map(|x| lift_enum(x))
79 {
76 // Match `Some` variant first. 80 // Match `Some` variant first.
77 cov_mark::hit!(option_order); 81 cov_mark::hit!(option_order);
78 variants.reverse() 82 variants.reverse()
@@ -151,49 +155,99 @@ fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
151 } 155 }
152} 156}
153 157
154fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> { 158#[derive(Eq, PartialEq, Clone)]
159enum ExtendedEnum {
160 Bool,
161 Enum(hir::Enum),
162}
163
164#[derive(Eq, PartialEq, Clone)]
165enum ExtendedVariant {
166 True,
167 False,
168 Variant(hir::Variant),
169}
170
171fn lift_enum(e: hir::Enum) -> ExtendedEnum {
172 ExtendedEnum::Enum(e)
173}
174
175impl ExtendedEnum {
176 fn variants(&self, db: &RootDatabase) -> Vec<ExtendedVariant> {
177 match self {
178 ExtendedEnum::Enum(e) => {
179 e.variants(db).into_iter().map(|x| ExtendedVariant::Variant(x)).collect::<Vec<_>>()
180 }
181 ExtendedEnum::Bool => {
182 Vec::<ExtendedVariant>::from([ExtendedVariant::True, ExtendedVariant::False])
183 }
184 }
185 }
186}
187
188fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<ExtendedEnum> {
155 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() { 189 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
156 Some(Adt::Enum(e)) => Some(e), 190 Some(Adt::Enum(e)) => Some(ExtendedEnum::Enum(e)),
157 _ => None, 191 _ => {
192 if ty.is_bool() {
193 Some(ExtendedEnum::Bool)
194 } else {
195 None
196 }
197 }
158 }) 198 })
159} 199}
160 200
161fn resolve_tuple_of_enum_def( 201fn resolve_tuple_of_enum_def(
162 sema: &Semantics<RootDatabase>, 202 sema: &Semantics<RootDatabase>,
163 expr: &ast::Expr, 203 expr: &ast::Expr,
164) -> Option<Vec<hir::Enum>> { 204) -> Option<Vec<ExtendedEnum>> {
165 sema.type_of_expr(&expr)? 205 sema.type_of_expr(&expr)?
166 .tuple_fields(sema.db) 206 .tuple_fields(sema.db)
167 .iter() 207 .iter()
168 .map(|ty| { 208 .map(|ty| {
169 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() { 209 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
170 Some(Adt::Enum(e)) => Some(e), 210 Some(Adt::Enum(e)) => Some(lift_enum(e)),
171 // For now we only handle expansion for a tuple of enums. Here 211 // For now we only handle expansion for a tuple of enums. Here
172 // we map non-enum items to None and rely on `collect` to 212 // we map non-enum items to None and rely on `collect` to
173 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>. 213 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
174 _ => None, 214 _ => {
215 if ty.is_bool() {
216 Some(ExtendedEnum::Bool)
217 } else {
218 None
219 }
220 }
175 }) 221 })
176 }) 222 })
177 .collect() 223 .collect()
178} 224}
179 225
180fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option<ast::Pat> { 226fn build_pat(db: &RootDatabase, module: hir::Module, var: ExtendedVariant) -> Option<ast::Pat> {
181 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?); 227 match var {
228 ExtendedVariant::Variant(var) => {
229 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
230
231 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
232 let pat: ast::Pat = match var.source(db)?.value.kind() {
233 ast::StructKind::Tuple(field_list) => {
234 let pats =
235 iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
236 make::tuple_struct_pat(path, pats).into()
237 }
238 ast::StructKind::Record(field_list) => {
239 let pats =
240 field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
241 make::record_pat(path, pats).into()
242 }
243 ast::StructKind::Unit => make::path_pat(path),
244 };
182 245
183 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though 246 Some(pat)
184 let pat: ast::Pat = match var.source(db)?.value.kind() {
185 ast::StructKind::Tuple(field_list) => {
186 let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
187 make::tuple_struct_pat(path, pats).into()
188 }
189 ast::StructKind::Record(field_list) => {
190 let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
191 make::record_pat(path, pats).into()
192 } 247 }
193 ast::StructKind::Unit => make::path_pat(path), 248 ExtendedVariant::True => Some(ast::Pat::from(make::literal_pat("true"))),
194 }; 249 ExtendedVariant::False => Some(ast::Pat::from(make::literal_pat("false"))),
195 250 }
196 Some(pat)
197} 251}
198 252
199#[cfg(test)] 253#[cfg(test)]
@@ -226,6 +280,21 @@ mod tests {
226 } 280 }
227 281
228 #[test] 282 #[test]
283 fn all_boolean_match_arms_provided() {
284 check_assist_not_applicable(
285 fill_match_arms,
286 r#"
287 fn foo(a: bool) {
288 match a$0 {
289 true => {}
290 false => {}
291 }
292 }
293 "#,
294 )
295 }
296
297 #[test]
229 fn tuple_of_non_enum() { 298 fn tuple_of_non_enum() {
230 // for now this case is not handled, although it potentially could be 299 // for now this case is not handled, although it potentially could be
231 // in the future 300 // in the future
@@ -241,6 +310,113 @@ mod tests {
241 } 310 }
242 311
243 #[test] 312 #[test]
313 fn fill_match_arms_boolean() {
314 check_assist(
315 fill_match_arms,
316 r#"
317 fn foo(a: bool) {
318 match a$0 {
319 }
320 }
321 "#,
322 r#"
323 fn foo(a: bool) {
324 match a {
325 $0true => {}
326 false => {}
327 }
328 }
329 "#,
330 )
331 }
332
333 #[test]
334 fn partial_fill_boolean() {
335 check_assist(
336 fill_match_arms,
337 r#"
338 fn foo(a: bool) {
339 match a$0 {
340 true => {}
341 }
342 }
343 "#,
344 r#"
345 fn foo(a: bool) {
346 match a {
347 true => {}
348 $0false => {}
349 }
350 }
351 "#,
352 )
353 }
354
355 #[test]
356 fn all_boolean_tuple_arms_provided() {
357 check_assist_not_applicable(
358 fill_match_arms,
359 r#"
360 fn foo(a: bool) {
361 match (a, a)$0 {
362 (true, true) => {}
363 (true, false) => {}
364 (false, true) => {}
365 (false, false) => {}
366 }
367 }
368 "#,
369 )
370 }
371
372 #[test]
373 fn fill_boolean_tuple() {
374 check_assist(
375 fill_match_arms,
376 r#"
377 fn foo(a: bool) {
378 match (a, a)$0 {
379 }
380 }
381 "#,
382 r#"
383 fn foo(a: bool) {
384 match (a, a) {
385 $0(true, true) => {}
386 (true, false) => {}
387 (false, true) => {}
388 (false, false) => {}
389 }
390 }
391 "#,
392 )
393 }
394
395 #[test]
396 fn partial_fill_boolean_tuple() {
397 check_assist(
398 fill_match_arms,
399 r#"
400 fn foo(a: bool) {
401 match (a, a)$0 {
402 (false, true) => {}
403 }
404 }
405 "#,
406 r#"
407 fn foo(a: bool) {
408 match (a, a) {
409 (false, true) => {}
410 $0(true, true) => {}
411 (true, false) => {}
412 (false, false) => {}
413 }
414 }
415 "#,
416 )
417 }
418
419 #[test]
244 fn partial_fill_record_tuple() { 420 fn partial_fill_record_tuple() {
245 check_assist( 421 check_assist(
246 fill_match_arms, 422 fill_match_arms,
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs
index ea1466dc8..f5dafc8cb 100644
--- a/crates/ide_assists/src/handlers/inline_local_variable.rs
+++ b/crates/ide_assists/src/handlers/inline_local_variable.rs
@@ -1,7 +1,9 @@
1use ide_db::{defs::Definition, search::FileReference}; 1use either::Either;
2use hir::PathResolution;
3use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
2use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
3use syntax::{ 5use syntax::{
4 ast::{self, AstNode, AstToken}, 6 ast::{self, AstNode, AstToken, NameOwner},
5 TextRange, 7 TextRange,
6}; 8};
7 9
@@ -27,44 +29,28 @@ use crate::{
27// } 29// }
28// ``` 30// ```
29pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; 32 let InlineData { let_stmt, delete_let, replace_usages, target } =
31 let bind_pat = match let_stmt.pat()? { 33 inline_let(ctx).or_else(|| inline_usage(ctx))?;
32 ast::Pat::IdentPat(pat) => pat,
33 _ => return None,
34 };
35 if bind_pat.mut_token().is_some() {
36 cov_mark::hit!(test_not_inline_mut_variable);
37 return None;
38 }
39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
40 cov_mark::hit!(not_applicable_outside_of_bind_pat);
41 return None;
42 }
43 let initializer_expr = let_stmt.initializer()?; 34 let initializer_expr = let_stmt.initializer()?;
44 35
45 let def = ctx.sema.to_def(&bind_pat)?; 36 let delete_range = if delete_let {
46 let def = Definition::Local(def); 37 if let Some(whitespace) = let_stmt
47 let usages = def.usages(&ctx.sema).all(); 38 .syntax()
48 if usages.is_empty() { 39 .next_sibling_or_token()
49 cov_mark::hit!(test_not_applicable_if_variable_unused); 40 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
50 return None; 41 {
51 }; 42 Some(TextRange::new(
52 43 let_stmt.syntax().text_range().start(),
53 let delete_range = if let Some(whitespace) = let_stmt 44 whitespace.syntax().text_range().end(),
54 .syntax() 45 ))
55 .next_sibling_or_token() 46 } else {
56 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) 47 Some(let_stmt.syntax().text_range())
57 { 48 }
58 TextRange::new(
59 let_stmt.syntax().text_range().start(),
60 whitespace.syntax().text_range().end(),
61 )
62 } else { 49 } else {
63 let_stmt.syntax().text_range() 50 None
64 }; 51 };
65 52
66 let wrap_in_parens = usages 53 let wrap_in_parens = replace_usages
67 .references
68 .iter() 54 .iter()
69 .map(|(&file_id, refs)| { 55 .map(|(&file_id, refs)| {
70 refs.iter() 56 refs.iter()
@@ -114,14 +100,20 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
114 let init_str = initializer_expr.syntax().text().to_string(); 100 let init_str = initializer_expr.syntax().text().to_string();
115 let init_in_paren = format!("({})", &init_str); 101 let init_in_paren = format!("({})", &init_str);
116 102
117 let target = bind_pat.syntax().text_range(); 103 let target = match target {
104 ast::NameOrNameRef::Name(it) => it.syntax().text_range(),
105 ast::NameOrNameRef::NameRef(it) => it.syntax().text_range(),
106 };
107
118 acc.add( 108 acc.add(
119 AssistId("inline_local_variable", AssistKind::RefactorInline), 109 AssistId("inline_local_variable", AssistKind::RefactorInline),
120 "Inline variable", 110 "Inline variable",
121 target, 111 target,
122 move |builder| { 112 move |builder| {
123 builder.delete(delete_range); 113 if let Some(range) = delete_range {
124 for (file_id, references) in usages.references { 114 builder.delete(range);
115 }
116 for (file_id, references) in replace_usages {
125 for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { 117 for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) {
126 let replacement = 118 let replacement =
127 if should_wrap { init_in_paren.clone() } else { init_str.clone() }; 119 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
@@ -140,6 +132,81 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O
140 ) 132 )
141} 133}
142 134
135struct InlineData {
136 let_stmt: ast::LetStmt,
137 delete_let: bool,
138 target: ast::NameOrNameRef,
139 replace_usages: FxHashMap<FileId, Vec<FileReference>>,
140}
141
142fn inline_let(ctx: &AssistContext) -> Option<InlineData> {
143 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
144 let bind_pat = match let_stmt.pat()? {
145 ast::Pat::IdentPat(pat) => pat,
146 _ => return None,
147 };
148 if bind_pat.mut_token().is_some() {
149 cov_mark::hit!(test_not_inline_mut_variable);
150 return None;
151 }
152 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
153 cov_mark::hit!(not_applicable_outside_of_bind_pat);
154 return None;
155 }
156
157 let def = ctx.sema.to_def(&bind_pat)?;
158 let def = Definition::Local(def);
159 let usages = def.usages(&ctx.sema).all();
160 if usages.is_empty() {
161 cov_mark::hit!(test_not_applicable_if_variable_unused);
162 return None;
163 };
164
165 Some(InlineData {
166 let_stmt,
167 delete_let: true,
168 target: ast::NameOrNameRef::Name(bind_pat.name()?),
169 replace_usages: usages.references,
170 })
171}
172
173fn inline_usage(ctx: &AssistContext) -> Option<InlineData> {
174 let path_expr = ctx.find_node_at_offset::<ast::PathExpr>()?;
175 let path = path_expr.path()?;
176 let name = match path.as_single_segment()?.kind()? {
177 ast::PathSegmentKind::Name(name) => name,
178 _ => return None,
179 };
180
181 let local = match ctx.sema.resolve_path(&path)? {
182 PathResolution::Local(local) => local,
183 _ => return None,
184 };
185
186 let bind_pat = match local.source(ctx.db()).value {
187 Either::Left(ident) => ident,
188 _ => return None,
189 };
190
191 let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
192
193 let def = Definition::Local(local);
194 let mut usages = def.usages(&ctx.sema).all();
195
196 let delete_let = usages.references.values().map(|v| v.len()).sum::<usize>() == 1;
197
198 for references in usages.references.values_mut() {
199 references.retain(|reference| reference.name.as_name_ref() == Some(&name));
200 }
201
202 Some(InlineData {
203 let_stmt,
204 delete_let,
205 target: ast::NameOrNameRef::NameRef(name),
206 replace_usages: usages.references,
207 })
208}
209
143#[cfg(test)] 210#[cfg(test)]
144mod tests { 211mod tests {
145 use crate::tests::{check_assist, check_assist_not_applicable}; 212 use crate::tests::{check_assist, check_assist_not_applicable};
@@ -726,4 +793,84 @@ fn main() {
726", 793",
727 ) 794 )
728 } 795 }
796
797 #[test]
798 fn works_on_local_usage() {
799 check_assist(
800 inline_local_variable,
801 r#"
802fn f() {
803 let xyz = 0;
804 xyz$0;
805}
806"#,
807 r#"
808fn f() {
809 0;
810}
811"#,
812 );
813 }
814
815 #[test]
816 fn does_not_remove_let_when_multiple_usages() {
817 check_assist(
818 inline_local_variable,
819 r#"
820fn f() {
821 let xyz = 0;
822 xyz$0;
823 xyz;
824}
825"#,
826 r#"
827fn f() {
828 let xyz = 0;
829 0;
830 xyz;
831}
832"#,
833 );
834 }
835
836 #[test]
837 fn not_applicable_with_non_ident_pattern() {
838 check_assist_not_applicable(
839 inline_local_variable,
840 r#"
841fn main() {
842 let (x, y) = (0, 1);
843 x$0;
844}
845"#,
846 );
847 }
848
849 #[test]
850 fn not_applicable_on_local_usage_in_macro() {
851 check_assist_not_applicable(
852 inline_local_variable,
853 r#"
854macro_rules! m {
855 ($i:ident) => { $i }
856}
857fn f() {
858 let xyz = 0;
859 m!(xyz$0); // replacing it would break the macro
860}
861"#,
862 );
863 check_assist_not_applicable(
864 inline_local_variable,
865 r#"
866macro_rules! m {
867 ($i:ident) => { $i }
868}
869fn f() {
870 let xyz$0 = 0;
871 m!(xyz); // replacing it would break the macro
872}
873"#,
874 );
875 }
729} 876}
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
index 870a8d4ff..694d897d1 100644
--- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
+++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs
@@ -47,6 +47,11 @@ pub(crate) fn replace_derive_with_manual_impl(
47 return None; 47 return None;
48 } 48 }
49 49
50 if !args.syntax().text_range().contains(ctx.offset()) {
51 cov_mark::hit!(outside_of_attr_args);
52 return None;
53 }
54
50 let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; 55 let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?;
51 let trait_name = trait_token.text(); 56 let trait_name = trait_token.text();
52 57
@@ -207,7 +212,7 @@ mod tests {
207 fn add_custom_impl_debug() { 212 fn add_custom_impl_debug() {
208 check_assist( 213 check_assist(
209 replace_derive_with_manual_impl, 214 replace_derive_with_manual_impl,
210 " 215 r#"
211mod fmt { 216mod fmt {
212 pub struct Error; 217 pub struct Error;
213 pub type Result = Result<(), Error>; 218 pub type Result = Result<(), Error>;
@@ -221,8 +226,8 @@ mod fmt {
221struct Foo { 226struct Foo {
222 bar: String, 227 bar: String,
223} 228}
224", 229"#,
225 " 230 r#"
226mod fmt { 231mod fmt {
227 pub struct Error; 232 pub struct Error;
228 pub type Result = Result<(), Error>; 233 pub type Result = Result<(), Error>;
@@ -241,14 +246,14 @@ impl fmt::Debug for Foo {
241 ${0:todo!()} 246 ${0:todo!()}
242 } 247 }
243} 248}
244", 249"#,
245 ) 250 )
246 } 251 }
247 #[test] 252 #[test]
248 fn add_custom_impl_all() { 253 fn add_custom_impl_all() {
249 check_assist( 254 check_assist(
250 replace_derive_with_manual_impl, 255 replace_derive_with_manual_impl,
251 " 256 r#"
252mod foo { 257mod foo {
253 pub trait Bar { 258 pub trait Bar {
254 type Qux; 259 type Qux;
@@ -263,8 +268,8 @@ mod foo {
263struct Foo { 268struct Foo {
264 bar: String, 269 bar: String,
265} 270}
266", 271"#,
267 " 272 r#"
268mod foo { 273mod foo {
269 pub trait Bar { 274 pub trait Bar {
270 type Qux; 275 type Qux;
@@ -290,20 +295,20 @@ impl foo::Bar for Foo {
290 todo!() 295 todo!()
291 } 296 }
292} 297}
293", 298"#,
294 ) 299 )
295 } 300 }
296 #[test] 301 #[test]
297 fn add_custom_impl_for_unique_input() { 302 fn add_custom_impl_for_unique_input() {
298 check_assist( 303 check_assist(
299 replace_derive_with_manual_impl, 304 replace_derive_with_manual_impl,
300 " 305 r#"
301#[derive(Debu$0g)] 306#[derive(Debu$0g)]
302struct Foo { 307struct Foo {
303 bar: String, 308 bar: String,
304} 309}
305 ", 310 "#,
306 " 311 r#"
307struct Foo { 312struct Foo {
308 bar: String, 313 bar: String,
309} 314}
@@ -311,7 +316,7 @@ struct Foo {
311impl Debug for Foo { 316impl Debug for Foo {
312 $0 317 $0
313} 318}
314 ", 319 "#,
315 ) 320 )
316 } 321 }
317 322
@@ -319,13 +324,13 @@ impl Debug for Foo {
319 fn add_custom_impl_for_with_visibility_modifier() { 324 fn add_custom_impl_for_with_visibility_modifier() {
320 check_assist( 325 check_assist(
321 replace_derive_with_manual_impl, 326 replace_derive_with_manual_impl,
322 " 327 r#"
323#[derive(Debug$0)] 328#[derive(Debug$0)]
324pub struct Foo { 329pub struct Foo {
325 bar: String, 330 bar: String,
326} 331}
327 ", 332 "#,
328 " 333 r#"
329pub struct Foo { 334pub struct Foo {
330 bar: String, 335 bar: String,
331} 336}
@@ -333,7 +338,7 @@ pub struct Foo {
333impl Debug for Foo { 338impl Debug for Foo {
334 $0 339 $0
335} 340}
336 ", 341 "#,
337 ) 342 )
338 } 343 }
339 344
@@ -341,18 +346,18 @@ impl Debug for Foo {
341 fn add_custom_impl_when_multiple_inputs() { 346 fn add_custom_impl_when_multiple_inputs() {
342 check_assist( 347 check_assist(
343 replace_derive_with_manual_impl, 348 replace_derive_with_manual_impl,
344 " 349 r#"
345#[derive(Display, Debug$0, Serialize)] 350#[derive(Display, Debug$0, Serialize)]
346struct Foo {} 351struct Foo {}
347 ", 352 "#,
348 " 353 r#"
349#[derive(Display, Serialize)] 354#[derive(Display, Serialize)]
350struct Foo {} 355struct Foo {}
351 356
352impl Debug for Foo { 357impl Debug for Foo {
353 $0 358 $0
354} 359}
355 ", 360 "#,
356 ) 361 )
357 } 362 }
358 363
@@ -360,10 +365,10 @@ impl Debug for Foo {
360 fn test_ignore_derive_macro_without_input() { 365 fn test_ignore_derive_macro_without_input() {
361 check_assist_not_applicable( 366 check_assist_not_applicable(
362 replace_derive_with_manual_impl, 367 replace_derive_with_manual_impl,
363 " 368 r#"
364#[derive($0)] 369#[derive($0)]
365struct Foo {} 370struct Foo {}
366 ", 371 "#,
367 ) 372 )
368 } 373 }
369 374
@@ -371,18 +376,18 @@ struct Foo {}
371 fn test_ignore_if_cursor_on_param() { 376 fn test_ignore_if_cursor_on_param() {
372 check_assist_not_applicable( 377 check_assist_not_applicable(
373 replace_derive_with_manual_impl, 378 replace_derive_with_manual_impl,
374 " 379 r#"
375#[derive$0(Debug)] 380#[derive$0(Debug)]
376struct Foo {} 381struct Foo {}
377 ", 382 "#,
378 ); 383 );
379 384
380 check_assist_not_applicable( 385 check_assist_not_applicable(
381 replace_derive_with_manual_impl, 386 replace_derive_with_manual_impl,
382 " 387 r#"
383#[derive(Debug)$0] 388#[derive(Debug)$0]
384struct Foo {} 389struct Foo {}
385 ", 390 "#,
386 ) 391 )
387 } 392 }
388 393
@@ -390,10 +395,22 @@ struct Foo {}
390 fn test_ignore_if_not_derive() { 395 fn test_ignore_if_not_derive() {
391 check_assist_not_applicable( 396 check_assist_not_applicable(
392 replace_derive_with_manual_impl, 397 replace_derive_with_manual_impl,
393 " 398 r#"
394#[allow(non_camel_$0case_types)] 399#[allow(non_camel_$0case_types)]
395struct Foo {} 400struct Foo {}
396 ", 401 "#,
397 ) 402 )
398 } 403 }
404
405 #[test]
406 fn works_at_start_of_file() {
407 cov_mark::check!(outside_of_attr_args);
408 check_assist_not_applicable(
409 replace_derive_with_manual_impl,
410 r#"
411$0#[derive(Debug)]
412struct S;
413 "#,
414 );
415 }
399} 416}
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs
index 8996c1b61..88ae5c9a9 100644
--- a/crates/ide_assists/src/lib.rs
+++ b/crates/ide_assists/src/lib.rs
@@ -120,6 +120,7 @@ mod handlers {
120 mod convert_comment_block; 120 mod convert_comment_block;
121 mod convert_iter_for_each_to_for; 121 mod convert_iter_for_each_to_for;
122 mod convert_into_to_from; 122 mod convert_into_to_from;
123 mod convert_tuple_struct_to_named_struct;
123 mod early_return; 124 mod early_return;
124 mod expand_glob_import; 125 mod expand_glob_import;
125 mod extract_function; 126 mod extract_function;
@@ -190,6 +191,7 @@ mod handlers {
190 convert_comment_block::convert_comment_block, 191 convert_comment_block::convert_comment_block,
191 convert_iter_for_each_to_for::convert_iter_for_each_to_for, 192 convert_iter_for_each_to_for::convert_iter_for_each_to_for,
192 convert_into_to_from::convert_into_to_from, 193 convert_into_to_from::convert_into_to_from,
194 convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
193 early_return::convert_to_guarded_return, 195 early_return::convert_to_guarded_return,
194 expand_glob_import::expand_glob_import, 196 expand_glob_import::expand_glob_import,
195 extract_struct_from_enum_variant::extract_struct_from_enum_variant, 197 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs
index 41559b43a..59bcef8fb 100644
--- a/crates/ide_assists/src/tests/generated.rs
+++ b/crates/ide_assists/src/tests/generated.rs
@@ -292,6 +292,47 @@ fn main() {
292} 292}
293 293
294#[test] 294#[test]
295fn doctest_convert_tuple_struct_to_named_struct() {
296 check_doc_test(
297 "convert_tuple_struct_to_named_struct",
298 r#####"
299struct Point$0(f32, f32);
300
301impl Point {
302 pub fn new(x: f32, y: f32) -> Self {
303 Point(x, y)
304 }
305
306 pub fn x(&self) -> f32 {
307 self.0
308 }
309
310 pub fn y(&self) -> f32 {
311 self.1
312 }
313}
314"#####,
315 r#####"
316struct Point { field1: f32, field2: f32 }
317
318impl Point {
319 pub fn new(x: f32, y: f32) -> Self {
320 Point { field1: x, field2: y }
321 }
322
323 pub fn x(&self) -> f32 {
324 self.field1
325 }
326
327 pub fn y(&self) -> f32 {
328 self.field2
329 }
330}
331"#####,
332 )
333}
334
335#[test]
295fn doctest_expand_glob_import() { 336fn doctest_expand_glob_import() {
296 check_doc_test( 337 check_doc_test(
297 "expand_glob_import", 338 "expand_glob_import",
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs
index d67524937..5a90ad715 100644
--- a/crates/ide_assists/src/utils.rs
+++ b/crates/ide_assists/src/utils.rs
@@ -140,7 +140,8 @@ pub fn add_trait_assoc_items_to_impl(
140 140
141 let items = items 141 let items = items
142 .into_iter() 142 .into_iter()
143 .map(|it| ast_transform::apply(&*ast_transform, it)) 143 .map(|it| it.clone_for_update())
144 .inspect(|it| ast_transform::apply(&*ast_transform, it))
144 .map(|it| match it { 145 .map(|it| match it {
145 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)), 146 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
146 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()), 147 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index b55e3851e..8f899ea56 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -7,7 +7,9 @@
7use std::{convert::TryInto, mem}; 7use std::{convert::TryInto, mem};
8 8
9use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt}; 9use base_db::{FileId, FileRange, SourceDatabase, SourceDatabaseExt};
10use hir::{DefWithBody, HasAttrs, HasSource, InFile, ModuleSource, Semantics, Visibility}; 10use hir::{
11 DefWithBody, HasAttrs, HasSource, InFile, ModuleDef, ModuleSource, Semantics, Visibility,
12};
11use once_cell::unsync::Lazy; 13use once_cell::unsync::Lazy;
12use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
13use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; 15use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
@@ -295,7 +297,7 @@ impl Definition {
295 } 297 }
296 298
297 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> { 299 pub fn usages<'a>(&'a self, sema: &'a Semantics<RootDatabase>) -> FindUsages<'a> {
298 FindUsages { def: self, sema, scope: None } 300 FindUsages { def: self, sema, scope: None, include_self_kw_refs: false }
299 } 301 }
300} 302}
301 303
@@ -303,9 +305,15 @@ pub struct FindUsages<'a> {
303 def: &'a Definition, 305 def: &'a Definition,
304 sema: &'a Semantics<'a, RootDatabase>, 306 sema: &'a Semantics<'a, RootDatabase>,
305 scope: Option<SearchScope>, 307 scope: Option<SearchScope>,
308 include_self_kw_refs: bool,
306} 309}
307 310
308impl<'a> FindUsages<'a> { 311impl<'a> FindUsages<'a> {
312 pub fn include_self_kw_refs(mut self, include: bool) -> FindUsages<'a> {
313 self.include_self_kw_refs = include;
314 self
315 }
316
309 pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> { 317 pub fn in_scope(self, scope: SearchScope) -> FindUsages<'a> {
310 self.set_scope(Some(scope)) 318 self.set_scope(Some(scope))
311 } 319 }
@@ -352,6 +360,8 @@ impl<'a> FindUsages<'a> {
352 }; 360 };
353 361
354 let pat = name.as_str(); 362 let pat = name.as_str();
363 let search_for_self = self.include_self_kw_refs;
364
355 for (file_id, search_range) in search_scope { 365 for (file_id, search_range) in search_scope {
356 let text = sema.db.file_text(file_id); 366 let text = sema.db.file_text(file_id);
357 let search_range = 367 let search_range =
@@ -359,31 +369,47 @@ impl<'a> FindUsages<'a> {
359 369
360 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone()); 370 let tree = Lazy::new(|| sema.parse(file_id).syntax().clone());
361 371
362 for (idx, _) in text.match_indices(pat) { 372 let mut handle_match = |idx: usize| -> bool {
363 let offset: TextSize = idx.try_into().unwrap(); 373 let offset: TextSize = idx.try_into().unwrap();
364 if !search_range.contains_inclusive(offset) { 374 if !search_range.contains_inclusive(offset) {
365 continue; 375 return false;
366 } 376 }
367 377
368 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) { 378 if let Some(name) = sema.find_node_at_offset_with_descend(&tree, offset) {
369 match name { 379 match name {
370 ast::NameLike::NameRef(name_ref) => { 380 ast::NameLike::NameRef(name_ref) => {
371 if self.found_name_ref(&name_ref, sink) { 381 if self.found_name_ref(&name_ref, sink) {
372 return; 382 return true;
373 } 383 }
374 } 384 }
375 ast::NameLike::Name(name) => { 385 ast::NameLike::Name(name) => {
376 if self.found_name(&name, sink) { 386 if self.found_name(&name, sink) {
377 return; 387 return true;
378 } 388 }
379 } 389 }
380 ast::NameLike::Lifetime(lifetime) => { 390 ast::NameLike::Lifetime(lifetime) => {
381 if self.found_lifetime(&lifetime, sink) { 391 if self.found_lifetime(&lifetime, sink) {
382 return; 392 return true;
383 } 393 }
384 } 394 }
385 } 395 }
386 } 396 }
397
398 return false;
399 };
400
401 for (idx, _) in text.match_indices(pat) {
402 if handle_match(idx) {
403 return;
404 }
405 }
406
407 if search_for_self {
408 for (idx, _) in text.match_indices("Self") {
409 if handle_match(idx) {
410 return;
411 }
412 }
387 } 413 }
388 } 414 }
389 } 415 }
@@ -422,6 +448,24 @@ impl<'a> FindUsages<'a> {
422 }; 448 };
423 sink(file_id, reference) 449 sink(file_id, reference)
424 } 450 }
451 Some(NameRefClass::Definition(Definition::SelfType(impl_))) => {
452 let ty = impl_.self_ty(self.sema.db);
453
454 if let Some(adt) = ty.as_adt() {
455 if &Definition::ModuleDef(ModuleDef::Adt(adt)) == self.def {
456 let FileRange { file_id, range } =
457 self.sema.original_range(name_ref.syntax());
458 let reference = FileReference {
459 range,
460 name: ast::NameLike::NameRef(name_ref.clone()),
461 access: None,
462 };
463 return sink(file_id, reference);
464 }
465 }
466
467 false
468 }
425 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { 469 Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => {
426 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); 470 let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
427 let reference = match self.def { 471 let reference = match self.def {
diff --git a/crates/parser/src/grammar/patterns.rs b/crates/parser/src/grammar/patterns.rs
index da71498a8..3ab347834 100644
--- a/crates/parser/src/grammar/patterns.rs
+++ b/crates/parser/src/grammar/patterns.rs
@@ -206,13 +206,15 @@ fn record_pat_field_list(p: &mut Parser) {
206 T![.] if p.at(T![..]) => p.bump(T![..]), 206 T![.] if p.at(T![..]) => p.bump(T![..]),
207 T!['{'] => error_block(p, "expected ident"), 207 T!['{'] => error_block(p, "expected ident"),
208 208
209 c => { 209 _ => {
210 let m = p.start(); 210 let m = p.start();
211 match c { 211 attributes::outer_attrs(p);
212 match p.current() {
212 // test record_pat_field 213 // test record_pat_field
213 // fn foo() { 214 // fn foo() {
214 // let S { 0: 1 } = (); 215 // let S { 0: 1 } = ();
215 // let S { x: 1 } = (); 216 // let S { x: 1 } = ();
217 // let S { #[cfg(any())] x: 1 } = ();
216 // } 218 // }
217 IDENT | INT_NUMBER if p.nth(1) == T![:] => { 219 IDENT | INT_NUMBER if p.nth(1) == T![:] => {
218 name_ref_or_index(p); 220 name_ref_or_index(p);
diff --git a/crates/proc_macro_api/Cargo.toml b/crates/proc_macro_api/Cargo.toml
index 16fd56c7e..1ba1e4abd 100644
--- a/crates/proc_macro_api/Cargo.toml
+++ b/crates/proc_macro_api/Cargo.toml
@@ -15,10 +15,11 @@ serde_json = { version = "1.0", features = ["unbounded_depth"] }
15log = "0.4.8" 15log = "0.4.8"
16crossbeam-channel = "0.5.0" 16crossbeam-channel = "0.5.0"
17jod-thread = "0.1.1" 17jod-thread = "0.1.1"
18memmap = "0.7.0"
19object = { version = "0.23.0", default-features = false, features = ["std", "read_core", "elf", "macho", "pe", "unaligned"] }
20snap = "1.0"
18 21
19tt = { path = "../tt", version = "0.0.0" } 22tt = { path = "../tt", version = "0.0.0" }
20base_db = { path = "../base_db", version = "0.0.0" } 23base_db = { path = "../base_db", version = "0.0.0" }
21stdx = { path = "../stdx", version = "0.0.0" } 24stdx = { path = "../stdx", version = "0.0.0" }
22snap = "1" 25profile = { path = "../profile", version = "0.0.0" }
23object = { version = "0.23.0", default-features = false, features = ["std", "read_core", "elf", "macho", "pe", "unaligned"] }
24memmap = "0.7.0"
diff --git a/crates/proc_macro_api/src/lib.rs b/crates/proc_macro_api/src/lib.rs
index 2dd2a8541..654bd9943 100644
--- a/crates/proc_macro_api/src/lib.rs
+++ b/crates/proc_macro_api/src/lib.rs
@@ -77,6 +77,7 @@ impl ProcMacroClient {
77 } 77 }
78 78
79 pub fn by_dylib_path(&self, dylib_path: &Path) -> Vec<ProcMacro> { 79 pub fn by_dylib_path(&self, dylib_path: &Path) -> Vec<ProcMacro> {
80 let _p = profile::span("ProcMacroClient::by_dylib_path");
80 match version::read_dylib_info(dylib_path) { 81 match version::read_dylib_info(dylib_path) {
81 Ok(info) => { 82 Ok(info) => {
82 if info.version.0 < 1 || info.version.1 < 47 { 83 if info.version.0 < 1 || info.version.1 < 47 {
diff --git a/crates/project_model/src/build_data.rs b/crates/project_model/src/build_data.rs
index ab5cc8c49..faca336de 100644
--- a/crates/project_model/src/build_data.rs
+++ b/crates/project_model/src/build_data.rs
@@ -1,7 +1,6 @@
1//! Handles build script specific information 1//! Handles build script specific information
2 2
3use std::{ 3use std::{
4 io::BufReader,
5 path::PathBuf, 4 path::PathBuf,
6 process::{Command, Stdio}, 5 process::{Command, Stdio},
7 sync::Arc, 6 sync::Arc,
@@ -13,7 +12,8 @@ use cargo_metadata::{BuildScript, Message};
13use itertools::Itertools; 12use itertools::Itertools;
14use paths::{AbsPath, AbsPathBuf}; 13use paths::{AbsPath, AbsPathBuf};
15use rustc_hash::FxHashMap; 14use rustc_hash::FxHashMap;
16use stdx::{format_to, JodChild}; 15use serde::Deserialize;
16use stdx::format_to;
17 17
18use crate::{cfg_flag::CfgFlag, CargoConfig}; 18use crate::{cfg_flag::CfgFlag, CargoConfig};
19 19
@@ -171,67 +171,86 @@ impl WorkspaceBuildData {
171 171
172 cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); 172 cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
173 173
174 let mut child = cmd.spawn().map(JodChild)?;
175 let child_stdout = child.stdout.take().unwrap();
176 let stdout = BufReader::new(child_stdout);
177
178 let mut res = WorkspaceBuildData::default(); 174 let mut res = WorkspaceBuildData::default();
179 for message in cargo_metadata::Message::parse_stream(stdout).flatten() {
180 match message {
181 Message::BuildScriptExecuted(BuildScript {
182 package_id,
183 out_dir,
184 cfgs,
185 env,
186 ..
187 }) => {
188 let cfgs = {
189 let mut acc = Vec::new();
190 for cfg in cfgs {
191 match cfg.parse::<CfgFlag>() {
192 Ok(it) => acc.push(it),
193 Err(err) => {
194 anyhow::bail!("invalid cfg from cargo-metadata: {}", err)
195 }
196 };
197 }
198 acc
199 };
200 let package_build_data =
201 res.per_package.entry(package_id.repr.clone()).or_default();
202 // cargo_metadata crate returns default (empty) path for
203 // older cargos, which is not absolute, so work around that.
204 if !out_dir.as_str().is_empty() {
205 let out_dir = AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
206 package_build_data.out_dir = Some(out_dir);
207 package_build_data.cfgs = cfgs;
208 }
209 175
210 package_build_data.envs = env; 176 let mut callback_err = None;
177 let output = stdx::process::streaming_output(
178 cmd,
179 &mut |line| {
180 if callback_err.is_some() {
181 return;
211 } 182 }
212 Message::CompilerArtifact(message) => { 183
213 progress(format!("metadata {}", message.target.name)); 184 // Copy-pasted from existing cargo_metadata. It seems like we
214 185 // should be using sered_stacker here?
215 if message.target.kind.contains(&"proc-macro".to_string()) { 186 let mut deserializer = serde_json::Deserializer::from_str(&line);
216 let package_id = message.package_id; 187 deserializer.disable_recursion_limit();
217 // Skip rmeta file 188 let message = Message::deserialize(&mut deserializer)
218 if let Some(filename) = message.filenames.iter().find(|name| is_dylib(name)) 189 .unwrap_or(Message::TextLine(line.to_string()));
219 { 190
220 let filename = AbsPathBuf::assert(PathBuf::from(&filename)); 191 match message {
221 let package_build_data = 192 Message::BuildScriptExecuted(BuildScript {
222 res.per_package.entry(package_id.repr.clone()).or_default(); 193 package_id,
223 package_build_data.proc_macro_dylib_path = Some(filename); 194 out_dir,
195 cfgs,
196 env,
197 ..
198 }) => {
199 let cfgs = {
200 let mut acc = Vec::new();
201 for cfg in cfgs {
202 match cfg.parse::<CfgFlag>() {
203 Ok(it) => acc.push(it),
204 Err(err) => {
205 callback_err = Some(anyhow::format_err!(
206 "invalid cfg from cargo-metadata: {}",
207 err
208 ));
209 return;
210 }
211 };
212 }
213 acc
214 };
215 let package_build_data =
216 res.per_package.entry(package_id.repr.clone()).or_default();
217 // cargo_metadata crate returns default (empty) path for
218 // older cargos, which is not absolute, so work around that.
219 if !out_dir.as_str().is_empty() {
220 let out_dir =
221 AbsPathBuf::assert(PathBuf::from(out_dir.into_os_string()));
222 package_build_data.out_dir = Some(out_dir);
223 package_build_data.cfgs = cfgs;
224 } 224 }
225
226 package_build_data.envs = env;
225 } 227 }
228 Message::CompilerArtifact(message) => {
229 progress(format!("metadata {}", message.target.name));
230
231 if message.target.kind.contains(&"proc-macro".to_string()) {
232 let package_id = message.package_id;
233 // Skip rmeta file
234 if let Some(filename) =
235 message.filenames.iter().find(|name| is_dylib(name))
236 {
237 let filename = AbsPathBuf::assert(PathBuf::from(&filename));
238 let package_build_data =
239 res.per_package.entry(package_id.repr.clone()).or_default();
240 package_build_data.proc_macro_dylib_path = Some(filename);
241 }
242 }
243 }
244 Message::CompilerMessage(message) => {
245 progress(message.target.name.clone());
246 }
247 Message::BuildFinished(_) => {}
248 Message::TextLine(_) => {}
249 _ => {}
226 } 250 }
227 Message::CompilerMessage(message) => { 251 },
228 progress(message.target.name.clone()); 252 &mut |_| (),
229 } 253 )?;
230 Message::BuildFinished(_) => {}
231 Message::TextLine(_) => {}
232 _ => {}
233 }
234 }
235 254
236 for package in packages { 255 for package in packages {
237 let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default(); 256 let package_build_data = res.per_package.entry(package.id.repr.clone()).or_default();
@@ -244,7 +263,6 @@ impl WorkspaceBuildData {
244 } 263 }
245 } 264 }
246 265
247 let output = child.into_inner().wait_with_output()?;
248 if !output.status.success() { 266 if !output.status.success() {
249 let mut stderr = String::from_utf8(output.stderr).unwrap_or_default(); 267 let mut stderr = String::from_utf8(output.stderr).unwrap_or_default();
250 if stderr.is_empty() { 268 if stderr.is_empty() {
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 7ddea22c8..1109d2daf 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -17,7 +17,7 @@ use ide_db::helpers::{
17}; 17};
18use lsp_types::{ClientCapabilities, MarkupKind}; 18use lsp_types::{ClientCapabilities, MarkupKind};
19use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource}; 19use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource};
20use rustc_hash::FxHashSet; 20use rustc_hash::{FxHashMap, FxHashSet};
21use serde::{de::DeserializeOwned, Deserialize}; 21use serde::{de::DeserializeOwned, Deserialize};
22use vfs::AbsPathBuf; 22use vfs::AbsPathBuf;
23 23
@@ -99,6 +99,9 @@ config_data! {
99 diagnostics_enableExperimental: bool = "true", 99 diagnostics_enableExperimental: bool = "true",
100 /// List of rust-analyzer diagnostics to disable. 100 /// List of rust-analyzer diagnostics to disable.
101 diagnostics_disabled: FxHashSet<String> = "[]", 101 diagnostics_disabled: FxHashSet<String> = "[]",
102 /// Map of prefixes to be substituted when parsing diagnostic file paths.
103 /// This should be the reverse mapping of what is passed to `rustc` as `--remap-path-prefix`.
104 diagnostics_remapPrefix: FxHashMap<String, String> = "{}",
102 /// List of warnings that should be displayed with info severity. 105 /// List of warnings that should be displayed with info severity.
103 /// 106 ///
104 /// The warnings will be indicated by a blue squiggly underline in code 107 /// The warnings will be indicated by a blue squiggly underline in code
@@ -474,6 +477,7 @@ impl Config {
474 } 477 }
475 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig { 478 pub fn diagnostics_map(&self) -> DiagnosticsMapConfig {
476 DiagnosticsMapConfig { 479 DiagnosticsMapConfig {
480 remap_prefix: self.data.diagnostics_remapPrefix.clone(),
477 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(), 481 warnings_as_info: self.data.diagnostics_warningsAsInfo.clone(),
478 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(), 482 warnings_as_hint: self.data.diagnostics_warningsAsHint.clone(),
479 } 483 }
@@ -835,6 +839,9 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json
835 "items": { "type": "string" }, 839 "items": { "type": "string" },
836 "uniqueItems": true, 840 "uniqueItems": true,
837 }, 841 },
842 "FxHashMap<String, String>" => set! {
843 "type": "object",
844 },
838 "Option<usize>" => set! { 845 "Option<usize>" => set! {
839 "type": ["null", "integer"], 846 "type": ["null", "integer"],
840 "minimum": 0, 847 "minimum": 0,
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index f01548c50..d4b9db362 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -12,6 +12,7 @@ pub(crate) type CheckFixes = Arc<FxHashMap<FileId, Vec<Fix>>>;
12 12
13#[derive(Debug, Default, Clone)] 13#[derive(Debug, Default, Clone)]
14pub struct DiagnosticsMapConfig { 14pub struct DiagnosticsMapConfig {
15 pub remap_prefix: FxHashMap<String, String>,
15 pub warnings_as_info: Vec<String>, 16 pub warnings_as_info: Vec<String>,
16 pub warnings_as_hint: Vec<String>, 17 pub warnings_as_hint: Vec<String>,
17} 18}
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index ca18997e4..82dd0da9a 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -1,6 +1,9 @@
1//! This module provides the functionality needed to convert diagnostics from 1//! This module provides the functionality needed to convert diagnostics from
2//! `cargo check` json format to the LSP diagnostic format. 2//! `cargo check` json format to the LSP diagnostic format.
3use std::{collections::HashMap, path::Path}; 3use std::{
4 collections::HashMap,
5 path::{Path, PathBuf},
6};
4 7
5use flycheck::{DiagnosticLevel, DiagnosticSpan}; 8use flycheck::{DiagnosticLevel, DiagnosticSpan};
6use stdx::format_to; 9use stdx::format_to;
@@ -41,8 +44,12 @@ fn is_dummy_macro_file(file_name: &str) -> bool {
41} 44}
42 45
43/// Converts a Rust span to a LSP location 46/// Converts a Rust span to a LSP location
44fn location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location { 47fn location(
45 let file_name = workspace_root.join(&span.file_name); 48 config: &DiagnosticsMapConfig,
49 workspace_root: &Path,
50 span: &DiagnosticSpan,
51) -> lsp_types::Location {
52 let file_name = resolve_path(config, workspace_root, &span.file_name);
46 let uri = url_from_abs_path(&file_name); 53 let uri = url_from_abs_path(&file_name);
47 54
48 // FIXME: this doesn't handle UTF16 offsets correctly 55 // FIXME: this doesn't handle UTF16 offsets correctly
@@ -58,32 +65,50 @@ fn location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location
58/// 65///
59/// This takes locations pointing into the standard library, or generally outside the current 66/// This takes locations pointing into the standard library, or generally outside the current
60/// workspace into account and tries to avoid those, in case macros are involved. 67/// workspace into account and tries to avoid those, in case macros are involved.
61fn primary_location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location { 68fn primary_location(
69 config: &DiagnosticsMapConfig,
70 workspace_root: &Path,
71 span: &DiagnosticSpan,
72) -> lsp_types::Location {
62 let span_stack = std::iter::successors(Some(span), |span| Some(&span.expansion.as_ref()?.span)); 73 let span_stack = std::iter::successors(Some(span), |span| Some(&span.expansion.as_ref()?.span));
63 for span in span_stack.clone() { 74 for span in span_stack.clone() {
64 let abs_path = workspace_root.join(&span.file_name); 75 let abs_path = resolve_path(config, workspace_root, &span.file_name);
65 if !is_dummy_macro_file(&span.file_name) && abs_path.starts_with(workspace_root) { 76 if !is_dummy_macro_file(&span.file_name) && abs_path.starts_with(workspace_root) {
66 return location(workspace_root, span); 77 return location(config, workspace_root, span);
67 } 78 }
68 } 79 }
69 80
70 // Fall back to the outermost macro invocation if no suitable span comes up. 81 // Fall back to the outermost macro invocation if no suitable span comes up.
71 let last_span = span_stack.last().unwrap(); 82 let last_span = span_stack.last().unwrap();
72 location(workspace_root, last_span) 83 location(config, workspace_root, last_span)
73} 84}
74 85
75/// Converts a secondary Rust span to a LSP related information 86/// Converts a secondary Rust span to a LSP related information
76/// 87///
77/// If the span is unlabelled this will return `None`. 88/// If the span is unlabelled this will return `None`.
78fn diagnostic_related_information( 89fn diagnostic_related_information(
90 config: &DiagnosticsMapConfig,
79 workspace_root: &Path, 91 workspace_root: &Path,
80 span: &DiagnosticSpan, 92 span: &DiagnosticSpan,
81) -> Option<lsp_types::DiagnosticRelatedInformation> { 93) -> Option<lsp_types::DiagnosticRelatedInformation> {
82 let message = span.label.clone()?; 94 let message = span.label.clone()?;
83 let location = location(workspace_root, span); 95 let location = location(config, workspace_root, span);
84 Some(lsp_types::DiagnosticRelatedInformation { location, message }) 96 Some(lsp_types::DiagnosticRelatedInformation { location, message })
85} 97}
86 98
99/// Resolves paths applying any matching path prefix remappings, and then
100/// joining the path to the workspace root.
101fn resolve_path(config: &DiagnosticsMapConfig, workspace_root: &Path, file_name: &str) -> PathBuf {
102 match config
103 .remap_prefix
104 .iter()
105 .find_map(|(from, to)| file_name.strip_prefix(from).map(|file_name| (to, file_name)))
106 {
107 Some((to, file_name)) => workspace_root.join(format!("{}{}", to, file_name)),
108 None => workspace_root.join(file_name),
109 }
110}
111
87struct SubDiagnostic { 112struct SubDiagnostic {
88 related: lsp_types::DiagnosticRelatedInformation, 113 related: lsp_types::DiagnosticRelatedInformation,
89 suggested_fix: Option<lsp_ext::CodeAction>, 114 suggested_fix: Option<lsp_ext::CodeAction>,
@@ -95,6 +120,7 @@ enum MappedRustChildDiagnostic {
95} 120}
96 121
97fn map_rust_child_diagnostic( 122fn map_rust_child_diagnostic(
123 config: &DiagnosticsMapConfig,
98 workspace_root: &Path, 124 workspace_root: &Path,
99 rd: &flycheck::Diagnostic, 125 rd: &flycheck::Diagnostic,
100) -> MappedRustChildDiagnostic { 126) -> MappedRustChildDiagnostic {
@@ -108,7 +134,7 @@ fn map_rust_child_diagnostic(
108 let mut edit_map: HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>> = HashMap::new(); 134 let mut edit_map: HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>> = HashMap::new();
109 for &span in &spans { 135 for &span in &spans {
110 if let Some(suggested_replacement) = &span.suggested_replacement { 136 if let Some(suggested_replacement) = &span.suggested_replacement {
111 let location = location(workspace_root, span); 137 let location = location(config, workspace_root, span);
112 let edit = lsp_types::TextEdit::new(location.range, suggested_replacement.clone()); 138 let edit = lsp_types::TextEdit::new(location.range, suggested_replacement.clone());
113 edit_map.entry(location.uri).or_default().push(edit); 139 edit_map.entry(location.uri).or_default().push(edit);
114 } 140 }
@@ -117,7 +143,7 @@ fn map_rust_child_diagnostic(
117 if edit_map.is_empty() { 143 if edit_map.is_empty() {
118 MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic { 144 MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic {
119 related: lsp_types::DiagnosticRelatedInformation { 145 related: lsp_types::DiagnosticRelatedInformation {
120 location: location(workspace_root, spans[0]), 146 location: location(config, workspace_root, spans[0]),
121 message: rd.message.clone(), 147 message: rd.message.clone(),
122 }, 148 },
123 suggested_fix: None, 149 suggested_fix: None,
@@ -125,7 +151,7 @@ fn map_rust_child_diagnostic(
125 } else { 151 } else {
126 MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic { 152 MappedRustChildDiagnostic::SubDiagnostic(SubDiagnostic {
127 related: lsp_types::DiagnosticRelatedInformation { 153 related: lsp_types::DiagnosticRelatedInformation {
128 location: location(workspace_root, spans[0]), 154 location: location(config, workspace_root, spans[0]),
129 message: rd.message.clone(), 155 message: rd.message.clone(),
130 }, 156 },
131 suggested_fix: Some(lsp_ext::CodeAction { 157 suggested_fix: Some(lsp_ext::CodeAction {
@@ -190,7 +216,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
190 let mut tags = Vec::new(); 216 let mut tags = Vec::new();
191 217
192 for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { 218 for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
193 let related = diagnostic_related_information(workspace_root, secondary_span); 219 let related = diagnostic_related_information(config, workspace_root, secondary_span);
194 if let Some(related) = related { 220 if let Some(related) = related {
195 subdiagnostics.push(SubDiagnostic { related, suggested_fix: None }); 221 subdiagnostics.push(SubDiagnostic { related, suggested_fix: None });
196 } 222 }
@@ -198,7 +224,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
198 224
199 let mut message = rd.message.clone(); 225 let mut message = rd.message.clone();
200 for child in &rd.children { 226 for child in &rd.children {
201 let child = map_rust_child_diagnostic(workspace_root, &child); 227 let child = map_rust_child_diagnostic(config, workspace_root, &child);
202 match child { 228 match child {
203 MappedRustChildDiagnostic::SubDiagnostic(sub) => { 229 MappedRustChildDiagnostic::SubDiagnostic(sub) => {
204 subdiagnostics.push(sub); 230 subdiagnostics.push(sub);
@@ -242,7 +268,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
242 primary_spans 268 primary_spans
243 .iter() 269 .iter()
244 .flat_map(|primary_span| { 270 .flat_map(|primary_span| {
245 let primary_location = primary_location(workspace_root, &primary_span); 271 let primary_location = primary_location(config, workspace_root, &primary_span);
246 272
247 let mut message = message.clone(); 273 let mut message = message.clone();
248 if needs_primary_span_label { 274 if needs_primary_span_label {
@@ -272,7 +298,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
272 // generated that code. 298 // generated that code.
273 let is_in_macro_call = i != 0; 299 let is_in_macro_call = i != 0;
274 300
275 let secondary_location = location(workspace_root, &span); 301 let secondary_location = location(config, workspace_root, &span);
276 if secondary_location == primary_location { 302 if secondary_location == primary_location {
277 continue; 303 continue;
278 } 304 }
diff --git a/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index e51532d88..0ae2758cc 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/crates/rust-analyzer/src/reload.rs
@@ -322,6 +322,7 @@ impl GlobalState {
322 let loader = &mut self.loader; 322 let loader = &mut self.loader;
323 let mem_docs = &self.mem_docs; 323 let mem_docs = &self.mem_docs;
324 let mut load = |path: &AbsPath| { 324 let mut load = |path: &AbsPath| {
325 let _p = profile::span("GlobalState::load");
325 let vfs_path = vfs::VfsPath::from(path.to_path_buf()); 326 let vfs_path = vfs::VfsPath::from(path.to_path_buf());
326 if !mem_docs.contains_key(&vfs_path) { 327 if !mem_docs.contains_key(&vfs_path) {
327 let contents = loader.handle.load_sync(path); 328 let contents = loader.handle.load_sync(path);
diff --git a/crates/stdx/Cargo.toml b/crates/stdx/Cargo.toml
index d28b5e658..f78c5da7c 100644
--- a/crates/stdx/Cargo.toml
+++ b/crates/stdx/Cargo.toml
@@ -10,10 +10,15 @@ edition = "2018"
10doctest = false 10doctest = false
11 11
12[dependencies] 12[dependencies]
13libc = "0.2.93"
13backtrace = { version = "0.3.44", optional = true } 14backtrace = { version = "0.3.44", optional = true }
14always-assert = { version = "0.1.2", features = ["log"] } 15always-assert = { version = "0.1.2", features = ["log"] }
15# Think twice before adding anything here 16# Think twice before adding anything here
16 17
18[target.'cfg(windows)'.dependencies]
19miow = "0.3.6"
20winapi = "0.3.9"
21
17[features] 22[features]
18# Uncomment to enable for the whole crate graph 23# Uncomment to enable for the whole crate graph
19# default = [ "backtrace" ] 24# default = [ "backtrace" ]
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index b0a18d58d..857567a85 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -1,7 +1,8 @@
1//! Missing batteries for standard libraries. 1//! Missing batteries for standard libraries.
2use std::{cmp::Ordering, ops, process, time::Instant}; 2use std::{cmp::Ordering, ops, time::Instant};
3 3
4mod macros; 4mod macros;
5pub mod process;
5pub mod panic_context; 6pub mod panic_context;
6 7
7pub use always_assert::{always, never}; 8pub use always_assert::{always, never};
@@ -178,18 +179,30 @@ where
178 start..start + len 179 start..start + len
179} 180}
180 181
182pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
183 struct D<F: FnOnce()>(Option<F>);
184 impl<F: FnOnce()> Drop for D<F> {
185 fn drop(&mut self) {
186 if let Some(f) = self.0.take() {
187 f()
188 }
189 }
190 }
191 D(Some(f))
192}
193
181#[repr(transparent)] 194#[repr(transparent)]
182pub struct JodChild(pub process::Child); 195pub struct JodChild(pub std::process::Child);
183 196
184impl ops::Deref for JodChild { 197impl ops::Deref for JodChild {
185 type Target = process::Child; 198 type Target = std::process::Child;
186 fn deref(&self) -> &process::Child { 199 fn deref(&self) -> &std::process::Child {
187 &self.0 200 &self.0
188 } 201 }
189} 202}
190 203
191impl ops::DerefMut for JodChild { 204impl ops::DerefMut for JodChild {
192 fn deref_mut(&mut self) -> &mut process::Child { 205 fn deref_mut(&mut self) -> &mut std::process::Child {
193 &mut self.0 206 &mut self.0
194 } 207 }
195} 208}
@@ -202,9 +215,9 @@ impl Drop for JodChild {
202} 215}
203 216
204impl JodChild { 217impl JodChild {
205 pub fn into_inner(self) -> process::Child { 218 pub fn into_inner(self) -> std::process::Child {
206 // SAFETY: repr transparent 219 // SAFETY: repr transparent
207 unsafe { std::mem::transmute::<JodChild, process::Child>(self) } 220 unsafe { std::mem::transmute::<JodChild, std::process::Child>(self) }
208 } 221 }
209} 222}
210 223
diff --git a/crates/stdx/src/process.rs b/crates/stdx/src/process.rs
new file mode 100644
index 000000000..b0fa12f76
--- /dev/null
+++ b/crates/stdx/src/process.rs
@@ -0,0 +1,238 @@
1//! Read both stdout and stderr of child without deadlocks.
2//!
3//! https://github.com/rust-lang/cargo/blob/905af549966f23a9288e9993a85d1249a5436556/crates/cargo-util/src/read2.rs
4//! https://github.com/rust-lang/cargo/blob/58a961314437258065e23cb6316dfc121d96fb71/crates/cargo-util/src/process_builder.rs#L231
5
6use std::{
7 io,
8 process::{Command, Output, Stdio},
9};
10
11pub fn streaming_output(
12 mut cmd: Command,
13 on_stdout_line: &mut dyn FnMut(&str),
14 on_stderr_line: &mut dyn FnMut(&str),
15) -> io::Result<Output> {
16 let mut stdout = Vec::new();
17 let mut stderr = Vec::new();
18
19 let cmd = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null());
20
21 let status = {
22 let mut child = cmd.spawn()?;
23 let out = child.stdout.take().unwrap();
24 let err = child.stderr.take().unwrap();
25 imp::read2(out, err, &mut |is_out, data, eof| {
26 let idx = if eof {
27 data.len()
28 } else {
29 match data.iter().rposition(|b| *b == b'\n') {
30 Some(i) => i + 1,
31 None => return,
32 }
33 };
34 {
35 // scope for new_lines
36 let new_lines = {
37 let dst = if is_out { &mut stdout } else { &mut stderr };
38 let start = dst.len();
39 let data = data.drain(..idx);
40 dst.extend(data);
41 &dst[start..]
42 };
43 for line in String::from_utf8_lossy(new_lines).lines() {
44 if is_out {
45 on_stdout_line(line)
46 } else {
47 on_stderr_line(line)
48 }
49 }
50 }
51 })?;
52 child.wait()?
53 };
54
55 Ok(Output { status, stdout, stderr })
56}
57
58#[cfg(unix)]
59mod imp {
60 use std::{
61 io::{self, prelude::*},
62 mem,
63 os::unix::prelude::*,
64 process::{ChildStderr, ChildStdout},
65 };
66
67 pub(crate) fn read2(
68 mut out_pipe: ChildStdout,
69 mut err_pipe: ChildStderr,
70 data: &mut dyn FnMut(bool, &mut Vec<u8>, bool),
71 ) -> io::Result<()> {
72 unsafe {
73 libc::fcntl(out_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
74 libc::fcntl(err_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
75 }
76
77 let mut out_done = false;
78 let mut err_done = false;
79 let mut out = Vec::new();
80 let mut err = Vec::new();
81
82 let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() };
83 fds[0].fd = out_pipe.as_raw_fd();
84 fds[0].events = libc::POLLIN;
85 fds[1].fd = err_pipe.as_raw_fd();
86 fds[1].events = libc::POLLIN;
87 let mut nfds = 2;
88 let mut errfd = 1;
89
90 while nfds > 0 {
91 // wait for either pipe to become readable using `select`
92 let r = unsafe { libc::poll(fds.as_mut_ptr(), nfds, -1) };
93 if r == -1 {
94 let err = io::Error::last_os_error();
95 if err.kind() == io::ErrorKind::Interrupted {
96 continue;
97 }
98 return Err(err);
99 }
100
101 // Read as much as we can from each pipe, ignoring EWOULDBLOCK or
102 // EAGAIN. If we hit EOF, then this will happen because the underlying
103 // reader will return Ok(0), in which case we'll see `Ok` ourselves. In
104 // this case we flip the other fd back into blocking mode and read
105 // whatever's leftover on that file descriptor.
106 let handle = |res: io::Result<_>| match res {
107 Ok(_) => Ok(true),
108 Err(e) => {
109 if e.kind() == io::ErrorKind::WouldBlock {
110 Ok(false)
111 } else {
112 Err(e)
113 }
114 }
115 };
116 if !err_done && fds[errfd].revents != 0 && handle(err_pipe.read_to_end(&mut err))? {
117 err_done = true;
118 nfds -= 1;
119 }
120 data(false, &mut err, err_done);
121 if !out_done && fds[0].revents != 0 && handle(out_pipe.read_to_end(&mut out))? {
122 out_done = true;
123 fds[0].fd = err_pipe.as_raw_fd();
124 errfd = 0;
125 nfds -= 1;
126 }
127 data(true, &mut out, out_done);
128 }
129 Ok(())
130 }
131}
132
133#[cfg(windows)]
134mod imp {
135 use std::{
136 io,
137 os::windows::prelude::*,
138 process::{ChildStderr, ChildStdout},
139 slice,
140 };
141
142 use miow::{
143 iocp::{CompletionPort, CompletionStatus},
144 pipe::NamedPipe,
145 Overlapped,
146 };
147 use winapi::shared::winerror::ERROR_BROKEN_PIPE;
148
149 struct Pipe<'a> {
150 dst: &'a mut Vec<u8>,
151 overlapped: Overlapped,
152 pipe: NamedPipe,
153 done: bool,
154 }
155
156 pub(crate) fn read2(
157 out_pipe: ChildStdout,
158 err_pipe: ChildStderr,
159 data: &mut dyn FnMut(bool, &mut Vec<u8>, bool),
160 ) -> io::Result<()> {
161 let mut out = Vec::new();
162 let mut err = Vec::new();
163
164 let port = CompletionPort::new(1)?;
165 port.add_handle(0, &out_pipe)?;
166 port.add_handle(1, &err_pipe)?;
167
168 unsafe {
169 let mut out_pipe = Pipe::new(out_pipe, &mut out);
170 let mut err_pipe = Pipe::new(err_pipe, &mut err);
171
172 out_pipe.read()?;
173 err_pipe.read()?;
174
175 let mut status = [CompletionStatus::zero(), CompletionStatus::zero()];
176
177 while !out_pipe.done || !err_pipe.done {
178 for status in port.get_many(&mut status, None)? {
179 if status.token() == 0 {
180 out_pipe.complete(status);
181 data(true, out_pipe.dst, out_pipe.done);
182 out_pipe.read()?;
183 } else {
184 err_pipe.complete(status);
185 data(false, err_pipe.dst, err_pipe.done);
186 err_pipe.read()?;
187 }
188 }
189 }
190
191 Ok(())
192 }
193 }
194
195 impl<'a> Pipe<'a> {
196 unsafe fn new<P: IntoRawHandle>(p: P, dst: &'a mut Vec<u8>) -> Pipe<'a> {
197 Pipe {
198 dst,
199 pipe: NamedPipe::from_raw_handle(p.into_raw_handle()),
200 overlapped: Overlapped::zero(),
201 done: false,
202 }
203 }
204
205 unsafe fn read(&mut self) -> io::Result<()> {
206 let dst = slice_to_end(self.dst);
207 match self.pipe.read_overlapped(dst, self.overlapped.raw()) {
208 Ok(_) => Ok(()),
209 Err(e) => {
210 if e.raw_os_error() == Some(ERROR_BROKEN_PIPE as i32) {
211 self.done = true;
212 Ok(())
213 } else {
214 Err(e)
215 }
216 }
217 }
218 }
219
220 unsafe fn complete(&mut self, status: &CompletionStatus) {
221 let prev = self.dst.len();
222 self.dst.set_len(prev + status.bytes_transferred() as usize);
223 if status.bytes_transferred() == 0 {
224 self.done = true;
225 }
226 }
227 }
228
229 unsafe fn slice_to_end(v: &mut Vec<u8>) -> &mut [u8] {
230 if v.capacity() == 0 {
231 v.reserve(16);
232 }
233 if v.capacity() == v.len() {
234 v.reserve(1);
235 }
236 slice::from_raw_parts_mut(v.as_mut_ptr().add(v.len()), v.capacity() - v.len())
237 }
238}
diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml
index a8c1a8075..556f80882 100644
--- a/crates/syntax/Cargo.toml
+++ b/crates/syntax/Cargo.toml
@@ -14,7 +14,7 @@ doctest = false
14cov-mark = { version = "1.1", features = ["thread-local"] } 14cov-mark = { version = "1.1", features = ["thread-local"] }
15itertools = "0.10.0" 15itertools = "0.10.0"
16rowan = "=0.13.0-pre.3" 16rowan = "=0.13.0-pre.3"
17rustc_lexer = { version = "714.0.0", package = "rustc-ap-rustc_lexer" } 17rustc_lexer = { version = "716.0.0", package = "rustc-ap-rustc_lexer" }
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19arrayvec = "0.7" 19arrayvec = "0.7"
20once_cell = "1.3.1" 20once_cell = "1.3.1"
diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs
index a153a9e1c..c9229c4e0 100644
--- a/crates/syntax/src/algo.rs
+++ b/crates/syntax/src/algo.rs
@@ -342,10 +342,10 @@ enum InsertPos {
342 342
343#[derive(Default)] 343#[derive(Default)]
344pub struct SyntaxRewriter<'a> { 344pub struct SyntaxRewriter<'a> {
345 f: Option<Box<dyn Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a>>,
346 //FIXME: add debug_assertions that all elements are in fact from the same file. 345 //FIXME: add debug_assertions that all elements are in fact from the same file.
347 replacements: FxHashMap<SyntaxElement, Replacement>, 346 replacements: FxHashMap<SyntaxElement, Replacement>,
348 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>, 347 insertions: IndexMap<InsertPos, Vec<SyntaxElement>>,
348 _pd: std::marker::PhantomData<&'a ()>,
349} 349}
350 350
351impl fmt::Debug for SyntaxRewriter<'_> { 351impl fmt::Debug for SyntaxRewriter<'_> {
@@ -357,14 +357,7 @@ impl fmt::Debug for SyntaxRewriter<'_> {
357 } 357 }
358} 358}
359 359
360impl<'a> SyntaxRewriter<'a> { 360impl SyntaxRewriter<'_> {
361 pub fn from_fn(f: impl Fn(&SyntaxElement) -> Option<SyntaxElement> + 'a) -> SyntaxRewriter<'a> {
362 SyntaxRewriter {
363 f: Some(Box::new(f)),
364 replacements: FxHashMap::default(),
365 insertions: IndexMap::default(),
366 }
367 }
368 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) { 361 pub fn delete<T: Clone + Into<SyntaxElement>>(&mut self, what: &T) {
369 let what = what.clone().into(); 362 let what = what.clone().into();
370 let replacement = Replacement::Delete; 363 let replacement = Replacement::Delete;
@@ -470,7 +463,7 @@ impl<'a> SyntaxRewriter<'a> {
470 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode { 463 pub fn rewrite(&self, node: &SyntaxNode) -> SyntaxNode {
471 let _p = profile::span("rewrite"); 464 let _p = profile::span("rewrite");
472 465
473 if self.f.is_none() && self.replacements.is_empty() && self.insertions.is_empty() { 466 if self.replacements.is_empty() && self.insertions.is_empty() {
474 return node.clone(); 467 return node.clone();
475 } 468 }
476 let green = self.rewrite_children(node); 469 let green = self.rewrite_children(node);
@@ -495,7 +488,6 @@ impl<'a> SyntaxRewriter<'a> {
495 } 488 }
496 } 489 }
497 490
498 assert!(self.f.is_none());
499 self.replacements 491 self.replacements
500 .keys() 492 .keys()
501 .filter_map(element_to_node_or_parent) 493 .filter_map(element_to_node_or_parent)
@@ -510,10 +502,6 @@ impl<'a> SyntaxRewriter<'a> {
510 } 502 }
511 503
512 fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> { 504 fn replacement(&self, element: &SyntaxElement) -> Option<Replacement> {
513 if let Some(f) = &self.f {
514 assert!(self.replacements.is_empty());
515 return f(element).map(Replacement::Single);
516 }
517 self.replacements.get(element).cloned() 505 self.replacements.get(element).cloned()
518 } 506 }
519 507
@@ -574,7 +562,6 @@ fn element_to_green(element: SyntaxElement) -> NodeOrToken<rowan::GreenNode, row
574 562
575impl ops::AddAssign for SyntaxRewriter<'_> { 563impl ops::AddAssign for SyntaxRewriter<'_> {
576 fn add_assign(&mut self, rhs: SyntaxRewriter) { 564 fn add_assign(&mut self, rhs: SyntaxRewriter) {
577 assert!(rhs.f.is_none());
578 self.replacements.extend(rhs.replacements); 565 self.replacements.extend(rhs.replacements);
579 for (pos, insertions) in rhs.insertions.into_iter() { 566 for (pos, insertions) in rhs.insertions.into_iter() {
580 match self.insertions.entry(pos) { 567 match self.insertions.entry(pos) {
diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs
index 882e9fa09..42da09606 100644
--- a/crates/syntax/src/ast/make.rs
+++ b/crates/syntax/src/ast/make.rs
@@ -137,6 +137,17 @@ pub fn use_(visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast:
137 ast_from_text(&format!("{}use {};", visibility, use_tree)) 137 ast_from_text(&format!("{}use {};", visibility, use_tree))
138} 138}
139 139
140pub fn record_expr(path: ast::Path, fields: ast::RecordExprFieldList) -> ast::RecordExpr {
141 ast_from_text(&format!("fn f() {{ {} {} }}", path, fields))
142}
143
144pub fn record_expr_field_list(
145 fields: impl IntoIterator<Item = ast::RecordExprField>,
146) -> ast::RecordExprFieldList {
147 let fields = fields.into_iter().join(", ");
148 ast_from_text(&format!("fn f() {{ S {{ {} }} }}", fields))
149}
150
140pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField { 151pub fn record_expr_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordExprField {
141 return match expr { 152 return match expr {
142 Some(expr) => from_text(&format!("{}: {}", name, expr)), 153 Some(expr) => from_text(&format!("{}: {}", name, expr)),
@@ -294,6 +305,14 @@ pub fn wildcard_pat() -> ast::WildcardPat {
294 } 305 }
295} 306}
296 307
308pub fn literal_pat(lit: &str) -> ast::LiteralPat {
309 return from_text(lit);
310
311 fn from_text(text: &str) -> ast::LiteralPat {
312 ast_from_text(&format!("fn f() {{ match x {{ {} => {{}} }} }}", text))
313 }
314}
315
297/// Creates a tuple of patterns from an iterator of patterns. 316/// Creates a tuple of patterns from an iterator of patterns.
298/// 317///
299/// Invariant: `pats` must be length > 0 318/// Invariant: `pats` must be length > 0
@@ -331,6 +350,21 @@ pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) ->
331 } 350 }
332} 351}
333 352
353pub fn record_pat_with_fields(path: ast::Path, fields: ast::RecordPatFieldList) -> ast::RecordPat {
354 ast_from_text(&format!("fn f({} {}: ()))", path, fields))
355}
356
357pub fn record_pat_field_list(
358 fields: impl IntoIterator<Item = ast::RecordPatField>,
359) -> ast::RecordPatFieldList {
360 let fields = fields.into_iter().join(", ");
361 ast_from_text(&format!("fn f(S {{ {} }}: ()))", fields))
362}
363
364pub fn record_pat_field(name_ref: ast::NameRef, pat: ast::Pat) -> ast::RecordPatField {
365 ast_from_text(&format!("fn f(S {{ {}: {} }}: ()))", name_ref, pat))
366}
367
334/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise. 368/// Returns a `BindPat` if the path has just one segment, a `PathPat` otherwise.
335pub fn path_pat(path: ast::Path) -> ast::Pat { 369pub fn path_pat(path: ast::Path) -> ast::Pat {
336 return from_text(&path.to_string()); 370 return from_text(&path.to_string());
diff --git a/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast
index 925409bdf..e9202a612 100644
--- a/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast
+++ b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rast
@@ -1,5 +1,5 @@
1SOURCE_FILE@0..63 1SOURCE_FILE@0..102
2 FN@0..62 2 FN@0..101
3 [email protected] "fn" 3 [email protected] "fn"
4 [email protected] " " 4 [email protected] " "
5 [email protected] 5 [email protected]
@@ -8,7 +8,7 @@ [email protected]
8 [email protected] "(" 8 [email protected] "("
9 [email protected] ")" 9 [email protected] ")"
10 [email protected] " " 10 [email protected] " "
11 BLOCK_EXPR@9..62 11 BLOCK_EXPR@9..101
12 [email protected] "{" 12 [email protected] "{"
13 [email protected] "\n " 13 [email protected] "\n "
14 [email protected] 14 [email protected]
@@ -70,6 +70,52 @@ [email protected]
70 [email protected] "(" 70 [email protected] "("
71 [email protected] ")" 71 [email protected] ")"
72 [email protected] ";" 72 [email protected] ";"
73 [email protected] "\n" 73 [email protected] "\n "
74 [email protected] "}" 74 [email protected]
75 [email protected] "\n" 75 [email protected] "let"
76 [email protected] " "
77 [email protected]
78 [email protected]
79 [email protected]
80 [email protected]
81 [email protected] "S"
82 [email protected] " "
83 [email protected]
84 [email protected] "{"
85 [email protected] " "
86 [email protected]
87 [email protected]
88 [email protected] "#"
89 [email protected] "["
90 [email protected]
91 [email protected]
92 [email protected]
93 [email protected] "cfg"
94 [email protected]
95 [email protected] "("
96 [email protected] "any"
97 [email protected]
98 [email protected] "("
99 [email protected] ")"
100 [email protected] ")"
101 [email protected] "]"
102 [email protected] " "
103 [email protected]
104 [email protected] "x"
105 [email protected] ":"
106 [email protected] " "
107 [email protected]
108 [email protected]
109 [email protected] "1"
110 [email protected] " "
111 [email protected] "}"
112 [email protected] " "
113 [email protected] "="
114 [email protected] " "
115 [email protected]
116 [email protected] "("
117 [email protected] ")"
118 [email protected] ";"
119 [email protected] "\n"
120 [email protected] "}"
121 [email protected] "\n"
diff --git a/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs
index 26b1d5f89..53cfdc22d 100644
--- a/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs
+++ b/crates/syntax/test_data/parser/inline/ok/0145_record_pat_field.rs
@@ -1,4 +1,5 @@
1fn foo() { 1fn foo() {
2 let S { 0: 1 } = (); 2 let S { 0: 1 } = ();
3 let S { x: 1 } = (); 3 let S { x: 1 } = ();
4 let S { #[cfg(any())] x: 1 } = ();
4} 5}