diff options
32 files changed, 1239 insertions, 765 deletions
diff --git a/Cargo.lock b/Cargo.lock index b890b6e19..d470d84f2 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1191,9 +1191,9 @@ dependencies = [ | |||
1191 | 1191 | ||
1192 | [[package]] | 1192 | [[package]] |
1193 | name = "pulldown-cmark" | 1193 | name = "pulldown-cmark" |
1194 | version = "0.7.2" | 1194 | version = "0.8.0" |
1195 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1195 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1196 | checksum = "ca36dea94d187597e104a5c8e4b07576a8a45aa5db48a65e12940d3eb7461f55" | 1196 | checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" |
1197 | dependencies = [ | 1197 | dependencies = [ |
1198 | "bitflags", | 1198 | "bitflags", |
1199 | "memchr", | 1199 | "memchr", |
@@ -1202,9 +1202,9 @@ dependencies = [ | |||
1202 | 1202 | ||
1203 | [[package]] | 1203 | [[package]] |
1204 | name = "pulldown-cmark-to-cmark" | 1204 | name = "pulldown-cmark-to-cmark" |
1205 | version = "5.0.0" | 1205 | version = "6.0.0" |
1206 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1207 | checksum = "32accf4473121d8c0b508ca5673363703762d6cc59cf25af1df48f653346f736" | 1207 | checksum = "e8f2b9878102358ec65434fdd1a9a161f8648bb2f531acc9260e4d094c96de23" |
1208 | dependencies = [ | 1208 | dependencies = [ |
1209 | "pulldown-cmark", | 1209 | "pulldown-cmark", |
1210 | ] | 1210 | ] |
@@ -1361,11 +1361,11 @@ checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" | |||
1361 | 1361 | ||
1362 | [[package]] | 1362 | [[package]] |
1363 | name = "salsa" | 1363 | name = "salsa" |
1364 | version = "0.15.2" | 1364 | version = "0.16.0" |
1365 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1366 | checksum = "9ab29056d4fb4048a5f0d169c9b6e5526160c9ec37aded5a6879c2c9c445a8e4" | 1366 | checksum = "d8fadca2ab5de17acf66d744f4888049ca8f1bb9b8a1ab8afd9d032cc959c5dc" |
1367 | dependencies = [ | 1367 | dependencies = [ |
1368 | "crossbeam-utils 0.7.2", | 1368 | "crossbeam-utils 0.8.0", |
1369 | "indexmap", | 1369 | "indexmap", |
1370 | "lock_api", | 1370 | "lock_api", |
1371 | "log", | 1371 | "log", |
@@ -1378,9 +1378,9 @@ dependencies = [ | |||
1378 | 1378 | ||
1379 | [[package]] | 1379 | [[package]] |
1380 | name = "salsa-macros" | 1380 | name = "salsa-macros" |
1381 | version = "0.15.2" | 1381 | version = "0.16.0" |
1382 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1383 | checksum = "a1c3aec007c63c4ed4cd7a018529fb0b5575c4562575fc6a40d6cd2ae0b792ef" | 1383 | checksum = "cd3904a4ba0a9d0211816177fd34b04c7095443f8cdacd11175064fe541c8fe2" |
1384 | dependencies = [ | 1384 | dependencies = [ |
1385 | "heck", | 1385 | "heck", |
1386 | "proc-macro2", | 1386 | "proc-macro2", |
@@ -6,3 +6,4 @@ status = [ | |||
6 | "TypeScript (windows-latest)", | 6 | "TypeScript (windows-latest)", |
7 | ] | 7 | ] |
8 | delete_merged_branches = true | 8 | delete_merged_branches = true |
9 | timeout_sec = 1200 # 20 min | ||
diff --git a/crates/assists/src/handlers/add_turbo_fish.rs b/crates/assists/src/handlers/add_turbo_fish.rs index f4f997d8e..e3d84d698 100644 --- a/crates/assists/src/handlers/add_turbo_fish.rs +++ b/crates/assists/src/handlers/add_turbo_fish.rs | |||
@@ -1,4 +1,4 @@ | |||
1 | use ide_db::defs::{classify_name_ref, Definition, NameRefClass}; | 1 | use ide_db::defs::{Definition, NameRefClass}; |
2 | use syntax::{ast, AstNode, SyntaxKind, T}; | 2 | use syntax::{ast, AstNode, SyntaxKind, T}; |
3 | use test_utils::mark; | 3 | use test_utils::mark; |
4 | 4 | ||
@@ -39,7 +39,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( | |||
39 | return None; | 39 | return None; |
40 | } | 40 | } |
41 | let name_ref = ast::NameRef::cast(ident.parent())?; | 41 | let name_ref = ast::NameRef::cast(ident.parent())?; |
42 | let def = match classify_name_ref(&ctx.sema, &name_ref)? { | 42 | let def = match NameRefClass::classify(&ctx.sema, &name_ref)? { |
43 | NameRefClass::Definition(def) => def, | 43 | NameRefClass::Definition(def) => def, |
44 | NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None, | 44 | NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None, |
45 | }; | 45 | }; |
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs index d3ee98e5f..4a7059c83 100644 --- a/crates/assists/src/handlers/auto_import.rs +++ b/crates/assists/src/handlers/auto_import.rs | |||
@@ -1,23 +1,66 @@ | |||
1 | use std::collections::BTreeSet; | 1 | use syntax::ast; |
2 | |||
3 | use either::Either; | ||
4 | use hir::{ | ||
5 | AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait, | ||
6 | Type, | ||
7 | }; | ||
8 | use ide_db::{imports_locator, RootDatabase}; | ||
9 | use insert_use::ImportScope; | ||
10 | use rustc_hash::FxHashSet; | ||
11 | use syntax::{ | ||
12 | ast::{self, AstNode}, | ||
13 | SyntaxNode, | ||
14 | }; | ||
15 | 2 | ||
16 | use crate::{ | 3 | use crate::{ |
17 | utils::insert_use, utils::mod_path_to_ast, AssistContext, AssistId, AssistKind, Assists, | 4 | utils::import_assets::{ImportAssets, ImportCandidate}, |
18 | GroupLabel, | 5 | utils::{insert_use, mod_path_to_ast, ImportScope}, |
6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, | ||
19 | }; | 7 | }; |
20 | 8 | ||
9 | // Feature: Import Insertion | ||
10 | // | ||
11 | // Using the `auto-import` assist it is possible to insert missing imports for unresolved items. | ||
12 | // When inserting an import it will do so in a structured manner by keeping imports grouped, | ||
13 | // separated by a newline in the following order: | ||
14 | // | ||
15 | // - `std` and `core` | ||
16 | // - External Crates | ||
17 | // - Current Crate, paths prefixed by `crate` | ||
18 | // - Current Module, paths prefixed by `self` | ||
19 | // - Super Module, paths prefixed by `super` | ||
20 | // | ||
21 | // Example: | ||
22 | // ```rust | ||
23 | // use std::fs::File; | ||
24 | // | ||
25 | // use itertools::Itertools; | ||
26 | // use syntax::ast; | ||
27 | // | ||
28 | // use crate::utils::insert_use; | ||
29 | // | ||
30 | // use self::auto_import; | ||
31 | // | ||
32 | // use super::AssistContext; | ||
33 | // ``` | ||
34 | // | ||
35 | // .Merge Behaviour | ||
36 | // | ||
37 | // It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting. | ||
38 | // It has the following configurations: | ||
39 | // | ||
40 | // - `full`: This setting will cause auto-import to always completely merge use-trees that share the | ||
41 | // same path prefix while also merging inner trees that share the same path-prefix. This kind of | ||
42 | // nesting is only supported in Rust versions later than 1.24. | ||
43 | // - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree | ||
44 | // will only contain a nesting of single segment paths at the very end. | ||
45 | // - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple | ||
46 | // paths. | ||
47 | // | ||
48 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehaviour`. | ||
49 | // | ||
50 | // .Import Prefix | ||
51 | // | ||
52 | // The style of imports in the same crate is configurable through the `importPrefix` setting. | ||
53 | // It has the following configurations: | ||
54 | // | ||
55 | // - `by_crate`: This setting will force paths to be always absolute, starting with the `crate` | ||
56 | // prefix, unless the item is defined outside of the current crate. | ||
57 | // - `by_self`: This setting will force paths that are relative to the current module to always | ||
58 | // start with `self`. This will result in paths that always start with either `crate`, `self`, | ||
59 | // `super` or an extern crate identifier. | ||
60 | // - `plain`: This setting does not impose any restrictions in imports. | ||
61 | // | ||
62 | // In `VS Code` the configuration for this is `rust-analyzer.assist.importPrefix`. | ||
63 | |||
21 | // Assist: auto_import | 64 | // Assist: auto_import |
22 | // | 65 | // |
23 | // If the name is unresolved, provides all possible imports for it. | 66 | // If the name is unresolved, provides all possible imports for it. |
@@ -38,16 +81,24 @@ use crate::{ | |||
38 | // # pub mod std { pub mod collections { pub struct HashMap { } } } | 81 | // # pub mod std { pub mod collections { pub struct HashMap { } } } |
39 | // ``` | 82 | // ``` |
40 | pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 83 | pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
41 | let auto_import_assets = AutoImportAssets::new(ctx)?; | 84 | let import_assets = |
42 | let proposed_imports = auto_import_assets.search_for_imports(ctx); | 85 | if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { |
86 | ImportAssets::for_regular_path(path_under_caret, &ctx.sema) | ||
87 | } else if let Some(method_under_caret) = | ||
88 | ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>() | ||
89 | { | ||
90 | ImportAssets::for_method_call(method_under_caret, &ctx.sema) | ||
91 | } else { | ||
92 | None | ||
93 | }?; | ||
94 | let proposed_imports = import_assets.search_for_imports(&ctx.sema, &ctx.config.insert_use); | ||
43 | if proposed_imports.is_empty() { | 95 | if proposed_imports.is_empty() { |
44 | return None; | 96 | return None; |
45 | } | 97 | } |
46 | 98 | ||
47 | let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range; | 99 | let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range; |
48 | let group = auto_import_assets.get_import_group_message(); | 100 | let group = import_group_message(import_assets.import_candidate()); |
49 | let scope = | 101 | let scope = ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), ctx)?; |
50 | ImportScope::find_insert_use_container(&auto_import_assets.syntax_under_caret, ctx)?; | ||
51 | let syntax = scope.as_syntax_node(); | 102 | let syntax = scope.as_syntax_node(); |
52 | for import in proposed_imports { | 103 | for import in proposed_imports { |
53 | acc.add_group( | 104 | acc.add_group( |
@@ -65,227 +116,18 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
65 | Some(()) | 116 | Some(()) |
66 | } | 117 | } |
67 | 118 | ||
68 | #[derive(Debug)] | 119 | fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel { |
69 | struct AutoImportAssets { | 120 | let name = match import_candidate { |
70 | import_candidate: ImportCandidate, | 121 | ImportCandidate::UnqualifiedName(candidate) |
71 | module_with_name_to_import: Module, | 122 | | ImportCandidate::QualifierStart(candidate) => format!("Import {}", &candidate.name), |
72 | syntax_under_caret: SyntaxNode, | 123 | ImportCandidate::TraitAssocItem(candidate) => { |
73 | } | 124 | format!("Import a trait for item {}", &candidate.name) |
74 | |||
75 | impl AutoImportAssets { | ||
76 | fn new(ctx: &AssistContext) -> Option<Self> { | ||
77 | if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() { | ||
78 | Self::for_regular_path(path_under_caret, &ctx) | ||
79 | } else { | ||
80 | Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx) | ||
81 | } | 125 | } |
82 | } | 126 | ImportCandidate::TraitMethod(candidate) => { |
83 | 127 | format!("Import a trait for method {}", &candidate.name) | |
84 | fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> { | ||
85 | let syntax_under_caret = method_call.syntax().to_owned(); | ||
86 | let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; | ||
87 | Some(Self { | ||
88 | import_candidate: ImportCandidate::for_method_call(&ctx.sema, &method_call)?, | ||
89 | module_with_name_to_import, | ||
90 | syntax_under_caret, | ||
91 | }) | ||
92 | } | ||
93 | |||
94 | fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> { | ||
95 | let syntax_under_caret = path_under_caret.syntax().to_owned(); | ||
96 | if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { | ||
97 | return None; | ||
98 | } | 128 | } |
99 | 129 | }; | |
100 | let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?; | 130 | GroupLabel(name) |
101 | Some(Self { | ||
102 | import_candidate: ImportCandidate::for_regular_path(&ctx.sema, &path_under_caret)?, | ||
103 | module_with_name_to_import, | ||
104 | syntax_under_caret, | ||
105 | }) | ||
106 | } | ||
107 | |||
108 | fn get_search_query(&self) -> &str { | ||
109 | match &self.import_candidate { | ||
110 | ImportCandidate::UnqualifiedName(name) => name, | ||
111 | ImportCandidate::QualifierStart(qualifier_start) => qualifier_start, | ||
112 | ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name, | ||
113 | ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name, | ||
114 | } | ||
115 | } | ||
116 | |||
117 | fn get_import_group_message(&self) -> GroupLabel { | ||
118 | let name = match &self.import_candidate { | ||
119 | ImportCandidate::UnqualifiedName(name) => format!("Import {}", name), | ||
120 | ImportCandidate::QualifierStart(qualifier_start) => { | ||
121 | format!("Import {}", qualifier_start) | ||
122 | } | ||
123 | ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => { | ||
124 | format!("Import a trait for item {}", trait_assoc_item_name) | ||
125 | } | ||
126 | ImportCandidate::TraitMethod(_, trait_method_name) => { | ||
127 | format!("Import a trait for method {}", trait_method_name) | ||
128 | } | ||
129 | }; | ||
130 | GroupLabel(name) | ||
131 | } | ||
132 | |||
133 | fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> { | ||
134 | let _p = profile::span("auto_import::search_for_imports"); | ||
135 | let db = ctx.db(); | ||
136 | let current_crate = self.module_with_name_to_import.krate(); | ||
137 | imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query()) | ||
138 | .into_iter() | ||
139 | .filter_map(|candidate| match &self.import_candidate { | ||
140 | ImportCandidate::TraitAssocItem(assoc_item_type, _) => { | ||
141 | let located_assoc_item = match candidate { | ||
142 | Either::Left(ModuleDef::Function(located_function)) => located_function | ||
143 | .as_assoc_item(db) | ||
144 | .map(|assoc| assoc.container(db)) | ||
145 | .and_then(Self::assoc_to_trait), | ||
146 | Either::Left(ModuleDef::Const(located_const)) => located_const | ||
147 | .as_assoc_item(db) | ||
148 | .map(|assoc| assoc.container(db)) | ||
149 | .and_then(Self::assoc_to_trait), | ||
150 | _ => None, | ||
151 | }?; | ||
152 | |||
153 | let mut trait_candidates = FxHashSet::default(); | ||
154 | trait_candidates.insert(located_assoc_item.into()); | ||
155 | |||
156 | assoc_item_type | ||
157 | .iterate_path_candidates( | ||
158 | db, | ||
159 | current_crate, | ||
160 | &trait_candidates, | ||
161 | None, | ||
162 | |_, assoc| Self::assoc_to_trait(assoc.container(db)), | ||
163 | ) | ||
164 | .map(ModuleDef::from) | ||
165 | .map(Either::Left) | ||
166 | } | ||
167 | ImportCandidate::TraitMethod(function_callee, _) => { | ||
168 | let located_assoc_item = | ||
169 | if let Either::Left(ModuleDef::Function(located_function)) = candidate { | ||
170 | located_function | ||
171 | .as_assoc_item(db) | ||
172 | .map(|assoc| assoc.container(db)) | ||
173 | .and_then(Self::assoc_to_trait) | ||
174 | } else { | ||
175 | None | ||
176 | }?; | ||
177 | |||
178 | let mut trait_candidates = FxHashSet::default(); | ||
179 | trait_candidates.insert(located_assoc_item.into()); | ||
180 | |||
181 | function_callee | ||
182 | .iterate_method_candidates( | ||
183 | db, | ||
184 | current_crate, | ||
185 | &trait_candidates, | ||
186 | None, | ||
187 | |_, function| { | ||
188 | Self::assoc_to_trait(function.as_assoc_item(db)?.container(db)) | ||
189 | }, | ||
190 | ) | ||
191 | .map(ModuleDef::from) | ||
192 | .map(Either::Left) | ||
193 | } | ||
194 | _ => Some(candidate), | ||
195 | }) | ||
196 | .filter_map(|candidate| match candidate { | ||
197 | Either::Left(module_def) => self.module_with_name_to_import.find_use_path_prefixed( | ||
198 | db, | ||
199 | module_def, | ||
200 | ctx.config.insert_use.prefix_kind, | ||
201 | ), | ||
202 | Either::Right(macro_def) => self.module_with_name_to_import.find_use_path_prefixed( | ||
203 | db, | ||
204 | macro_def, | ||
205 | ctx.config.insert_use.prefix_kind, | ||
206 | ), | ||
207 | }) | ||
208 | .filter(|use_path| !use_path.segments.is_empty()) | ||
209 | .take(20) | ||
210 | .collect::<BTreeSet<_>>() | ||
211 | } | ||
212 | |||
213 | fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> { | ||
214 | if let AssocItemContainer::Trait(extracted_trait) = assoc { | ||
215 | Some(extracted_trait) | ||
216 | } else { | ||
217 | None | ||
218 | } | ||
219 | } | ||
220 | } | ||
221 | |||
222 | #[derive(Debug)] | ||
223 | enum ImportCandidate { | ||
224 | /// Simple name like 'HashMap' | ||
225 | UnqualifiedName(String), | ||
226 | /// First part of the qualified name. | ||
227 | /// For 'std::collections::HashMap', that will be 'std'. | ||
228 | QualifierStart(String), | ||
229 | /// A trait associated function (with no self parameter) or associated constant. | ||
230 | /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type | ||
231 | /// and `String` is the `test_function` | ||
232 | TraitAssocItem(Type, String), | ||
233 | /// A trait method with self parameter. | ||
234 | /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type | ||
235 | /// and `String` is the `test_method` | ||
236 | TraitMethod(Type, String), | ||
237 | } | ||
238 | |||
239 | impl ImportCandidate { | ||
240 | fn for_method_call( | ||
241 | sema: &Semantics<RootDatabase>, | ||
242 | method_call: &ast::MethodCallExpr, | ||
243 | ) -> Option<Self> { | ||
244 | if sema.resolve_method_call(method_call).is_some() { | ||
245 | return None; | ||
246 | } | ||
247 | Some(Self::TraitMethod( | ||
248 | sema.type_of_expr(&method_call.receiver()?)?, | ||
249 | method_call.name_ref()?.syntax().to_string(), | ||
250 | )) | ||
251 | } | ||
252 | |||
253 | fn for_regular_path( | ||
254 | sema: &Semantics<RootDatabase>, | ||
255 | path_under_caret: &ast::Path, | ||
256 | ) -> Option<Self> { | ||
257 | if sema.resolve_path(path_under_caret).is_some() { | ||
258 | return None; | ||
259 | } | ||
260 | |||
261 | let segment = path_under_caret.segment()?; | ||
262 | if let Some(qualifier) = path_under_caret.qualifier() { | ||
263 | let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; | ||
264 | let qualifier_start_path = | ||
265 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | ||
266 | if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) { | ||
267 | let qualifier_resolution = if qualifier_start_path == qualifier { | ||
268 | qualifier_start_resolution | ||
269 | } else { | ||
270 | sema.resolve_path(&qualifier)? | ||
271 | }; | ||
272 | if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution { | ||
273 | Some(ImportCandidate::TraitAssocItem( | ||
274 | assoc_item_path.ty(sema.db), | ||
275 | segment.syntax().to_string(), | ||
276 | )) | ||
277 | } else { | ||
278 | None | ||
279 | } | ||
280 | } else { | ||
281 | Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string())) | ||
282 | } | ||
283 | } else { | ||
284 | Some(ImportCandidate::UnqualifiedName( | ||
285 | segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(), | ||
286 | )) | ||
287 | } | ||
288 | } | ||
289 | } | 131 | } |
290 | 132 | ||
291 | #[cfg(test)] | 133 | #[cfg(test)] |
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs index d1adff972..316a58d88 100644 --- a/crates/assists/src/handlers/expand_glob_import.rs +++ b/crates/assists/src/handlers/expand_glob_import.rs | |||
@@ -1,7 +1,7 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef}; | 2 | use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef}; |
3 | use ide_db::{ | 3 | use ide_db::{ |
4 | defs::{classify_name_ref, Definition, NameRefClass}, | 4 | defs::{Definition, NameRefClass}, |
5 | search::SearchScope, | 5 | search::SearchScope, |
6 | }; | 6 | }; |
7 | use syntax::{ | 7 | use syntax::{ |
@@ -217,7 +217,7 @@ fn find_imported_defs(ctx: &AssistContext, star: SyntaxToken) -> Option<Vec<Def> | |||
217 | .flatten() | 217 | .flatten() |
218 | .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast))) | 218 | .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast))) |
219 | .flatten() | 219 | .flatten() |
220 | .filter_map(|r| match classify_name_ref(&ctx.sema, &r)? { | 220 | .filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? { |
221 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), | 221 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), |
222 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), | 222 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), |
223 | _ => None, | 223 | _ => None, |
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs index fe33cee53..fd9c9e03c 100644 --- a/crates/assists/src/handlers/merge_imports.rs +++ b/crates/assists/src/handlers/merge_imports.rs | |||
@@ -73,6 +73,20 @@ mod tests { | |||
73 | use super::*; | 73 | use super::*; |
74 | 74 | ||
75 | #[test] | 75 | #[test] |
76 | fn test_merge_equal() { | ||
77 | check_assist( | ||
78 | merge_imports, | ||
79 | r" | ||
80 | use std::fmt<|>::{Display, Debug}; | ||
81 | use std::fmt::{Display, Debug}; | ||
82 | ", | ||
83 | r" | ||
84 | use std::fmt::{Debug, Display}; | ||
85 | ", | ||
86 | ) | ||
87 | } | ||
88 | |||
89 | #[test] | ||
76 | fn test_merge_first() { | 90 | fn test_merge_first() { |
77 | check_assist( | 91 | check_assist( |
78 | merge_imports, | 92 | merge_imports, |
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs index e95b971a4..c50bc7604 100644 --- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -124,6 +124,23 @@ mod tests { | |||
124 | use super::*; | 124 | use super::*; |
125 | 125 | ||
126 | #[test] | 126 | #[test] |
127 | fn test_replace_already_imported() { | ||
128 | check_assist( | ||
129 | replace_qualified_name_with_use, | ||
130 | r"use std::fs; | ||
131 | |||
132 | fn main() { | ||
133 | std::f<|>s::Path | ||
134 | }", | ||
135 | r"use std::fs; | ||
136 | |||
137 | fn main() { | ||
138 | fs::Path | ||
139 | }", | ||
140 | ) | ||
141 | } | ||
142 | |||
143 | #[test] | ||
127 | fn test_replace_add_use_no_anchor() { | 144 | fn test_replace_add_use_no_anchor() { |
128 | check_assist( | 145 | check_assist( |
129 | replace_qualified_name_with_use, | 146 | replace_qualified_name_with_use, |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index c1847f601..b37b0d2b6 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | //! Assorted functions shared by several assists. | 1 | //! Assorted functions shared by several assists. |
2 | pub(crate) mod insert_use; | 2 | pub(crate) mod insert_use; |
3 | pub(crate) mod import_assets; | ||
3 | 4 | ||
4 | use std::{iter, ops}; | 5 | use std::{iter, ops}; |
5 | 6 | ||
diff --git a/crates/assists/src/utils/import_assets.rs b/crates/assists/src/utils/import_assets.rs new file mode 100644 index 000000000..601f51098 --- /dev/null +++ b/crates/assists/src/utils/import_assets.rs | |||
@@ -0,0 +1,268 @@ | |||
1 | //! Look up accessible paths for items. | ||
2 | use std::collections::BTreeSet; | ||
3 | |||
4 | use either::Either; | ||
5 | use hir::{AsAssocItem, AssocItemContainer, ModuleDef, Semantics}; | ||
6 | use ide_db::{imports_locator, RootDatabase}; | ||
7 | use rustc_hash::FxHashSet; | ||
8 | use syntax::{ast, AstNode, SyntaxNode}; | ||
9 | |||
10 | use crate::assist_config::InsertUseConfig; | ||
11 | |||
12 | #[derive(Debug)] | ||
13 | pub(crate) enum ImportCandidate { | ||
14 | /// Simple name like 'HashMap' | ||
15 | UnqualifiedName(PathImportCandidate), | ||
16 | /// First part of the qualified name. | ||
17 | /// For 'std::collections::HashMap', that will be 'std'. | ||
18 | QualifierStart(PathImportCandidate), | ||
19 | /// A trait associated function (with no self parameter) or associated constant. | ||
20 | /// For 'test_mod::TestEnum::test_function', `ty` is the `test_mod::TestEnum` expression type | ||
21 | /// and `name` is the `test_function` | ||
22 | TraitAssocItem(TraitImportCandidate), | ||
23 | /// A trait method with self parameter. | ||
24 | /// For 'test_enum.test_method()', `ty` is the `test_enum` expression type | ||
25 | /// and `name` is the `test_method` | ||
26 | TraitMethod(TraitImportCandidate), | ||
27 | } | ||
28 | |||
29 | #[derive(Debug)] | ||
30 | pub(crate) struct TraitImportCandidate { | ||
31 | pub ty: hir::Type, | ||
32 | pub name: String, | ||
33 | } | ||
34 | |||
35 | #[derive(Debug)] | ||
36 | pub(crate) struct PathImportCandidate { | ||
37 | pub name: String, | ||
38 | } | ||
39 | |||
40 | #[derive(Debug)] | ||
41 | pub(crate) struct ImportAssets { | ||
42 | import_candidate: ImportCandidate, | ||
43 | module_with_name_to_import: hir::Module, | ||
44 | syntax_under_caret: SyntaxNode, | ||
45 | } | ||
46 | |||
47 | impl ImportAssets { | ||
48 | pub(crate) fn for_method_call( | ||
49 | method_call: ast::MethodCallExpr, | ||
50 | sema: &Semantics<RootDatabase>, | ||
51 | ) -> Option<Self> { | ||
52 | let syntax_under_caret = method_call.syntax().to_owned(); | ||
53 | let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?; | ||
54 | Some(Self { | ||
55 | import_candidate: ImportCandidate::for_method_call(sema, &method_call)?, | ||
56 | module_with_name_to_import, | ||
57 | syntax_under_caret, | ||
58 | }) | ||
59 | } | ||
60 | |||
61 | pub(crate) fn for_regular_path( | ||
62 | path_under_caret: ast::Path, | ||
63 | sema: &Semantics<RootDatabase>, | ||
64 | ) -> Option<Self> { | ||
65 | let syntax_under_caret = path_under_caret.syntax().to_owned(); | ||
66 | if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() { | ||
67 | return None; | ||
68 | } | ||
69 | |||
70 | let module_with_name_to_import = sema.scope(&syntax_under_caret).module()?; | ||
71 | Some(Self { | ||
72 | import_candidate: ImportCandidate::for_regular_path(sema, &path_under_caret)?, | ||
73 | module_with_name_to_import, | ||
74 | syntax_under_caret, | ||
75 | }) | ||
76 | } | ||
77 | |||
78 | pub(crate) fn syntax_under_caret(&self) -> &SyntaxNode { | ||
79 | &self.syntax_under_caret | ||
80 | } | ||
81 | |||
82 | pub(crate) fn import_candidate(&self) -> &ImportCandidate { | ||
83 | &self.import_candidate | ||
84 | } | ||
85 | |||
86 | fn get_search_query(&self) -> &str { | ||
87 | match &self.import_candidate { | ||
88 | ImportCandidate::UnqualifiedName(candidate) | ||
89 | | ImportCandidate::QualifierStart(candidate) => &candidate.name, | ||
90 | ImportCandidate::TraitAssocItem(candidate) | ||
91 | | ImportCandidate::TraitMethod(candidate) => &candidate.name, | ||
92 | } | ||
93 | } | ||
94 | |||
95 | pub(crate) fn search_for_imports( | ||
96 | &self, | ||
97 | sema: &Semantics<RootDatabase>, | ||
98 | config: &InsertUseConfig, | ||
99 | ) -> BTreeSet<hir::ModPath> { | ||
100 | let _p = profile::span("import_assists::search_for_imports"); | ||
101 | self.search_for(sema, Some(config.prefix_kind)) | ||
102 | } | ||
103 | |||
104 | /// This may return non-absolute paths if a part of the returned path is already imported into scope. | ||
105 | #[allow(dead_code)] | ||
106 | pub(crate) fn search_for_relative_paths( | ||
107 | &self, | ||
108 | sema: &Semantics<RootDatabase>, | ||
109 | ) -> BTreeSet<hir::ModPath> { | ||
110 | let _p = profile::span("import_assists::search_for_relative_paths"); | ||
111 | self.search_for(sema, None) | ||
112 | } | ||
113 | |||
114 | fn search_for( | ||
115 | &self, | ||
116 | sema: &Semantics<RootDatabase>, | ||
117 | prefixed: Option<hir::PrefixKind>, | ||
118 | ) -> BTreeSet<hir::ModPath> { | ||
119 | let db = sema.db; | ||
120 | let mut trait_candidates = FxHashSet::default(); | ||
121 | let current_crate = self.module_with_name_to_import.krate(); | ||
122 | |||
123 | let filter = |candidate: Either<hir::ModuleDef, hir::MacroDef>| { | ||
124 | trait_candidates.clear(); | ||
125 | match &self.import_candidate { | ||
126 | ImportCandidate::TraitAssocItem(trait_candidate) => { | ||
127 | let located_assoc_item = match candidate { | ||
128 | Either::Left(ModuleDef::Function(located_function)) => { | ||
129 | located_function.as_assoc_item(db) | ||
130 | } | ||
131 | Either::Left(ModuleDef::Const(located_const)) => { | ||
132 | located_const.as_assoc_item(db) | ||
133 | } | ||
134 | _ => None, | ||
135 | } | ||
136 | .map(|assoc| assoc.container(db)) | ||
137 | .and_then(Self::assoc_to_trait)?; | ||
138 | |||
139 | trait_candidates.insert(located_assoc_item.into()); | ||
140 | |||
141 | trait_candidate | ||
142 | .ty | ||
143 | .iterate_path_candidates( | ||
144 | db, | ||
145 | current_crate, | ||
146 | &trait_candidates, | ||
147 | None, | ||
148 | |_, assoc| Self::assoc_to_trait(assoc.container(db)), | ||
149 | ) | ||
150 | .map(ModuleDef::from) | ||
151 | .map(Either::Left) | ||
152 | } | ||
153 | ImportCandidate::TraitMethod(trait_candidate) => { | ||
154 | let located_assoc_item = | ||
155 | if let Either::Left(ModuleDef::Function(located_function)) = candidate { | ||
156 | located_function | ||
157 | .as_assoc_item(db) | ||
158 | .map(|assoc| assoc.container(db)) | ||
159 | .and_then(Self::assoc_to_trait) | ||
160 | } else { | ||
161 | None | ||
162 | }?; | ||
163 | |||
164 | trait_candidates.insert(located_assoc_item.into()); | ||
165 | |||
166 | trait_candidate | ||
167 | .ty | ||
168 | .iterate_method_candidates( | ||
169 | db, | ||
170 | current_crate, | ||
171 | &trait_candidates, | ||
172 | None, | ||
173 | |_, function| { | ||
174 | Self::assoc_to_trait(function.as_assoc_item(db)?.container(db)) | ||
175 | }, | ||
176 | ) | ||
177 | .map(ModuleDef::from) | ||
178 | .map(Either::Left) | ||
179 | } | ||
180 | _ => Some(candidate), | ||
181 | } | ||
182 | }; | ||
183 | |||
184 | imports_locator::find_imports(sema, current_crate, &self.get_search_query()) | ||
185 | .into_iter() | ||
186 | .filter_map(filter) | ||
187 | .filter_map(|candidate| { | ||
188 | let item: hir::ItemInNs = candidate.either(Into::into, Into::into); | ||
189 | if let Some(prefix_kind) = prefixed { | ||
190 | self.module_with_name_to_import.find_use_path_prefixed(db, item, prefix_kind) | ||
191 | } else { | ||
192 | self.module_with_name_to_import.find_use_path(db, item) | ||
193 | } | ||
194 | }) | ||
195 | .filter(|use_path| !use_path.segments.is_empty()) | ||
196 | .take(20) | ||
197 | .collect::<BTreeSet<_>>() | ||
198 | } | ||
199 | |||
200 | fn assoc_to_trait(assoc: AssocItemContainer) -> Option<hir::Trait> { | ||
201 | if let AssocItemContainer::Trait(extracted_trait) = assoc { | ||
202 | Some(extracted_trait) | ||
203 | } else { | ||
204 | None | ||
205 | } | ||
206 | } | ||
207 | } | ||
208 | |||
209 | impl ImportCandidate { | ||
210 | fn for_method_call( | ||
211 | sema: &Semantics<RootDatabase>, | ||
212 | method_call: &ast::MethodCallExpr, | ||
213 | ) -> Option<Self> { | ||
214 | match sema.resolve_method_call(method_call) { | ||
215 | Some(_) => None, | ||
216 | None => Some(Self::TraitMethod(TraitImportCandidate { | ||
217 | ty: sema.type_of_expr(&method_call.receiver()?)?, | ||
218 | name: method_call.name_ref()?.syntax().to_string(), | ||
219 | })), | ||
220 | } | ||
221 | } | ||
222 | |||
223 | fn for_regular_path( | ||
224 | sema: &Semantics<RootDatabase>, | ||
225 | path_under_caret: &ast::Path, | ||
226 | ) -> Option<Self> { | ||
227 | if sema.resolve_path(path_under_caret).is_some() { | ||
228 | return None; | ||
229 | } | ||
230 | |||
231 | let segment = path_under_caret.segment()?; | ||
232 | let candidate = if let Some(qualifier) = path_under_caret.qualifier() { | ||
233 | let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?; | ||
234 | let qualifier_start_path = | ||
235 | qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?; | ||
236 | if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) { | ||
237 | let qualifier_resolution = if qualifier_start_path == qualifier { | ||
238 | qualifier_start_resolution | ||
239 | } else { | ||
240 | sema.resolve_path(&qualifier)? | ||
241 | }; | ||
242 | match qualifier_resolution { | ||
243 | hir::PathResolution::Def(hir::ModuleDef::Adt(assoc_item_path)) => { | ||
244 | ImportCandidate::TraitAssocItem(TraitImportCandidate { | ||
245 | ty: assoc_item_path.ty(sema.db), | ||
246 | name: segment.syntax().to_string(), | ||
247 | }) | ||
248 | } | ||
249 | _ => return None, | ||
250 | } | ||
251 | } else { | ||
252 | ImportCandidate::QualifierStart(PathImportCandidate { | ||
253 | name: qualifier_start.syntax().to_string(), | ||
254 | }) | ||
255 | } | ||
256 | } else { | ||
257 | ImportCandidate::UnqualifiedName(PathImportCandidate { | ||
258 | name: segment | ||
259 | .syntax() | ||
260 | .descendants() | ||
261 | .find_map(ast::NameRef::cast)? | ||
262 | .syntax() | ||
263 | .to_string(), | ||
264 | }) | ||
265 | }; | ||
266 | Some(candidate) | ||
267 | } | ||
268 | } | ||
diff --git a/crates/assists/src/utils/insert_use.rs b/crates/assists/src/utils/insert_use.rs index bfd457d18..409985b3b 100644 --- a/crates/assists/src/utils/insert_use.rs +++ b/crates/assists/src/utils/insert_use.rs | |||
@@ -173,8 +173,15 @@ pub(crate) fn try_merge_trees( | |||
173 | let rhs_path = rhs.path()?; | 173 | let rhs_path = rhs.path()?; |
174 | 174 | ||
175 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; | 175 | let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?; |
176 | let lhs = lhs.split_prefix(&lhs_prefix); | 176 | let (lhs, rhs) = if is_simple_path(lhs) |
177 | let rhs = rhs.split_prefix(&rhs_prefix); | 177 | && is_simple_path(rhs) |
178 | && lhs_path == lhs_prefix | ||
179 | && rhs_path == rhs_prefix | ||
180 | { | ||
181 | (lhs.clone(), rhs.clone()) | ||
182 | } else { | ||
183 | (lhs.split_prefix(&lhs_prefix), rhs.split_prefix(&rhs_prefix)) | ||
184 | }; | ||
178 | recursive_merge(&lhs, &rhs, merge) | 185 | recursive_merge(&lhs, &rhs, merge) |
179 | } | 186 | } |
180 | 187 | ||
@@ -250,6 +257,10 @@ fn recursive_merge( | |||
250 | use_trees.insert(idx, make::glob_use_tree()); | 257 | use_trees.insert(idx, make::glob_use_tree()); |
251 | continue; | 258 | continue; |
252 | } | 259 | } |
260 | |||
261 | if lhs_t.use_tree_list().is_none() && rhs_t.use_tree_list().is_none() { | ||
262 | continue; | ||
263 | } | ||
253 | } | 264 | } |
254 | let lhs = lhs_t.split_prefix(&lhs_prefix); | 265 | let lhs = lhs_t.split_prefix(&lhs_prefix); |
255 | let rhs = rhs_t.split_prefix(&rhs_prefix); | 266 | let rhs = rhs_t.split_prefix(&rhs_prefix); |
@@ -295,6 +306,10 @@ fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Pa | |||
295 | } | 306 | } |
296 | } | 307 | } |
297 | 308 | ||
309 | fn is_simple_path(use_tree: &ast::UseTree) -> bool { | ||
310 | use_tree.use_tree_list().is_none() && use_tree.star_token().is_none() | ||
311 | } | ||
312 | |||
298 | fn path_is_self(path: &ast::Path) -> bool { | 313 | fn path_is_self(path: &ast::Path) -> bool { |
299 | path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none() | 314 | path.segment().and_then(|seg| seg.self_token()).is_some() && path.qualifier().is_none() |
300 | } | 315 | } |
@@ -524,6 +539,11 @@ mod tests { | |||
524 | use test_utils::assert_eq_text; | 539 | use test_utils::assert_eq_text; |
525 | 540 | ||
526 | #[test] | 541 | #[test] |
542 | fn insert_existing() { | ||
543 | check_full("std::fs", "use std::fs;", "use std::fs;") | ||
544 | } | ||
545 | |||
546 | #[test] | ||
527 | fn insert_start() { | 547 | fn insert_start() { |
528 | check_none( | 548 | check_none( |
529 | "std::bar::AA", | 549 | "std::bar::AA", |
diff --git a/crates/base_db/Cargo.toml b/crates/base_db/Cargo.toml index f7bfcb0d7..1724d2f85 100644 --- a/crates/base_db/Cargo.toml +++ b/crates/base_db/Cargo.toml | |||
@@ -10,7 +10,7 @@ edition = "2018" | |||
10 | doctest = false | 10 | doctest = false |
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | salsa = "0.15.2" | 13 | salsa = "0.16.0" |
14 | rustc-hash = "1.1.0" | 14 | rustc-hash = "1.1.0" |
15 | 15 | ||
16 | syntax = { path = "../syntax", version = "0.0.0" } | 16 | syntax = { path = "../syntax", version = "0.0.0" } |
diff --git a/crates/ide/Cargo.toml b/crates/ide/Cargo.toml index f0257403d..29dc9a6a8 100644 --- a/crates/ide/Cargo.toml +++ b/crates/ide/Cargo.toml | |||
@@ -16,8 +16,8 @@ itertools = "0.9.0" | |||
16 | log = "0.4.8" | 16 | log = "0.4.8" |
17 | rustc-hash = "1.1.0" | 17 | rustc-hash = "1.1.0" |
18 | oorandom = "11.1.2" | 18 | oorandom = "11.1.2" |
19 | pulldown-cmark-to-cmark = "5.0.0" | 19 | pulldown-cmark-to-cmark = "6.0.0" |
20 | pulldown-cmark = {version = "0.7.2", default-features = false} | 20 | pulldown-cmark = { version = "0.8.0", default-features = false } |
21 | url = "2.1.1" | 21 | url = "2.1.1" |
22 | 22 | ||
23 | stdx = { path = "../stdx", version = "0.0.0" } | 23 | stdx = { path = "../stdx", version = "0.0.0" } |
diff --git a/crates/ide/src/completion.rs b/crates/ide/src/completion.rs index 697f691b0..b0e35b2bd 100644 --- a/crates/ide/src/completion.rs +++ b/crates/ide/src/completion.rs | |||
@@ -61,6 +61,8 @@ pub use crate::completion::{ | |||
61 | // - `expr.refm` -> `&mut expr` | 61 | // - `expr.refm` -> `&mut expr` |
62 | // - `expr.not` -> `!expr` | 62 | // - `expr.not` -> `!expr` |
63 | // - `expr.dbg` -> `dbg!(expr)` | 63 | // - `expr.dbg` -> `dbg!(expr)` |
64 | // - `expr.dbgr` -> `dbg!(&expr)` | ||
65 | // - `expr.call` -> `(expr)` | ||
64 | // | 66 | // |
65 | // There also snippet completions: | 67 | // There also snippet completions: |
66 | // | 68 | // |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b30cdb6ed..1e5ea4617 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -5,6 +5,7 @@ | |||
5 | //! original files. So we need to map the ranges. | 5 | //! original files. So we need to map the ranges. |
6 | 6 | ||
7 | mod fixes; | 7 | mod fixes; |
8 | mod field_shorthand; | ||
8 | 9 | ||
9 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
10 | 11 | ||
@@ -80,7 +81,7 @@ pub(crate) fn diagnostics( | |||
80 | 81 | ||
81 | for node in parse.tree().syntax().descendants() { | 82 | for node in parse.tree().syntax().descendants() { |
82 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | 83 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); |
83 | check_struct_shorthand_initialization(&mut res, file_id, &node); | 84 | field_shorthand::check(&mut res, file_id, &node); |
84 | } | 85 | } |
85 | let res = RefCell::new(res); | 86 | let res = RefCell::new(res); |
86 | let sink_builder = DiagnosticSinkBuilder::new() | 87 | let sink_builder = DiagnosticSinkBuilder::new() |
@@ -188,42 +189,6 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
188 | None | 189 | None |
189 | } | 190 | } |
190 | 191 | ||
191 | fn check_struct_shorthand_initialization( | ||
192 | acc: &mut Vec<Diagnostic>, | ||
193 | file_id: FileId, | ||
194 | node: &SyntaxNode, | ||
195 | ) -> Option<()> { | ||
196 | let record_lit = ast::RecordExpr::cast(node.clone())?; | ||
197 | let record_field_list = record_lit.record_expr_field_list()?; | ||
198 | for record_field in record_field_list.fields() { | ||
199 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | ||
200 | let field_name = name_ref.syntax().text().to_string(); | ||
201 | let field_expr = expr.syntax().text().to_string(); | ||
202 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
203 | if field_name == field_expr && !field_name_is_tup_index { | ||
204 | let mut edit_builder = TextEdit::builder(); | ||
205 | edit_builder.delete(record_field.syntax().text_range()); | ||
206 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
207 | let edit = edit_builder.finish(); | ||
208 | |||
209 | let field_range = record_field.syntax().text_range(); | ||
210 | acc.push(Diagnostic { | ||
211 | // name: None, | ||
212 | range: field_range, | ||
213 | message: "Shorthand struct initialization".to_string(), | ||
214 | severity: Severity::WeakWarning, | ||
215 | fix: Some(Fix::new( | ||
216 | "Use struct shorthand initialization", | ||
217 | SourceFileEdit { file_id, edit }.into(), | ||
218 | field_range, | ||
219 | )), | ||
220 | }); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | Some(()) | ||
225 | } | ||
226 | |||
227 | #[cfg(test)] | 192 | #[cfg(test)] |
228 | mod tests { | 193 | mod tests { |
229 | use expect_test::{expect, Expect}; | 194 | use expect_test::{expect, Expect}; |
@@ -237,7 +202,7 @@ mod tests { | |||
237 | /// * a diagnostic is produced | 202 | /// * a diagnostic is produced |
238 | /// * this diagnostic fix trigger range touches the input cursor position | 203 | /// * this diagnostic fix trigger range touches the input cursor position |
239 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 204 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
240 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 205 | pub(super) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
241 | let after = trim_indent(ra_fixture_after); | 206 | let after = trim_indent(ra_fixture_after); |
242 | 207 | ||
243 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 208 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
@@ -319,7 +284,7 @@ mod tests { | |||
319 | 284 | ||
320 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 285 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
321 | /// apply to the file containing the cursor. | 286 | /// apply to the file containing the cursor. |
322 | fn check_no_diagnostics(ra_fixture: &str) { | 287 | pub(crate) fn check_no_diagnostics(ra_fixture: &str) { |
323 | let (analysis, files) = fixture::files(ra_fixture); | 288 | let (analysis, files) = fixture::files(ra_fixture); |
324 | let diagnostics = files | 289 | let diagnostics = files |
325 | .into_iter() | 290 | .into_iter() |
@@ -720,58 +685,6 @@ mod a { | |||
720 | } | 685 | } |
721 | 686 | ||
722 | #[test] | 687 | #[test] |
723 | fn test_check_struct_shorthand_initialization() { | ||
724 | check_no_diagnostics( | ||
725 | r#" | ||
726 | struct A { a: &'static str } | ||
727 | fn main() { A { a: "hello" } } | ||
728 | "#, | ||
729 | ); | ||
730 | check_no_diagnostics( | ||
731 | r#" | ||
732 | struct A(usize); | ||
733 | fn main() { A { 0: 0 } } | ||
734 | "#, | ||
735 | ); | ||
736 | |||
737 | check_fix( | ||
738 | r#" | ||
739 | struct A { a: &'static str } | ||
740 | fn main() { | ||
741 | let a = "haha"; | ||
742 | A { a<|>: a } | ||
743 | } | ||
744 | "#, | ||
745 | r#" | ||
746 | struct A { a: &'static str } | ||
747 | fn main() { | ||
748 | let a = "haha"; | ||
749 | A { a } | ||
750 | } | ||
751 | "#, | ||
752 | ); | ||
753 | |||
754 | check_fix( | ||
755 | r#" | ||
756 | struct A { a: &'static str, b: &'static str } | ||
757 | fn main() { | ||
758 | let a = "haha"; | ||
759 | let b = "bb"; | ||
760 | A { a<|>: a, b } | ||
761 | } | ||
762 | "#, | ||
763 | r#" | ||
764 | struct A { a: &'static str, b: &'static str } | ||
765 | fn main() { | ||
766 | let a = "haha"; | ||
767 | let b = "bb"; | ||
768 | A { a, b } | ||
769 | } | ||
770 | "#, | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn test_add_field_from_usage() { | 688 | fn test_add_field_from_usage() { |
776 | check_fix( | 689 | check_fix( |
777 | r" | 690 | r" |
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs new file mode 100644 index 000000000..2c4acd783 --- /dev/null +++ b/crates/ide/src/diagnostics/field_shorthand.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both | ||
2 | //! expressions and patterns. | ||
3 | |||
4 | use base_db::FileId; | ||
5 | use ide_db::source_change::SourceFileEdit; | ||
6 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{Diagnostic, Fix, Severity}; | ||
10 | |||
11 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { | ||
12 | match_ast! { | ||
13 | match node { | ||
14 | ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it), | ||
15 | ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it), | ||
16 | _ => () | ||
17 | } | ||
18 | }; | ||
19 | } | ||
20 | |||
21 | fn check_expr_field_shorthand( | ||
22 | acc: &mut Vec<Diagnostic>, | ||
23 | file_id: FileId, | ||
24 | record_expr: ast::RecordExpr, | ||
25 | ) { | ||
26 | let record_field_list = match record_expr.record_expr_field_list() { | ||
27 | Some(it) => it, | ||
28 | None => return, | ||
29 | }; | ||
30 | for record_field in record_field_list.fields() { | ||
31 | let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) { | ||
32 | Some(it) => it, | ||
33 | None => continue, | ||
34 | }; | ||
35 | |||
36 | let field_name = name_ref.syntax().text().to_string(); | ||
37 | let field_expr = expr.syntax().text().to_string(); | ||
38 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
39 | if field_name != field_expr || field_name_is_tup_index { | ||
40 | continue; | ||
41 | } | ||
42 | |||
43 | let mut edit_builder = TextEdit::builder(); | ||
44 | edit_builder.delete(record_field.syntax().text_range()); | ||
45 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
46 | let edit = edit_builder.finish(); | ||
47 | |||
48 | let field_range = record_field.syntax().text_range(); | ||
49 | acc.push(Diagnostic { | ||
50 | // name: None, | ||
51 | range: field_range, | ||
52 | message: "Shorthand struct initialization".to_string(), | ||
53 | severity: Severity::WeakWarning, | ||
54 | fix: Some(Fix::new( | ||
55 | "Use struct shorthand initialization", | ||
56 | SourceFileEdit { file_id, edit }.into(), | ||
57 | field_range, | ||
58 | )), | ||
59 | }); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | fn check_pat_field_shorthand( | ||
64 | acc: &mut Vec<Diagnostic>, | ||
65 | file_id: FileId, | ||
66 | record_pat: ast::RecordPat, | ||
67 | ) { | ||
68 | let record_pat_field_list = match record_pat.record_pat_field_list() { | ||
69 | Some(it) => it, | ||
70 | None => return, | ||
71 | }; | ||
72 | for record_pat_field in record_pat_field_list.fields() { | ||
73 | let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) { | ||
74 | Some(it) => it, | ||
75 | None => continue, | ||
76 | }; | ||
77 | |||
78 | let field_name = name_ref.syntax().text().to_string(); | ||
79 | let field_pat = pat.syntax().text().to_string(); | ||
80 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
81 | if field_name != field_pat || field_name_is_tup_index { | ||
82 | continue; | ||
83 | } | ||
84 | |||
85 | let mut edit_builder = TextEdit::builder(); | ||
86 | edit_builder.delete(record_pat_field.syntax().text_range()); | ||
87 | edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name); | ||
88 | let edit = edit_builder.finish(); | ||
89 | |||
90 | let field_range = record_pat_field.syntax().text_range(); | ||
91 | acc.push(Diagnostic { | ||
92 | // name: None, | ||
93 | range: field_range, | ||
94 | message: "Shorthand struct pattern".to_string(), | ||
95 | severity: Severity::WeakWarning, | ||
96 | fix: Some(Fix::new( | ||
97 | "Use struct field shorthand", | ||
98 | SourceFileEdit { file_id, edit }.into(), | ||
99 | field_range, | ||
100 | )), | ||
101 | }); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | #[cfg(test)] | ||
106 | mod tests { | ||
107 | use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; | ||
108 | |||
109 | #[test] | ||
110 | fn test_check_expr_field_shorthand() { | ||
111 | check_no_diagnostics( | ||
112 | r#" | ||
113 | struct A { a: &'static str } | ||
114 | fn main() { A { a: "hello" } } | ||
115 | "#, | ||
116 | ); | ||
117 | check_no_diagnostics( | ||
118 | r#" | ||
119 | struct A(usize); | ||
120 | fn main() { A { 0: 0 } } | ||
121 | "#, | ||
122 | ); | ||
123 | |||
124 | check_fix( | ||
125 | r#" | ||
126 | struct A { a: &'static str } | ||
127 | fn main() { | ||
128 | let a = "haha"; | ||
129 | A { a<|>: a } | ||
130 | } | ||
131 | "#, | ||
132 | r#" | ||
133 | struct A { a: &'static str } | ||
134 | fn main() { | ||
135 | let a = "haha"; | ||
136 | A { a } | ||
137 | } | ||
138 | "#, | ||
139 | ); | ||
140 | |||
141 | check_fix( | ||
142 | r#" | ||
143 | struct A { a: &'static str, b: &'static str } | ||
144 | fn main() { | ||
145 | let a = "haha"; | ||
146 | let b = "bb"; | ||
147 | A { a<|>: a, b } | ||
148 | } | ||
149 | "#, | ||
150 | r#" | ||
151 | struct A { a: &'static str, b: &'static str } | ||
152 | fn main() { | ||
153 | let a = "haha"; | ||
154 | let b = "bb"; | ||
155 | A { a, b } | ||
156 | } | ||
157 | "#, | ||
158 | ); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn test_check_pat_field_shorthand() { | ||
163 | check_no_diagnostics( | ||
164 | r#" | ||
165 | struct A { a: &'static str } | ||
166 | fn f(a: A) { let A { a: hello } = a; } | ||
167 | "#, | ||
168 | ); | ||
169 | check_no_diagnostics( | ||
170 | r#" | ||
171 | struct A(usize); | ||
172 | fn f(a: A) { let A { 0: 0 } = a; } | ||
173 | "#, | ||
174 | ); | ||
175 | |||
176 | check_fix( | ||
177 | r#" | ||
178 | struct A { a: &'static str } | ||
179 | fn f(a: A) { | ||
180 | let A { a<|>: a } = a; | ||
181 | } | ||
182 | "#, | ||
183 | r#" | ||
184 | struct A { a: &'static str } | ||
185 | fn f(a: A) { | ||
186 | let A { a } = a; | ||
187 | } | ||
188 | "#, | ||
189 | ); | ||
190 | |||
191 | check_fix( | ||
192 | r#" | ||
193 | struct A { a: &'static str, b: &'static str } | ||
194 | fn f(a: A) { | ||
195 | let A { a<|>: a, b } = a; | ||
196 | } | ||
197 | "#, | ||
198 | r#" | ||
199 | struct A { a: &'static str, b: &'static str } | ||
200 | fn f(a: A) { | ||
201 | let A { a, b } = a; | ||
202 | } | ||
203 | "#, | ||
204 | ); | ||
205 | } | ||
206 | } | ||
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 06af36b73..d9dc63b33 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -1,9 +1,10 @@ | |||
1 | //! Resolves and rewrites links in markdown documentation. | 1 | //! Resolves and rewrites links in markdown documentation. |
2 | 2 | ||
3 | use std::convert::TryFrom; | ||
3 | use std::iter::once; | 4 | use std::iter::once; |
4 | 5 | ||
5 | use itertools::Itertools; | 6 | use itertools::Itertools; |
6 | use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; | 7 | use pulldown_cmark::{BrokenLink, CowStr, Event, InlineStr, LinkType, Options, Parser, Tag}; |
7 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; | 8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; |
8 | use url::Url; | 9 | use url::Url; |
9 | 10 | ||
@@ -13,7 +14,7 @@ use hir::{ | |||
13 | ModuleDef, | 14 | ModuleDef, |
14 | }; | 15 | }; |
15 | use ide_db::{ | 16 | use ide_db::{ |
16 | defs::{classify_name, classify_name_ref, Definition}, | 17 | defs::{Definition, NameClass, NameRefClass}, |
17 | RootDatabase, | 18 | RootDatabase, |
18 | }; | 19 | }; |
19 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 20 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
@@ -24,11 +25,13 @@ pub type DocumentationLink = String; | |||
24 | 25 | ||
25 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) | 26 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) |
26 | pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | 27 | pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { |
27 | let doc = Parser::new_with_broken_link_callback( | 28 | let mut cb = |link: BrokenLink| { |
28 | markdown, | 29 | Some(( |
29 | Options::empty(), | 30 | /*url*/ link.reference.to_owned().into(), |
30 | Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), | 31 | /*title*/ link.reference.to_owned().into(), |
31 | ); | 32 | )) |
33 | }; | ||
34 | let doc = Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(&mut cb)); | ||
32 | 35 | ||
33 | let doc = map_links(doc, |target, title: &str| { | 36 | let doc = map_links(doc, |target, title: &str| { |
34 | // This check is imperfect, there's some overlap between valid intra-doc links | 37 | // This check is imperfect, there's some overlap between valid intra-doc links |
@@ -66,11 +69,11 @@ pub fn remove_links(markdown: &str) -> String { | |||
66 | let mut opts = Options::empty(); | 69 | let mut opts = Options::empty(); |
67 | opts.insert(Options::ENABLE_FOOTNOTES); | 70 | opts.insert(Options::ENABLE_FOOTNOTES); |
68 | 71 | ||
69 | let doc = Parser::new_with_broken_link_callback( | 72 | let mut cb = |_: BrokenLink| { |
70 | markdown, | 73 | let empty = InlineStr::try_from("").unwrap(); |
71 | opts, | 74 | Some((CowStr::Inlined(empty.clone()), CowStr::Inlined(empty))) |
72 | Some(&|_, _| Some((String::new(), String::new()))), | 75 | }; |
73 | ); | 76 | let doc = Parser::new_with_broken_link_callback(markdown, opts, Some(&mut cb)); |
74 | let doc = doc.filter_map(move |evt| match evt { | 77 | let doc = doc.filter_map(move |evt| match evt { |
75 | Event::Start(Tag::Link(link_type, ref target, ref title)) => { | 78 | Event::Start(Tag::Link(link_type, ref target, ref title)) => { |
76 | if link_type == LinkType::Inline && target.contains("://") { | 79 | if link_type == LinkType::Inline && target.contains("://") { |
@@ -229,8 +232,8 @@ pub(crate) fn external_docs( | |||
229 | let node = token.parent(); | 232 | let node = token.parent(); |
230 | let definition = match_ast! { | 233 | let definition = match_ast! { |
231 | match node { | 234 | match node { |
232 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), | 235 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
233 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), | 236 | ast::Name(name) => NameClass::classify(&sema, &name).map(|d| d.referenced_or_defined(sema.db)), |
234 | _ => None, | 237 | _ => None, |
235 | } | 238 | } |
236 | }; | 239 | }; |
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index 582bf4837..a87e31019 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use hir::Semantics; | 1 | use hir::Semantics; |
2 | use ide_db::{ | 2 | use ide_db::{ |
3 | defs::{classify_name, classify_name_ref}, | 3 | defs::{NameClass, NameRefClass}, |
4 | symbol_index, RootDatabase, | 4 | symbol_index, RootDatabase, |
5 | }; | 5 | }; |
6 | use syntax::{ | 6 | use syntax::{ |
@@ -40,7 +40,7 @@ pub(crate) fn goto_definition( | |||
40 | reference_definition(&sema, &name_ref).to_vec() | 40 | reference_definition(&sema, &name_ref).to_vec() |
41 | }, | 41 | }, |
42 | ast::Name(name) => { | 42 | ast::Name(name) => { |
43 | let def = classify_name(&sema, &name)?.definition(sema.db); | 43 | let def = NameClass::classify(&sema, &name)?.referenced_or_defined(sema.db); |
44 | let nav = def.try_to_nav(sema.db)?; | 44 | let nav = def.try_to_nav(sema.db)?; |
45 | vec![nav] | 45 | vec![nav] |
46 | }, | 46 | }, |
@@ -81,9 +81,9 @@ pub(crate) fn reference_definition( | |||
81 | sema: &Semantics<RootDatabase>, | 81 | sema: &Semantics<RootDatabase>, |
82 | name_ref: &ast::NameRef, | 82 | name_ref: &ast::NameRef, |
83 | ) -> ReferenceResult { | 83 | ) -> ReferenceResult { |
84 | let name_kind = classify_name_ref(sema, name_ref); | 84 | let name_kind = NameRefClass::classify(sema, name_ref); |
85 | if let Some(def) = name_kind { | 85 | if let Some(def) = name_kind { |
86 | let def = def.definition(sema.db); | 86 | let def = def.referenced(sema.db); |
87 | return match def.try_to_nav(sema.db) { | 87 | return match def.try_to_nav(sema.db) { |
88 | Some(nav) => ReferenceResult::Exact(nav), | 88 | Some(nav) => ReferenceResult::Exact(nav), |
89 | None => ReferenceResult::Approximate(Vec::new()), | 89 | None => ReferenceResult::Approximate(Vec::new()), |
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 6290b35bd..845333e2a 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -4,7 +4,7 @@ use hir::{ | |||
4 | Module, ModuleDef, ModuleSource, Semantics, | 4 | Module, ModuleDef, ModuleSource, Semantics, |
5 | }; | 5 | }; |
6 | use ide_db::{ | 6 | use ide_db::{ |
7 | defs::{classify_name, classify_name_ref, Definition}, | 7 | defs::{Definition, NameClass, NameRefClass}, |
8 | RootDatabase, | 8 | RootDatabase, |
9 | }; | 9 | }; |
10 | use itertools::Itertools; | 10 | use itertools::Itertools; |
@@ -107,8 +107,8 @@ pub(crate) fn hover( | |||
107 | let node = token.parent(); | 107 | let node = token.parent(); |
108 | let definition = match_ast! { | 108 | let definition = match_ast! { |
109 | match node { | 109 | match node { |
110 | ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)), | 110 | ast::Name(name) => NameClass::classify(&sema, &name).and_then(|d| d.defined(sema.db)), |
111 | ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)), | 111 | ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), |
112 | _ => None, | 112 | _ => None, |
113 | } | 113 | } |
114 | }; | 114 | }; |
@@ -3232,4 +3232,27 @@ fn main() { let foo_test = name_with_dashes::wrapper::Thing::new<|>(); } | |||
3232 | "#]], | 3232 | "#]], |
3233 | ) | 3233 | ) |
3234 | } | 3234 | } |
3235 | |||
3236 | #[test] | ||
3237 | fn hover_field_pat_shorthand_ref_match_ergonomics() { | ||
3238 | check( | ||
3239 | r#" | ||
3240 | struct S { | ||
3241 | f: i32, | ||
3242 | } | ||
3243 | |||
3244 | fn main() { | ||
3245 | let s = S { f: 0 }; | ||
3246 | let S { f<|> } = &s; | ||
3247 | } | ||
3248 | "#, | ||
3249 | expect![[r#" | ||
3250 | *f* | ||
3251 | |||
3252 | ```rust | ||
3253 | &i32 | ||
3254 | ``` | ||
3255 | "#]], | ||
3256 | ); | ||
3257 | } | ||
3235 | } | 3258 | } |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 88e2f2db3..67ec257a8 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -13,7 +13,7 @@ pub(crate) mod rename; | |||
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::Semantics; |
15 | use ide_db::{ | 15 | use ide_db::{ |
16 | defs::{classify_name, classify_name_ref, Definition}, | 16 | defs::{Definition, NameClass, NameRefClass}, |
17 | search::SearchScope, | 17 | search::SearchScope, |
18 | RootDatabase, | 18 | RootDatabase, |
19 | }; | 19 | }; |
@@ -132,13 +132,13 @@ fn find_name( | |||
132 | opt_name: Option<ast::Name>, | 132 | opt_name: Option<ast::Name>, |
133 | ) -> Option<RangeInfo<Definition>> { | 133 | ) -> Option<RangeInfo<Definition>> { |
134 | if let Some(name) = opt_name { | 134 | if let Some(name) = opt_name { |
135 | let def = classify_name(sema, &name)?.definition(sema.db); | 135 | let def = NameClass::classify(sema, &name)?.referenced_or_defined(sema.db); |
136 | let range = name.syntax().text_range(); | 136 | let range = name.syntax().text_range(); |
137 | return Some(RangeInfo::new(range, def)); | 137 | return Some(RangeInfo::new(range, def)); |
138 | } | 138 | } |
139 | let name_ref = | 139 | let name_ref = |
140 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; | 140 | sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?; |
141 | let def = classify_name_ref(sema, &name_ref)?.definition(sema.db); | 141 | let def = NameRefClass::classify(sema, &name_ref)?.referenced(sema.db); |
142 | let range = name_ref.syntax().text_range(); | 142 | let range = name_ref.syntax().text_range(); |
143 | Some(RangeInfo::new(range, def)) | 143 | Some(RangeInfo::new(range, def)) |
144 | } | 144 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index f9a11e43d..35aafc49d 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -3,7 +3,7 @@ | |||
3 | use base_db::SourceDatabaseExt; | 3 | use base_db::SourceDatabaseExt; |
4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | 4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; |
5 | use ide_db::{ | 5 | use ide_db::{ |
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 6 | defs::{Definition, NameClass, NameRefClass}, |
7 | RootDatabase, | 7 | RootDatabase, |
8 | }; | 8 | }; |
9 | 9 | ||
@@ -88,13 +88,13 @@ fn find_module_at_offset( | |||
88 | let module = match_ast! { | 88 | let module = match_ast! { |
89 | match (ident.parent()) { | 89 | match (ident.parent()) { |
90 | ast::NameRef(name_ref) => { | 90 | ast::NameRef(name_ref) => { |
91 | match classify_name_ref(sema, &name_ref)? { | 91 | match NameRefClass::classify(sema, &name_ref)? { |
92 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 92 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, |
93 | _ => return None, | 93 | _ => return None, |
94 | } | 94 | } |
95 | }, | 95 | }, |
96 | ast::Name(name) => { | 96 | ast::Name(name) => { |
97 | match classify_name(&sema, &name)? { | 97 | match NameClass::classify(&sema, &name)? { |
98 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | 98 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, |
99 | _ => return None, | 99 | _ => return None, |
100 | } | 100 | } |
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 6aafd6fd5..b35c03162 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs | |||
@@ -1,12 +1,14 @@ | |||
1 | mod tags; | 1 | mod format; |
2 | mod html; | 2 | mod html; |
3 | mod injection; | 3 | mod injection; |
4 | mod macro_rules; | ||
5 | mod tags; | ||
4 | #[cfg(test)] | 6 | #[cfg(test)] |
5 | mod tests; | 7 | mod tests; |
6 | 8 | ||
7 | use hir::{Local, Name, Semantics, VariantDef}; | 9 | use hir::{Local, Name, Semantics, VariantDef}; |
8 | use ide_db::{ | 10 | use ide_db::{ |
9 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | 11 | defs::{Definition, NameClass, NameRefClass}, |
10 | RootDatabase, | 12 | RootDatabase, |
11 | }; | 13 | }; |
12 | use rustc_hash::FxHashMap; | 14 | use rustc_hash::FxHashMap; |
@@ -17,9 +19,11 @@ use syntax::{ | |||
17 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, | 19 | SyntaxNode, SyntaxToken, TextRange, WalkEvent, T, |
18 | }; | 20 | }; |
19 | 21 | ||
20 | use crate::FileId; | 22 | use crate::{ |
23 | syntax_highlighting::{format::FormatStringHighlighter, macro_rules::MacroRulesHighlighter}, | ||
24 | FileId, | ||
25 | }; | ||
21 | 26 | ||
22 | use ast::FormatSpecifier; | ||
23 | pub(crate) use html::highlight_as_html; | 27 | pub(crate) use html::highlight_as_html; |
24 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; | 28 | pub use tags::{Highlight, HighlightModifier, HighlightModifiers, HighlightTag}; |
25 | 29 | ||
@@ -68,8 +72,9 @@ pub(crate) fn highlight( | |||
68 | // When we leave a node, the we use it to flatten the highlighted ranges. | 72 | // When we leave a node, the we use it to flatten the highlighted ranges. |
69 | let mut stack = HighlightedRangeStack::new(); | 73 | let mut stack = HighlightedRangeStack::new(); |
70 | 74 | ||
71 | let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None; | 75 | let mut current_macro_call: Option<ast::MacroCall> = None; |
72 | let mut format_string: Option<SyntaxElement> = None; | 76 | let mut format_string_highlighter = FormatStringHighlighter::default(); |
77 | let mut macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
73 | 78 | ||
74 | // Walk all nodes, keeping track of whether we are inside a macro or not. | 79 | // Walk all nodes, keeping track of whether we are inside a macro or not. |
75 | // If in macro, expand it first and highlight the expanded code. | 80 | // If in macro, expand it first and highlight the expanded code. |
@@ -99,9 +104,8 @@ pub(crate) fn highlight( | |||
99 | binding_hash: None, | 104 | binding_hash: None, |
100 | }); | 105 | }); |
101 | } | 106 | } |
102 | let mut is_macro_rules = None; | ||
103 | if let Some(name) = mc.is_macro_rules() { | 107 | if let Some(name) = mc.is_macro_rules() { |
104 | is_macro_rules = Some(MacroMatcherParseState::new()); | 108 | macro_rules_highlighter.init(); |
105 | if let Some((highlight, binding_hash)) = highlight_element( | 109 | if let Some((highlight, binding_hash)) = highlight_element( |
106 | &sema, | 110 | &sema, |
107 | &mut bindings_shadow_count, | 111 | &mut bindings_shadow_count, |
@@ -115,13 +119,14 @@ pub(crate) fn highlight( | |||
115 | }); | 119 | }); |
116 | } | 120 | } |
117 | } | 121 | } |
118 | current_macro_call = Some((mc.clone(), is_macro_rules)); | 122 | current_macro_call = Some(mc.clone()); |
119 | continue; | 123 | continue; |
120 | } | 124 | } |
121 | WalkEvent::Leave(Some(mc)) => { | 125 | WalkEvent::Leave(Some(mc)) => { |
122 | assert!(current_macro_call.map(|it| it.0) == Some(mc)); | 126 | assert!(current_macro_call == Some(mc)); |
123 | current_macro_call = None; | 127 | current_macro_call = None; |
124 | format_string = None; | 128 | format_string_highlighter = FormatStringHighlighter::default(); |
129 | macro_rules_highlighter = MacroRulesHighlighter::default(); | ||
125 | } | 130 | } |
126 | _ => (), | 131 | _ => (), |
127 | } | 132 | } |
@@ -148,20 +153,6 @@ pub(crate) fn highlight( | |||
148 | WalkEvent::Leave(_) => continue, | 153 | WalkEvent::Leave(_) => continue, |
149 | }; | 154 | }; |
150 | 155 | ||
151 | // check if in matcher part of a macro_rules rule | ||
152 | if let Some((_, Some(ref mut state))) = current_macro_call { | ||
153 | if let Some(tok) = element.as_token() { | ||
154 | if matches!( | ||
155 | update_macro_rules_state(tok, state), | ||
156 | RuleState::Matcher | RuleState::Expander | ||
157 | ) { | ||
158 | if skip_metavariables(element.clone()) { | ||
159 | continue; | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | } | ||
164 | |||
165 | let range = element.text_range(); | 156 | let range = element.text_range(); |
166 | 157 | ||
167 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { | 158 | let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { |
@@ -173,29 +164,9 @@ pub(crate) fn highlight( | |||
173 | let token = sema.descend_into_macros(token.clone()); | 164 | let token = sema.descend_into_macros(token.clone()); |
174 | let parent = token.parent(); | 165 | let parent = token.parent(); |
175 | 166 | ||
176 | // Check if macro takes a format string and remember it for highlighting later. | 167 | format_string_highlighter.check_for_format_string(&parent); |
177 | // The macros that accept a format string expand to a compiler builtin macros | 168 | if let Some(tok) = element.as_token() { |
178 | // `format_args` and `format_args_nl`. | 169 | macro_rules_highlighter.advance(tok); |
179 | if let Some(name) = parent | ||
180 | .parent() | ||
181 | .and_then(ast::MacroCall::cast) | ||
182 | .and_then(|mc| mc.path()) | ||
183 | .and_then(|p| p.segment()) | ||
184 | .and_then(|s| s.name_ref()) | ||
185 | { | ||
186 | match name.text().as_str() { | ||
187 | "format_args" | "format_args_nl" => { | ||
188 | format_string = parent | ||
189 | .children_with_tokens() | ||
190 | .filter(|t| t.kind() != WHITESPACE) | ||
191 | .nth(1) | ||
192 | .filter(|e| { | ||
193 | ast::String::can_cast(e.kind()) | ||
194 | || ast::RawString::can_cast(e.kind()) | ||
195 | }) | ||
196 | } | ||
197 | _ => {} | ||
198 | } | ||
199 | } | 170 | } |
200 | 171 | ||
201 | // We only care Name and Name_ref | 172 | // We only care Name and Name_ref |
@@ -214,31 +185,20 @@ pub(crate) fn highlight( | |||
214 | } | 185 | } |
215 | } | 186 | } |
216 | 187 | ||
217 | let is_format_string = format_string.as_ref() == Some(&element_to_highlight); | ||
218 | |||
219 | if let Some((highlight, binding_hash)) = highlight_element( | 188 | if let Some((highlight, binding_hash)) = highlight_element( |
220 | &sema, | 189 | &sema, |
221 | &mut bindings_shadow_count, | 190 | &mut bindings_shadow_count, |
222 | syntactic_name_ref_highlighting, | 191 | syntactic_name_ref_highlighting, |
223 | element_to_highlight.clone(), | 192 | element_to_highlight.clone(), |
224 | ) { | 193 | ) { |
225 | stack.add(HighlightedRange { range, highlight, binding_hash }); | 194 | if macro_rules_highlighter.highlight(element_to_highlight.clone()).is_none() { |
195 | stack.add(HighlightedRange { range, highlight, binding_hash }); | ||
196 | } | ||
197 | |||
226 | if let Some(string) = | 198 | if let Some(string) = |
227 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) | 199 | element_to_highlight.as_token().cloned().and_then(ast::String::cast) |
228 | { | 200 | { |
229 | if is_format_string { | 201 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
230 | stack.push(); | ||
231 | string.lex_format_specifier(|piece_range, kind| { | ||
232 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
233 | stack.add(HighlightedRange { | ||
234 | range: piece_range + range.start(), | ||
235 | highlight: highlight.into(), | ||
236 | binding_hash: None, | ||
237 | }); | ||
238 | } | ||
239 | }); | ||
240 | stack.pop(); | ||
241 | } | ||
242 | // Highlight escape sequences | 202 | // Highlight escape sequences |
243 | if let Some(char_ranges) = string.char_ranges() { | 203 | if let Some(char_ranges) = string.char_ranges() { |
244 | stack.push(); | 204 | stack.push(); |
@@ -256,19 +216,7 @@ pub(crate) fn highlight( | |||
256 | } else if let Some(string) = | 216 | } else if let Some(string) = |
257 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) | 217 | element_to_highlight.as_token().cloned().and_then(ast::RawString::cast) |
258 | { | 218 | { |
259 | if is_format_string { | 219 | format_string_highlighter.highlight_format_string(&mut stack, &string, range); |
260 | stack.push(); | ||
261 | string.lex_format_specifier(|piece_range, kind| { | ||
262 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
263 | stack.add(HighlightedRange { | ||
264 | range: piece_range + range.start(), | ||
265 | highlight: highlight.into(), | ||
266 | binding_hash: None, | ||
267 | }); | ||
268 | } | ||
269 | }); | ||
270 | stack.pop(); | ||
271 | } | ||
272 | } | 220 | } |
273 | } | 221 | } |
274 | } | 222 | } |
@@ -436,24 +384,6 @@ impl HighlightedRangeStack { | |||
436 | } | 384 | } |
437 | } | 385 | } |
438 | 386 | ||
439 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
440 | Some(match kind { | ||
441 | FormatSpecifier::Open | ||
442 | | FormatSpecifier::Close | ||
443 | | FormatSpecifier::Colon | ||
444 | | FormatSpecifier::Fill | ||
445 | | FormatSpecifier::Align | ||
446 | | FormatSpecifier::Sign | ||
447 | | FormatSpecifier::NumberSign | ||
448 | | FormatSpecifier::DollarSign | ||
449 | | FormatSpecifier::Dot | ||
450 | | FormatSpecifier::Asterisk | ||
451 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
452 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
453 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
454 | }) | ||
455 | } | ||
456 | |||
457 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { | 387 | fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> { |
458 | let path = macro_call.path()?; | 388 | let path = macro_call.path()?; |
459 | let name_ref = path.segment()?.name_ref()?; | 389 | let name_ref = path.segment()?.name_ref()?; |
@@ -513,7 +443,7 @@ fn highlight_element( | |||
513 | // Highlight definitions depending on the "type" of the definition. | 443 | // Highlight definitions depending on the "type" of the definition. |
514 | NAME => { | 444 | NAME => { |
515 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); | 445 | let name = element.into_node().and_then(ast::Name::cast).unwrap(); |
516 | let name_kind = classify_name(sema, &name); | 446 | let name_kind = NameClass::classify(sema, &name); |
517 | 447 | ||
518 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { | 448 | if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { |
519 | if let Some(name) = local.name(db) { | 449 | if let Some(name) = local.name(db) { |
@@ -529,9 +459,9 @@ fn highlight_element( | |||
529 | highlight_def(db, def) | HighlightModifier::Definition | 459 | highlight_def(db, def) | HighlightModifier::Definition |
530 | } | 460 | } |
531 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), | 461 | Some(NameClass::ConstReference(def)) => highlight_def(db, def), |
532 | Some(NameClass::FieldShorthand { field, .. }) => { | 462 | Some(NameClass::PatFieldShorthand { field_ref, .. }) => { |
533 | let mut h = HighlightTag::Field.into(); | 463 | let mut h = HighlightTag::Field.into(); |
534 | if let Definition::Field(field) = field { | 464 | if let Definition::Field(field) = field_ref { |
535 | if let VariantDef::Union(_) = field.parent_def(db) { | 465 | if let VariantDef::Union(_) = field.parent_def(db) { |
536 | h |= HighlightModifier::Unsafe; | 466 | h |= HighlightModifier::Unsafe; |
537 | } | 467 | } |
@@ -550,7 +480,7 @@ fn highlight_element( | |||
550 | NAME_REF => { | 480 | NAME_REF => { |
551 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); | 481 | let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); |
552 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { | 482 | highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { |
553 | match classify_name_ref(sema, &name_ref) { | 483 | match NameRefClass::classify(sema, &name_ref) { |
554 | Some(name_kind) => match name_kind { | 484 | Some(name_kind) => match name_kind { |
555 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), | 485 | NameRefClass::ExternCrate(_) => HighlightTag::Module.into(), |
556 | NameRefClass::Definition(def) => { | 486 | NameRefClass::Definition(def) => { |
@@ -934,99 +864,3 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas | |||
934 | _ => default.into(), | 864 | _ => default.into(), |
935 | } | 865 | } |
936 | } | 866 | } |
937 | |||
938 | struct MacroMatcherParseState { | ||
939 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
940 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
941 | paren_level: usize, | ||
942 | rule_state: RuleState, | ||
943 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
944 | in_invoc_body: bool, | ||
945 | } | ||
946 | |||
947 | impl MacroMatcherParseState { | ||
948 | fn new() -> Self { | ||
949 | MacroMatcherParseState { | ||
950 | paren_ty: None, | ||
951 | paren_level: 0, | ||
952 | in_invoc_body: false, | ||
953 | rule_state: RuleState::None, | ||
954 | } | ||
955 | } | ||
956 | } | ||
957 | |||
958 | #[derive(Copy, Clone, PartialEq)] | ||
959 | enum RuleState { | ||
960 | Matcher, | ||
961 | Expander, | ||
962 | Between, | ||
963 | None, | ||
964 | } | ||
965 | |||
966 | impl RuleState { | ||
967 | fn transition(&mut self) { | ||
968 | *self = match self { | ||
969 | RuleState::Matcher => RuleState::Between, | ||
970 | RuleState::Expander => RuleState::None, | ||
971 | RuleState::Between => RuleState::Expander, | ||
972 | RuleState::None => RuleState::Matcher, | ||
973 | }; | ||
974 | } | ||
975 | } | ||
976 | |||
977 | fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState { | ||
978 | if !state.in_invoc_body { | ||
979 | if tok.kind() == T!['{'] { | ||
980 | state.in_invoc_body = true; | ||
981 | } | ||
982 | return state.rule_state; | ||
983 | } | ||
984 | |||
985 | match state.paren_ty { | ||
986 | Some((open, close)) => { | ||
987 | if tok.kind() == open { | ||
988 | state.paren_level += 1; | ||
989 | } else if tok.kind() == close { | ||
990 | state.paren_level -= 1; | ||
991 | if state.paren_level == 0 { | ||
992 | let res = state.rule_state; | ||
993 | state.rule_state.transition(); | ||
994 | state.paren_ty = None; | ||
995 | return res; | ||
996 | } | ||
997 | } | ||
998 | } | ||
999 | None => { | ||
1000 | match tok.kind() { | ||
1001 | T!['('] => { | ||
1002 | state.paren_ty = Some((T!['('], T![')'])); | ||
1003 | } | ||
1004 | T!['{'] => { | ||
1005 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
1006 | } | ||
1007 | T!['['] => { | ||
1008 | state.paren_ty = Some((T!['['], T![']'])); | ||
1009 | } | ||
1010 | _ => (), | ||
1011 | } | ||
1012 | if state.paren_ty.is_some() { | ||
1013 | state.paren_level = 1; | ||
1014 | state.rule_state.transition(); | ||
1015 | } | ||
1016 | } | ||
1017 | } | ||
1018 | state.rule_state | ||
1019 | } | ||
1020 | |||
1021 | fn skip_metavariables(element: SyntaxElement) -> bool { | ||
1022 | let tok = match element.as_token() { | ||
1023 | Some(tok) => tok, | ||
1024 | None => return false, | ||
1025 | }; | ||
1026 | let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]); | ||
1027 | match tok.kind() { | ||
1028 | IDENT if is_fragment() => true, | ||
1029 | kind if kind.is_keyword() && is_fragment() => true, | ||
1030 | _ => false, | ||
1031 | } | ||
1032 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs new file mode 100644 index 000000000..71bde24f0 --- /dev/null +++ b/crates/ide/src/syntax_highlighting/format.rs | |||
@@ -0,0 +1,78 @@ | |||
1 | //! Syntax highlighting for format macro strings. | ||
2 | use syntax::{ | ||
3 | ast::{self, FormatSpecifier, HasFormatSpecifier}, | ||
4 | AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, | ||
5 | }; | ||
6 | |||
7 | use crate::{syntax_highlighting::HighlightedRangeStack, HighlightTag, HighlightedRange}; | ||
8 | |||
9 | #[derive(Default)] | ||
10 | pub(super) struct FormatStringHighlighter { | ||
11 | format_string: Option<SyntaxElement>, | ||
12 | } | ||
13 | |||
14 | impl FormatStringHighlighter { | ||
15 | pub(super) fn check_for_format_string(&mut self, parent: &SyntaxNode) { | ||
16 | // Check if macro takes a format string and remember it for highlighting later. | ||
17 | // The macros that accept a format string expand to a compiler builtin macros | ||
18 | // `format_args` and `format_args_nl`. | ||
19 | if let Some(name) = parent | ||
20 | .parent() | ||
21 | .and_then(ast::MacroCall::cast) | ||
22 | .and_then(|mc| mc.path()) | ||
23 | .and_then(|p| p.segment()) | ||
24 | .and_then(|s| s.name_ref()) | ||
25 | { | ||
26 | match name.text().as_str() { | ||
27 | "format_args" | "format_args_nl" => { | ||
28 | self.format_string = parent | ||
29 | .children_with_tokens() | ||
30 | .filter(|t| t.kind() != SyntaxKind::WHITESPACE) | ||
31 | .nth(1) | ||
32 | .filter(|e| { | ||
33 | ast::String::can_cast(e.kind()) || ast::RawString::can_cast(e.kind()) | ||
34 | }) | ||
35 | } | ||
36 | _ => {} | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | pub(super) fn highlight_format_string( | ||
41 | &self, | ||
42 | range_stack: &mut HighlightedRangeStack, | ||
43 | string: &impl HasFormatSpecifier, | ||
44 | range: TextRange, | ||
45 | ) { | ||
46 | if self.format_string.as_ref() == Some(&SyntaxElement::from(string.syntax().clone())) { | ||
47 | range_stack.push(); | ||
48 | string.lex_format_specifier(|piece_range, kind| { | ||
49 | if let Some(highlight) = highlight_format_specifier(kind) { | ||
50 | range_stack.add(HighlightedRange { | ||
51 | range: piece_range + range.start(), | ||
52 | highlight: highlight.into(), | ||
53 | binding_hash: None, | ||
54 | }); | ||
55 | } | ||
56 | }); | ||
57 | range_stack.pop(); | ||
58 | } | ||
59 | } | ||
60 | } | ||
61 | |||
62 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | ||
63 | Some(match kind { | ||
64 | FormatSpecifier::Open | ||
65 | | FormatSpecifier::Close | ||
66 | | FormatSpecifier::Colon | ||
67 | | FormatSpecifier::Fill | ||
68 | | FormatSpecifier::Align | ||
69 | | FormatSpecifier::Sign | ||
70 | | FormatSpecifier::NumberSign | ||
71 | | FormatSpecifier::DollarSign | ||
72 | | FormatSpecifier::Dot | ||
73 | | FormatSpecifier::Asterisk | ||
74 | | FormatSpecifier::QuestionMark => HighlightTag::FormatSpecifier, | ||
75 | FormatSpecifier::Integer | FormatSpecifier::Zero => HighlightTag::NumericLiteral, | ||
76 | FormatSpecifier::Identifier => HighlightTag::Local, | ||
77 | }) | ||
78 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/macro_rules.rs b/crates/ide/src/syntax_highlighting/macro_rules.rs new file mode 100644 index 000000000..4462af47e --- /dev/null +++ b/crates/ide/src/syntax_highlighting/macro_rules.rs | |||
@@ -0,0 +1,129 @@ | |||
1 | //! Syntax highlighting for macro_rules!. | ||
2 | use syntax::{SyntaxElement, SyntaxKind, SyntaxToken, TextRange, T}; | ||
3 | |||
4 | use crate::{HighlightTag, HighlightedRange}; | ||
5 | |||
6 | #[derive(Default)] | ||
7 | pub(super) struct MacroRulesHighlighter { | ||
8 | state: Option<MacroMatcherParseState>, | ||
9 | } | ||
10 | |||
11 | impl MacroRulesHighlighter { | ||
12 | pub(super) fn init(&mut self) { | ||
13 | self.state = Some(MacroMatcherParseState::default()); | ||
14 | } | ||
15 | |||
16 | pub(super) fn advance(&mut self, token: &SyntaxToken) { | ||
17 | if let Some(state) = self.state.as_mut() { | ||
18 | update_macro_rules_state(state, token); | ||
19 | } | ||
20 | } | ||
21 | |||
22 | pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HighlightedRange> { | ||
23 | if let Some(state) = self.state.as_ref() { | ||
24 | if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) { | ||
25 | if let Some(range) = is_metavariable(element) { | ||
26 | return Some(HighlightedRange { | ||
27 | range, | ||
28 | highlight: HighlightTag::UnresolvedReference.into(), | ||
29 | binding_hash: None, | ||
30 | }); | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | None | ||
35 | } | ||
36 | } | ||
37 | |||
38 | struct MacroMatcherParseState { | ||
39 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
40 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
41 | paren_level: usize, | ||
42 | rule_state: RuleState, | ||
43 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
44 | in_invoc_body: bool, | ||
45 | } | ||
46 | |||
47 | impl Default for MacroMatcherParseState { | ||
48 | fn default() -> Self { | ||
49 | MacroMatcherParseState { | ||
50 | paren_ty: None, | ||
51 | paren_level: 0, | ||
52 | in_invoc_body: false, | ||
53 | rule_state: RuleState::None, | ||
54 | } | ||
55 | } | ||
56 | } | ||
57 | |||
58 | #[derive(Copy, Clone, Debug, PartialEq)] | ||
59 | enum RuleState { | ||
60 | Matcher, | ||
61 | Expander, | ||
62 | Between, | ||
63 | None, | ||
64 | } | ||
65 | |||
66 | impl RuleState { | ||
67 | fn transition(&mut self) { | ||
68 | *self = match self { | ||
69 | RuleState::Matcher => RuleState::Between, | ||
70 | RuleState::Expander => RuleState::None, | ||
71 | RuleState::Between => RuleState::Expander, | ||
72 | RuleState::None => RuleState::Matcher, | ||
73 | }; | ||
74 | } | ||
75 | } | ||
76 | |||
77 | fn update_macro_rules_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) { | ||
78 | if !state.in_invoc_body { | ||
79 | if tok.kind() == T!['{'] { | ||
80 | state.in_invoc_body = true; | ||
81 | } | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | match state.paren_ty { | ||
86 | Some((open, close)) => { | ||
87 | if tok.kind() == open { | ||
88 | state.paren_level += 1; | ||
89 | } else if tok.kind() == close { | ||
90 | state.paren_level -= 1; | ||
91 | if state.paren_level == 0 { | ||
92 | state.rule_state.transition(); | ||
93 | state.paren_ty = None; | ||
94 | } | ||
95 | } | ||
96 | } | ||
97 | None => { | ||
98 | match tok.kind() { | ||
99 | T!['('] => { | ||
100 | state.paren_ty = Some((T!['('], T![')'])); | ||
101 | } | ||
102 | T!['{'] => { | ||
103 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
104 | } | ||
105 | T!['['] => { | ||
106 | state.paren_ty = Some((T!['['], T![']'])); | ||
107 | } | ||
108 | _ => (), | ||
109 | } | ||
110 | if state.paren_ty.is_some() { | ||
111 | state.paren_level = 1; | ||
112 | state.rule_state.transition(); | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | } | ||
117 | |||
118 | fn is_metavariable(element: SyntaxElement) -> Option<TextRange> { | ||
119 | let tok = element.as_token()?; | ||
120 | match tok.kind() { | ||
121 | kind if kind == SyntaxKind::IDENT || kind.is_keyword() => { | ||
122 | if let Some(_dollar) = tok.prev_token().filter(|t| t.kind() == SyntaxKind::DOLLAR) { | ||
123 | return Some(tok.text_range()); | ||
124 | } | ||
125 | } | ||
126 | _ => (), | ||
127 | }; | ||
128 | None | ||
129 | } | ||
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs index f8c7aa491..201a3d6fa 100644 --- a/crates/ide_db/src/defs.rs +++ b/crates/ide_db/src/defs.rs | |||
@@ -81,146 +81,152 @@ impl Definition { | |||
81 | pub enum NameClass { | 81 | pub enum NameClass { |
82 | ExternCrate(Crate), | 82 | ExternCrate(Crate), |
83 | Definition(Definition), | 83 | Definition(Definition), |
84 | /// `None` in `if let None = Some(82) {}` | 84 | /// `None` in `if let None = Some(82) {}`. |
85 | ConstReference(Definition), | 85 | ConstReference(Definition), |
86 | FieldShorthand { | 86 | /// `field` in `if let Foo { field } = foo`. |
87 | local: Local, | 87 | PatFieldShorthand { |
88 | field: Definition, | 88 | local_def: Local, |
89 | field_ref: Definition, | ||
89 | }, | 90 | }, |
90 | } | 91 | } |
91 | 92 | ||
92 | impl NameClass { | 93 | impl NameClass { |
93 | pub fn into_definition(self, db: &dyn HirDatabase) -> Option<Definition> { | 94 | /// `Definition` defined by this name. |
94 | Some(match self { | 95 | pub fn defined(self, db: &dyn HirDatabase) -> Option<Definition> { |
96 | let res = match self { | ||
95 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), | 97 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), |
96 | NameClass::Definition(it) => it, | 98 | NameClass::Definition(it) => it, |
97 | NameClass::ConstReference(_) => return None, | 99 | NameClass::ConstReference(_) => return None, |
98 | NameClass::FieldShorthand { local, field: _ } => Definition::Local(local), | 100 | NameClass::PatFieldShorthand { local_def, field_ref: _ } => { |
99 | }) | 101 | Definition::Local(local_def) |
102 | } | ||
103 | }; | ||
104 | Some(res) | ||
100 | } | 105 | } |
101 | 106 | ||
102 | pub fn definition(self, db: &dyn HirDatabase) -> Definition { | 107 | /// `Definition` referenced or defined by this name. |
108 | pub fn referenced_or_defined(self, db: &dyn HirDatabase) -> Definition { | ||
103 | match self { | 109 | match self { |
104 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), | 110 | NameClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), |
105 | NameClass::Definition(it) | NameClass::ConstReference(it) => it, | 111 | NameClass::Definition(it) | NameClass::ConstReference(it) => it, |
106 | NameClass::FieldShorthand { local: _, field } => field, | 112 | NameClass::PatFieldShorthand { local_def: _, field_ref } => field_ref, |
107 | } | 113 | } |
108 | } | 114 | } |
109 | } | ||
110 | 115 | ||
111 | pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> { | 116 | pub fn classify(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option<NameClass> { |
112 | let _p = profile::span("classify_name"); | 117 | let _p = profile::span("classify_name"); |
113 | 118 | ||
114 | let parent = name.syntax().parent()?; | 119 | let parent = name.syntax().parent()?; |
115 | 120 | ||
116 | if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) { | 121 | if let Some(bind_pat) = ast::IdentPat::cast(parent.clone()) { |
117 | if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) { | 122 | if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) { |
118 | return Some(NameClass::ConstReference(Definition::ModuleDef(def))); | 123 | return Some(NameClass::ConstReference(Definition::ModuleDef(def))); |
124 | } | ||
119 | } | 125 | } |
120 | } | ||
121 | 126 | ||
122 | match_ast! { | 127 | match_ast! { |
123 | match parent { | 128 | match parent { |
124 | ast::Rename(it) => { | 129 | ast::Rename(it) => { |
125 | if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) { | 130 | if let Some(use_tree) = it.syntax().parent().and_then(ast::UseTree::cast) { |
126 | let path = use_tree.path()?; | 131 | let path = use_tree.path()?; |
127 | let path_segment = path.segment()?; | 132 | let path_segment = path.segment()?; |
128 | let name_ref_class = path_segment | 133 | let name_ref_class = path_segment |
129 | .name_ref() | 134 | .name_ref() |
130 | // The rename might be from a `self` token, so fallback to the name higher | 135 | // The rename might be from a `self` token, so fallback to the name higher |
131 | // in the use tree. | 136 | // in the use tree. |
132 | .or_else(||{ | 137 | .or_else(||{ |
133 | if path_segment.self_token().is_none() { | 138 | if path_segment.self_token().is_none() { |
134 | return None; | 139 | return None; |
135 | } | 140 | } |
136 | 141 | ||
137 | let use_tree = use_tree | 142 | let use_tree = use_tree |
138 | .syntax() | 143 | .syntax() |
139 | .parent() | 144 | .parent() |
140 | .as_ref() | 145 | .as_ref() |
141 | // Skip over UseTreeList | 146 | // Skip over UseTreeList |
142 | .and_then(SyntaxNode::parent) | 147 | .and_then(SyntaxNode::parent) |
143 | .and_then(ast::UseTree::cast)?; | 148 | .and_then(ast::UseTree::cast)?; |
144 | let path = use_tree.path()?; | 149 | let path = use_tree.path()?; |
145 | let path_segment = path.segment()?; | 150 | let path_segment = path.segment()?; |
146 | path_segment.name_ref() | 151 | path_segment.name_ref() |
147 | }) | 152 | }) |
148 | .and_then(|name_ref| classify_name_ref(sema, &name_ref))?; | 153 | .and_then(|name_ref| NameRefClass::classify(sema, &name_ref))?; |
149 | 154 | ||
150 | Some(NameClass::Definition(name_ref_class.definition(sema.db))) | 155 | Some(NameClass::Definition(name_ref_class.referenced(sema.db))) |
151 | } else { | 156 | } else { |
152 | let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?; | 157 | let extern_crate = it.syntax().parent().and_then(ast::ExternCrate::cast)?; |
153 | let resolved = sema.resolve_extern_crate(&extern_crate)?; | 158 | let resolved = sema.resolve_extern_crate(&extern_crate)?; |
154 | Some(NameClass::ExternCrate(resolved)) | 159 | Some(NameClass::ExternCrate(resolved)) |
155 | } | 160 | } |
156 | }, | 161 | }, |
157 | ast::IdentPat(it) => { | 162 | ast::IdentPat(it) => { |
158 | let local = sema.to_def(&it)?; | 163 | let local = sema.to_def(&it)?; |
159 | 164 | ||
160 | if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) { | 165 | if let Some(record_pat_field) = it.syntax().parent().and_then(ast::RecordPatField::cast) { |
161 | if record_pat_field.name_ref().is_none() { | 166 | if record_pat_field.name_ref().is_none() { |
162 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { | 167 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { |
163 | let field = Definition::Field(field); | 168 | let field = Definition::Field(field); |
164 | return Some(NameClass::FieldShorthand { local, field }); | 169 | return Some(NameClass::PatFieldShorthand { local_def: local, field_ref: field }); |
170 | } | ||
165 | } | 171 | } |
166 | } | 172 | } |
167 | } | ||
168 | 173 | ||
169 | Some(NameClass::Definition(Definition::Local(local))) | 174 | Some(NameClass::Definition(Definition::Local(local))) |
170 | }, | 175 | }, |
171 | ast::RecordField(it) => { | 176 | ast::RecordField(it) => { |
172 | let field: hir::Field = sema.to_def(&it)?; | 177 | let field: hir::Field = sema.to_def(&it)?; |
173 | Some(NameClass::Definition(Definition::Field(field))) | 178 | Some(NameClass::Definition(Definition::Field(field))) |
174 | }, | 179 | }, |
175 | ast::Module(it) => { | 180 | ast::Module(it) => { |
176 | let def = sema.to_def(&it)?; | 181 | let def = sema.to_def(&it)?; |
177 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 182 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
178 | }, | 183 | }, |
179 | ast::Struct(it) => { | 184 | ast::Struct(it) => { |
180 | let def: hir::Struct = sema.to_def(&it)?; | 185 | let def: hir::Struct = sema.to_def(&it)?; |
181 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 186 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
182 | }, | 187 | }, |
183 | ast::Union(it) => { | 188 | ast::Union(it) => { |
184 | let def: hir::Union = sema.to_def(&it)?; | 189 | let def: hir::Union = sema.to_def(&it)?; |
185 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 190 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
186 | }, | 191 | }, |
187 | ast::Enum(it) => { | 192 | ast::Enum(it) => { |
188 | let def: hir::Enum = sema.to_def(&it)?; | 193 | let def: hir::Enum = sema.to_def(&it)?; |
189 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 194 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
190 | }, | 195 | }, |
191 | ast::Trait(it) => { | 196 | ast::Trait(it) => { |
192 | let def: hir::Trait = sema.to_def(&it)?; | 197 | let def: hir::Trait = sema.to_def(&it)?; |
193 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 198 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
194 | }, | 199 | }, |
195 | ast::Static(it) => { | 200 | ast::Static(it) => { |
196 | let def: hir::Static = sema.to_def(&it)?; | 201 | let def: hir::Static = sema.to_def(&it)?; |
197 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 202 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
198 | }, | 203 | }, |
199 | ast::Variant(it) => { | 204 | ast::Variant(it) => { |
200 | let def: hir::EnumVariant = sema.to_def(&it)?; | 205 | let def: hir::EnumVariant = sema.to_def(&it)?; |
201 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 206 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
202 | }, | 207 | }, |
203 | ast::Fn(it) => { | 208 | ast::Fn(it) => { |
204 | let def: hir::Function = sema.to_def(&it)?; | 209 | let def: hir::Function = sema.to_def(&it)?; |
205 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 210 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
206 | }, | 211 | }, |
207 | ast::Const(it) => { | 212 | ast::Const(it) => { |
208 | let def: hir::Const = sema.to_def(&it)?; | 213 | let def: hir::Const = sema.to_def(&it)?; |
209 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 214 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
210 | }, | 215 | }, |
211 | ast::TypeAlias(it) => { | 216 | ast::TypeAlias(it) => { |
212 | let def: hir::TypeAlias = sema.to_def(&it)?; | 217 | let def: hir::TypeAlias = sema.to_def(&it)?; |
213 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) | 218 | Some(NameClass::Definition(Definition::ModuleDef(def.into()))) |
214 | }, | 219 | }, |
215 | ast::MacroCall(it) => { | 220 | ast::MacroCall(it) => { |
216 | let def = sema.to_def(&it)?; | 221 | let def = sema.to_def(&it)?; |
217 | Some(NameClass::Definition(Definition::Macro(def))) | 222 | Some(NameClass::Definition(Definition::Macro(def))) |
218 | }, | 223 | }, |
219 | ast::TypeParam(it) => { | 224 | ast::TypeParam(it) => { |
220 | let def = sema.to_def(&it)?; | 225 | let def = sema.to_def(&it)?; |
221 | Some(NameClass::Definition(Definition::TypeParam(def))) | 226 | Some(NameClass::Definition(Definition::TypeParam(def))) |
222 | }, | 227 | }, |
223 | _ => None, | 228 | _ => None, |
229 | } | ||
224 | } | 230 | } |
225 | } | 231 | } |
226 | } | 232 | } |
@@ -229,102 +235,109 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option | |||
229 | pub enum NameRefClass { | 235 | pub enum NameRefClass { |
230 | ExternCrate(Crate), | 236 | ExternCrate(Crate), |
231 | Definition(Definition), | 237 | Definition(Definition), |
232 | FieldShorthand { local: Local, field: Definition }, | 238 | FieldShorthand { local_ref: Local, field_ref: Definition }, |
233 | } | 239 | } |
234 | 240 | ||
235 | impl NameRefClass { | 241 | impl NameRefClass { |
236 | pub fn definition(self, db: &dyn HirDatabase) -> Definition { | 242 | /// `Definition`, which this name refers to. |
243 | pub fn referenced(self, db: &dyn HirDatabase) -> Definition { | ||
237 | match self { | 244 | match self { |
238 | NameRefClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), | 245 | NameRefClass::ExternCrate(krate) => Definition::ModuleDef(krate.root_module(db).into()), |
239 | NameRefClass::Definition(def) => def, | 246 | NameRefClass::Definition(def) => def, |
240 | NameRefClass::FieldShorthand { local, field: _ } => Definition::Local(local), | 247 | NameRefClass::FieldShorthand { local_ref, field_ref: _ } => { |
248 | // FIXME: this is inherently ambiguous -- this name refers to | ||
249 | // two different defs.... | ||
250 | Definition::Local(local_ref) | ||
251 | } | ||
241 | } | 252 | } |
242 | } | 253 | } |
243 | } | ||
244 | 254 | ||
245 | // Note: we don't have unit-tests for this rather important function. | 255 | // Note: we don't have unit-tests for this rather important function. |
246 | // It is primarily exercised via goto definition tests in `ide`. | 256 | // It is primarily exercised via goto definition tests in `ide`. |
247 | pub fn classify_name_ref( | 257 | pub fn classify( |
248 | sema: &Semantics<RootDatabase>, | 258 | sema: &Semantics<RootDatabase>, |
249 | name_ref: &ast::NameRef, | 259 | name_ref: &ast::NameRef, |
250 | ) -> Option<NameRefClass> { | 260 | ) -> Option<NameRefClass> { |
251 | let _p = profile::span("classify_name_ref"); | 261 | let _p = profile::span("classify_name_ref"); |
252 | 262 | ||
253 | let parent = name_ref.syntax().parent()?; | 263 | let parent = name_ref.syntax().parent()?; |
254 | 264 | ||
255 | if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { | 265 | if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { |
256 | if let Some(func) = sema.resolve_method_call(&method_call) { | 266 | if let Some(func) = sema.resolve_method_call(&method_call) { |
257 | return Some(NameRefClass::Definition(Definition::ModuleDef(func.into()))); | 267 | return Some(NameRefClass::Definition(Definition::ModuleDef(func.into()))); |
268 | } | ||
258 | } | 269 | } |
259 | } | ||
260 | 270 | ||
261 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { | 271 | if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { |
262 | if let Some(field) = sema.resolve_field(&field_expr) { | 272 | if let Some(field) = sema.resolve_field(&field_expr) { |
263 | return Some(NameRefClass::Definition(Definition::Field(field))); | 273 | return Some(NameRefClass::Definition(Definition::Field(field))); |
274 | } | ||
264 | } | 275 | } |
265 | } | ||
266 | 276 | ||
267 | if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) { | 277 | if let Some(record_field) = ast::RecordExprField::for_field_name(name_ref) { |
268 | if let Some((field, local)) = sema.resolve_record_field(&record_field) { | 278 | if let Some((field, local)) = sema.resolve_record_field(&record_field) { |
269 | let field = Definition::Field(field); | 279 | let field = Definition::Field(field); |
270 | let res = match local { | 280 | let res = match local { |
271 | None => NameRefClass::Definition(field), | 281 | None => NameRefClass::Definition(field), |
272 | Some(local) => NameRefClass::FieldShorthand { field, local }, | 282 | Some(local) => { |
273 | }; | 283 | NameRefClass::FieldShorthand { field_ref: field, local_ref: local } |
274 | return Some(res); | 284 | } |
285 | }; | ||
286 | return Some(res); | ||
287 | } | ||
275 | } | 288 | } |
276 | } | ||
277 | 289 | ||
278 | if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) { | 290 | if let Some(record_pat_field) = ast::RecordPatField::cast(parent.clone()) { |
279 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { | 291 | if let Some(field) = sema.resolve_record_pat_field(&record_pat_field) { |
280 | let field = Definition::Field(field); | 292 | let field = Definition::Field(field); |
281 | return Some(NameRefClass::Definition(field)); | 293 | return Some(NameRefClass::Definition(field)); |
294 | } | ||
282 | } | 295 | } |
283 | } | ||
284 | 296 | ||
285 | if ast::AssocTypeArg::cast(parent.clone()).is_some() { | 297 | if ast::AssocTypeArg::cast(parent.clone()).is_some() { |
286 | // `Trait<Assoc = Ty>` | 298 | // `Trait<Assoc = Ty>` |
287 | // ^^^^^ | 299 | // ^^^^^ |
288 | let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; | 300 | let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; |
289 | let resolved = sema.resolve_path(&path)?; | 301 | let resolved = sema.resolve_path(&path)?; |
290 | if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved { | 302 | if let PathResolution::Def(ModuleDef::Trait(tr)) = resolved { |
291 | if let Some(ty) = tr | 303 | if let Some(ty) = tr |
292 | .items(sema.db) | 304 | .items(sema.db) |
293 | .iter() | 305 | .iter() |
294 | .filter_map(|assoc| match assoc { | 306 | .filter_map(|assoc| match assoc { |
295 | hir::AssocItem::TypeAlias(it) => Some(*it), | 307 | hir::AssocItem::TypeAlias(it) => Some(*it), |
296 | _ => None, | 308 | _ => None, |
297 | }) | 309 | }) |
298 | .find(|alias| alias.name(sema.db).to_string() == **name_ref.text()) | 310 | .find(|alias| alias.name(sema.db).to_string() == **name_ref.text()) |
299 | { | 311 | { |
300 | return Some(NameRefClass::Definition(Definition::ModuleDef( | 312 | return Some(NameRefClass::Definition(Definition::ModuleDef( |
301 | ModuleDef::TypeAlias(ty), | 313 | ModuleDef::TypeAlias(ty), |
302 | ))); | 314 | ))); |
315 | } | ||
303 | } | 316 | } |
304 | } | 317 | } |
305 | } | ||
306 | 318 | ||
307 | if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { | 319 | if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { |
308 | if let Some(path) = macro_call.path() { | 320 | if let Some(path) = macro_call.path() { |
309 | if path.qualifier().is_none() { | 321 | if path.qualifier().is_none() { |
310 | // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment | 322 | // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment |
311 | // paths are handled below (allowing `log<|>::info!` to resolve to the log crate). | 323 | // paths are handled below (allowing `log<|>::info!` to resolve to the log crate). |
312 | if let Some(macro_def) = sema.resolve_macro_call(¯o_call) { | 324 | if let Some(macro_def) = sema.resolve_macro_call(¯o_call) { |
313 | return Some(NameRefClass::Definition(Definition::Macro(macro_def))); | 325 | return Some(NameRefClass::Definition(Definition::Macro(macro_def))); |
326 | } | ||
314 | } | 327 | } |
315 | } | 328 | } |
316 | } | 329 | } |
317 | } | ||
318 | 330 | ||
319 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { | 331 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { |
320 | if let Some(resolved) = sema.resolve_path(&path) { | 332 | if let Some(resolved) = sema.resolve_path(&path) { |
321 | return Some(NameRefClass::Definition(resolved.into())); | 333 | return Some(NameRefClass::Definition(resolved.into())); |
334 | } | ||
322 | } | 335 | } |
323 | } | ||
324 | 336 | ||
325 | let extern_crate = ast::ExternCrate::cast(parent)?; | 337 | let extern_crate = ast::ExternCrate::cast(parent)?; |
326 | let resolved = sema.resolve_extern_crate(&extern_crate)?; | 338 | let resolved = sema.resolve_extern_crate(&extern_crate)?; |
327 | Some(NameRefClass::ExternCrate(resolved)) | 339 | Some(NameRefClass::ExternCrate(resolved)) |
340 | } | ||
328 | } | 341 | } |
329 | 342 | ||
330 | impl From<PathResolution> for Definition { | 343 | impl From<PathResolution> for Definition { |
diff --git a/crates/ide_db/src/imports_locator.rs b/crates/ide_db/src/imports_locator.rs index ed67e3553..df74be00b 100644 --- a/crates/ide_db/src/imports_locator.rs +++ b/crates/ide_db/src/imports_locator.rs | |||
@@ -5,7 +5,7 @@ use hir::{Crate, MacroDef, ModuleDef, Semantics}; | |||
5 | use syntax::{ast, AstNode, SyntaxKind::NAME}; | 5 | use syntax::{ast, AstNode, SyntaxKind::NAME}; |
6 | 6 | ||
7 | use crate::{ | 7 | use crate::{ |
8 | defs::{classify_name, Definition}, | 8 | defs::{Definition, NameClass}, |
9 | symbol_index::{self, FileSymbol, Query}, | 9 | symbol_index::{self, FileSymbol, Query}, |
10 | RootDatabase, | 10 | RootDatabase, |
11 | }; | 11 | }; |
@@ -60,5 +60,5 @@ fn get_name_definition<'a>( | |||
60 | candidate_node | 60 | candidate_node |
61 | }; | 61 | }; |
62 | let name = ast::Name::cast(candidate_name_node)?; | 62 | let name = ast::Name::cast(candidate_name_node)?; |
63 | classify_name(sema, &name)?.into_definition(sema.db) | 63 | NameClass::classify(sema, &name)?.defined(sema.db) |
64 | } | 64 | } |
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs index 8e3dcd99c..a24335240 100644 --- a/crates/ide_db/src/search.rs +++ b/crates/ide_db/src/search.rs | |||
@@ -14,7 +14,7 @@ use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; | |||
14 | 14 | ||
15 | use crate::defs::NameClass; | 15 | use crate::defs::NameClass; |
16 | use crate::{ | 16 | use crate::{ |
17 | defs::{classify_name, classify_name_ref, Definition, NameRefClass}, | 17 | defs::{Definition, NameRefClass}, |
18 | RootDatabase, | 18 | RootDatabase, |
19 | }; | 19 | }; |
20 | 20 | ||
@@ -276,7 +276,7 @@ impl<'a> FindUsages<'a> { | |||
276 | name_ref: &ast::NameRef, | 276 | name_ref: &ast::NameRef, |
277 | sink: &mut dyn FnMut(Reference) -> bool, | 277 | sink: &mut dyn FnMut(Reference) -> bool, |
278 | ) -> bool { | 278 | ) -> bool { |
279 | match classify_name_ref(self.sema, &name_ref) { | 279 | match NameRefClass::classify(self.sema, &name_ref) { |
280 | Some(NameRefClass::Definition(def)) if &def == self.def => { | 280 | Some(NameRefClass::Definition(def)) if &def == self.def => { |
281 | let kind = if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) | 281 | let kind = if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref) |
282 | { | 282 | { |
@@ -292,7 +292,7 @@ impl<'a> FindUsages<'a> { | |||
292 | }; | 292 | }; |
293 | sink(reference) | 293 | sink(reference) |
294 | } | 294 | } |
295 | Some(NameRefClass::FieldShorthand { local, field }) => { | 295 | Some(NameRefClass::FieldShorthand { local_ref: local, field_ref: field }) => { |
296 | let reference = match self.def { | 296 | let reference = match self.def { |
297 | Definition::Field(_) if &field == self.def => Reference { | 297 | Definition::Field(_) if &field == self.def => Reference { |
298 | file_range: self.sema.original_range(name_ref.syntax()), | 298 | file_range: self.sema.original_range(name_ref.syntax()), |
@@ -313,10 +313,10 @@ impl<'a> FindUsages<'a> { | |||
313 | } | 313 | } |
314 | 314 | ||
315 | fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool { | 315 | fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool { |
316 | match classify_name(self.sema, name) { | 316 | match NameClass::classify(self.sema, name) { |
317 | Some(NameClass::FieldShorthand { local: _, field }) => { | 317 | Some(NameClass::PatFieldShorthand { local_def: _, field_ref }) => { |
318 | let reference = match self.def { | 318 | let reference = match self.def { |
319 | Definition::Field(_) if &field == self.def => Reference { | 319 | Definition::Field(_) if &field_ref == self.def => Reference { |
320 | file_range: self.sema.original_range(name.syntax()), | 320 | file_range: self.sema.original_range(name.syntax()), |
321 | kind: ReferenceKind::FieldShorthandForField, | 321 | kind: ReferenceKind::FieldShorthandForField, |
322 | // FIXME: mutable patterns should have `Write` access | 322 | // FIXME: mutable patterns should have `Write` access |
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs index e529e07b0..b2ff98a15 100644 --- a/crates/project_model/src/sysroot.rs +++ b/crates/project_model/src/sysroot.rs | |||
@@ -49,6 +49,7 @@ impl Sysroot { | |||
49 | } | 49 | } |
50 | 50 | ||
51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { | 51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { |
52 | log::debug!("Discovering sysroot for {}", cargo_toml.display()); | ||
52 | let current_dir = cargo_toml.parent().unwrap(); | 53 | let current_dir = cargo_toml.parent().unwrap(); |
53 | let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; | 54 | let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; |
54 | let res = Sysroot::load(&sysroot_src_dir)?; | 55 | let res = Sysroot::load(&sysroot_src_dir)?; |
@@ -115,12 +116,14 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { | |||
115 | if let Ok(path) = env::var("RUST_SRC_PATH") { | 116 | if let Ok(path) = env::var("RUST_SRC_PATH") { |
116 | let path = AbsPathBuf::try_from(path.as_str()) | 117 | let path = AbsPathBuf::try_from(path.as_str()) |
117 | .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; | 118 | .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; |
119 | log::debug!("Discovered sysroot by RUST_SRC_PATH: {}", path.display()); | ||
118 | return Ok(path); | 120 | return Ok(path); |
119 | } | 121 | } |
120 | 122 | ||
121 | let sysroot_path = { | 123 | let sysroot_path = { |
122 | let mut rustc = Command::new(toolchain::rustc()); | 124 | let mut rustc = Command::new(toolchain::rustc()); |
123 | rustc.current_dir(current_dir).args(&["--print", "sysroot"]); | 125 | rustc.current_dir(current_dir).args(&["--print", "sysroot"]); |
126 | log::debug!("Discovering sysroot by {:?}", rustc); | ||
124 | let stdout = utf8_stdout(rustc)?; | 127 | let stdout = utf8_stdout(rustc)?; |
125 | AbsPathBuf::assert(PathBuf::from(stdout)) | 128 | AbsPathBuf::assert(PathBuf::from(stdout)) |
126 | }; | 129 | }; |
@@ -150,6 +153,7 @@ fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { | |||
150 | // FIXME: remove `src` when 1.47 comes out | 153 | // FIXME: remove `src` when 1.47 comes out |
151 | // https://github.com/rust-lang/rust/pull/73265 | 154 | // https://github.com/rust-lang/rust/pull/73265 |
152 | let rust_src = sysroot_path.join("lib/rustlib/src/rust"); | 155 | let rust_src = sysroot_path.join("lib/rustlib/src/rust"); |
156 | log::debug!("Checking sysroot (looking for `library` and `src` dirs): {}", rust_src.display()); | ||
153 | ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists()) | 157 | ["library", "src"].iter().map(|it| rust_src.join(it)).find(|it| it.exists()) |
154 | } | 158 | } |
155 | 159 | ||
diff --git a/docs/dev/README.md b/docs/dev/README.md index 36edddc70..90e74f226 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -35,7 +35,12 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0 | |||
35 | * [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), | 35 | * [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), |
36 | [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium), | 36 | [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium), |
37 | [E-hard](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-hard), | 37 | [E-hard](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-hard), |
38 | labels are *estimates* for how hard would be to write a fix. | 38 | [E-unknown](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-unknown), |
39 | labels are *estimates* for how hard would be to write a fix. Each triaged issue should have one of these labels. | ||
40 | * [S-actionable](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-actionable) and | ||
41 | [S-unactionable](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AS-unactionable) | ||
42 | specify if there are concrete steps to resolve or advance an issue. Roughly, actionable issues need only work to be fixed, | ||
43 | while unactionable ones are effectively wont-fix. Each triaged issue should have one of these labels. | ||
39 | * [fun](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3Afun) | 44 | * [fun](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3Afun) |
40 | is for cool, but probably hard stuff. | 45 | is for cool, but probably hard stuff. |
41 | 46 | ||
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 3f861f3e0..43a69d6ce 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -1,3 +1,13 @@ | |||
1 | <!--- | ||
2 | lsp_ext.rs hash: 286f8bbac885531a | ||
3 | |||
4 | If you need to change the above hash to make the test pass, please check if you | ||
5 | need to adjust this doc as well and ping this issue: | ||
6 | |||
7 | https://github.com/rust-analyzer/rust-analyzer/issues/4604 | ||
8 | |||
9 | ---> | ||
10 | |||
1 | # LSP Extensions | 11 | # LSP Extensions |
2 | 12 | ||
3 | This document describes LSP extensions used by rust-analyzer. | 13 | This document describes LSP extensions used by rust-analyzer. |
diff --git a/docs/dev/style.md b/docs/dev/style.md index 59067d234..883a6845d 100644 --- a/docs/dev/style.md +++ b/docs/dev/style.md | |||
@@ -186,6 +186,31 @@ impl Person { | |||
186 | } | 186 | } |
187 | ``` | 187 | ``` |
188 | 188 | ||
189 | ## Constructors | ||
190 | |||
191 | Prefer `Default` to zero-argument `new` function | ||
192 | |||
193 | ```rust | ||
194 | // Good | ||
195 | #[derive(Default)] | ||
196 | struct Foo { | ||
197 | bar: Option<Bar> | ||
198 | } | ||
199 | |||
200 | // Not as good | ||
201 | struct Foo { | ||
202 | bar: Option<Bar> | ||
203 | } | ||
204 | |||
205 | impl Foo { | ||
206 | fn new() -> Foo { | ||
207 | Foo { bar: None } | ||
208 | } | ||
209 | } | ||
210 | ``` | ||
211 | |||
212 | Prefer `Default` even it has to be implemented manually. | ||
213 | |||
189 | ## Avoid Monomorphization | 214 | ## Avoid Monomorphization |
190 | 215 | ||
191 | Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*. | 216 | Rust uses monomorphization to compile generic code, meaning that for each instantiation of a generic functions with concrete types, the function is compiled afresh, *per crate*. |
@@ -223,6 +248,8 @@ fn frbonicate(f: impl AsRef<Path>) { | |||
223 | 248 | ||
224 | # Premature Pessimization | 249 | # Premature Pessimization |
225 | 250 | ||
251 | ## Avoid Allocations | ||
252 | |||
226 | Avoid writing code which is slower than it needs to be. | 253 | Avoid writing code which is slower than it needs to be. |
227 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. | 254 | Don't allocate a `Vec` where an iterator would do, don't allocate strings needlessly. |
228 | 255 | ||
@@ -242,6 +269,8 @@ if words.len() != 2 { | |||
242 | } | 269 | } |
243 | ``` | 270 | ``` |
244 | 271 | ||
272 | ## Push Allocations to the Call Site | ||
273 | |||
245 | If allocation is inevitable, let the caller allocate the resource: | 274 | If allocation is inevitable, let the caller allocate the resource: |
246 | 275 | ||
247 | ```rust | 276 | ```rust |
@@ -257,6 +286,9 @@ fn frobnicate(s: &str) { | |||
257 | } | 286 | } |
258 | ``` | 287 | ``` |
259 | 288 | ||
289 | This is better because it reveals the costs. | ||
290 | It is also more efficient when the caller already owns the allocation. | ||
291 | |||
260 | ## Collection types | 292 | ## Collection types |
261 | 293 | ||
262 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. | 294 | Prefer `rustc_hash::FxHashMap` and `rustc_hash::FxHashSet` instead of the ones in `std::collections`. |
@@ -371,6 +403,18 @@ Default names: | |||
371 | * `n_foo` -- number of foos | 403 | * `n_foo` -- number of foos |
372 | * `foo_idx` -- index of `foo` | 404 | * `foo_idx` -- index of `foo` |
373 | 405 | ||
406 | Many names in rust-analyzer conflict with keywords. | ||
407 | We use mangled names instead of `r#ident` syntax: | ||
408 | |||
409 | ``` | ||
410 | struct -> strukt | ||
411 | crate -> krate | ||
412 | impl -> imp | ||
413 | trait -> trait_ | ||
414 | fn -> func | ||
415 | enum -> enum_ | ||
416 | mod -> module | ||
417 | ``` | ||
374 | 418 | ||
375 | ## Early Returns | 419 | ## Early Returns |
376 | 420 | ||
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 30ade9b98..ed0db2924 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -44,10 +44,12 @@ const paramHints = createHintStyle("parameter"); | |||
44 | const chainingHints = createHintStyle("chaining"); | 44 | const chainingHints = createHintStyle("chaining"); |
45 | 45 | ||
46 | function createHintStyle(hintKind: "type" | "parameter" | "chaining") { | 46 | function createHintStyle(hintKind: "type" | "parameter" | "chaining") { |
47 | // U+200C is a zero-width non-joiner to prevent the editor from forming a ligature | ||
48 | // between code and type hints | ||
47 | const [pos, render] = ({ | 49 | const [pos, render] = ({ |
48 | type: ["after", (label: string) => `: ${label}`], | 50 | type: ["after", (label: string) => `\u{200c}: ${label}`], |
49 | parameter: ["before", (label: string) => `${label}: `], | 51 | parameter: ["before", (label: string) => `${label}: `], |
50 | chaining: ["after", (label: string) => `: ${label}`], | 52 | chaining: ["after", (label: string) => `\u{200c}: ${label}`], |
51 | } as const)[hintKind]; | 53 | } as const)[hintKind]; |
52 | 54 | ||
53 | const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`); | 55 | const fg = new vscode.ThemeColor(`rust_analyzer.inlayHints.foreground.${hintKind}Hints`); |
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 0c233f87d..b3bb9d543 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs | |||
@@ -45,6 +45,41 @@ fn smoke_test_docs_generation() { | |||
45 | } | 45 | } |
46 | 46 | ||
47 | #[test] | 47 | #[test] |
48 | fn check_lsp_extensions_docs() { | ||
49 | let expected_hash = { | ||
50 | let lsp_ext_rs = | ||
51 | fs2::read_to_string(project_root().join("crates/rust-analyzer/src/lsp_ext.rs")) | ||
52 | .unwrap(); | ||
53 | stable_hash(lsp_ext_rs.as_str()) | ||
54 | }; | ||
55 | |||
56 | let actual_hash = { | ||
57 | let lsp_extensions_md = | ||
58 | fs2::read_to_string(project_root().join("docs/dev/lsp-extensions.md")).unwrap(); | ||
59 | let text = lsp_extensions_md | ||
60 | .lines() | ||
61 | .find_map(|line| line.strip_prefix("lsp_ext.rs hash:")) | ||
62 | .unwrap() | ||
63 | .trim(); | ||
64 | u64::from_str_radix(text, 16).unwrap() | ||
65 | }; | ||
66 | |||
67 | if actual_hash != expected_hash { | ||
68 | panic!( | ||
69 | " | ||
70 | lsp_ext.rs was changed without touching lsp-extensions.md. | ||
71 | |||
72 | Expected hash: {:x} | ||
73 | Actual hash: {:x} | ||
74 | |||
75 | Please adjust docs/dev/lsp-extensions.md. | ||
76 | ", | ||
77 | expected_hash, actual_hash | ||
78 | ) | ||
79 | } | ||
80 | } | ||
81 | |||
82 | #[test] | ||
48 | fn rust_files_are_tidy() { | 83 | fn rust_files_are_tidy() { |
49 | let mut tidy_docs = TidyDocs::default(); | 84 | let mut tidy_docs = TidyDocs::default(); |
50 | for path in rust_files(&project_root().join("crates")) { | 85 | for path in rust_files(&project_root().join("crates")) { |
@@ -280,3 +315,13 @@ fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool { | |||
280 | .filter_map(|it| it.as_os_str().to_str()) | 315 | .filter_map(|it| it.as_os_str().to_str()) |
281 | .any(|it| dirs_to_exclude.contains(&it)) | 316 | .any(|it| dirs_to_exclude.contains(&it)) |
282 | } | 317 | } |
318 | |||
319 | #[allow(deprecated)] | ||
320 | fn stable_hash(text: &str) -> u64 { | ||
321 | use std::hash::{Hash, Hasher, SipHasher}; | ||
322 | |||
323 | let text = text.replace('\r', ""); | ||
324 | let mut hasher = SipHasher::default(); | ||
325 | text.hash(&mut hasher); | ||
326 | hasher.finish() | ||
327 | } | ||