aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock54
-rw-r--r--Cargo.toml12
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs10
-rw-r--r--crates/base_db/src/input.rs28
-rw-r--r--crates/hir/src/code_model.rs47
-rw-r--r--crates/hir/src/diagnostics.rs3
-rw-r--r--crates/hir/src/semantics/source_to_def.rs4
-rw-r--r--crates/hir_def/src/adt.rs28
-rw-r--r--crates/hir_def/src/body/lower.rs5
-rw-r--r--crates/hir_def/src/data.rs2
-rw-r--r--crates/hir_def/src/item_scope.rs6
-rw-r--r--crates/hir_def/src/item_tree.rs1
-rw-r--r--crates/hir_def/src/item_tree/lower.rs3
-rw-r--r--crates/hir_def/src/item_tree/tests.rs24
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/hir_ty/src/diagnostics.rs85
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check.rs833
-rw-r--r--crates/hir_ty/src/diagnostics/decl_check/case_conv.rs194
-rw-r--r--crates/hir_ty/src/diagnostics/unsafe_check.rs6
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs2
-rw-r--r--crates/ide/src/diagnostics.rs141
-rw-r--r--crates/ide/src/diagnostics/fixes.rs24
-rw-r--r--crates/ide/src/doc_links.rs (renamed from crates/ide/src/link_rewrite.rs)285
-rw-r--r--crates/ide/src/hover.rs2
-rw-r--r--crates/ide/src/inlay_hints.rs81
-rw-r--r--crates/ide/src/lib.rs24
-rw-r--r--crates/ide/src/prime_caches.rs43
-rw-r--r--crates/ide/src/references.rs90
-rw-r--r--crates/ide/src/references/rename.rs182
-rw-r--r--crates/ide/src/syntax_highlighting.rs118
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlight_strings.html2
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html6
-rw-r--r--crates/ide/src/syntax_highlighting/tests.rs4
-rw-r--r--crates/ide_db/src/search.rs116
-rw-r--r--crates/project_model/src/sysroot.rs10
-rw-r--r--crates/rust-analyzer/src/handlers.rs29
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs43
-rw-r--r--crates/rust-analyzer/src/thread_pool.rs11
-rw-r--r--crates/stdx/src/lib.rs18
-rw-r--r--crates/stdx/src/macros.rs8
-rw-r--r--docs/dev/lsp-extensions.md25
-rw-r--r--editors/code/package.json9
-rw-r--r--editors/code/rust.tmGrammar.json1725
-rw-r--r--editors/code/src/commands.ts25
-rw-r--r--editors/code/src/lsp_ext.ts2
-rw-r--r--editors/code/src/main.ts1
47 files changed, 3439 insertions, 946 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4cfa4c519..fdb62e6ea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,12 +82,12 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
82 82
83[[package]] 83[[package]]
84name = "backtrace" 84name = "backtrace"
85version = "0.3.51" 85version = "0.3.53"
86source = "registry+https://github.com/rust-lang/crates.io-index" 86source = "registry+https://github.com/rust-lang/crates.io-index"
87checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1" 87checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
88dependencies = [ 88dependencies = [
89 "addr2line", 89 "addr2line",
90 "cfg-if 0.1.10", 90 "cfg-if 1.0.0",
91 "libc", 91 "libc",
92 "miniz_oxide", 92 "miniz_oxide",
93 "object", 93 "object",
@@ -140,9 +140,9 @@ dependencies = [
140 140
141[[package]] 141[[package]]
142name = "cc" 142name = "cc"
143version = "1.0.60" 143version = "1.0.61"
144source = "registry+https://github.com/rust-lang/crates.io-index" 144source = "registry+https://github.com/rust-lang/crates.io-index"
145checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" 145checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
146 146
147[[package]] 147[[package]]
148name = "cfg" 148name = "cfg"
@@ -168,9 +168,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
168 168
169[[package]] 169[[package]]
170name = "chalk-derive" 170name = "chalk-derive"
171version = "0.32.0" 171version = "0.33.0"
172source = "registry+https://github.com/rust-lang/crates.io-index" 172source = "registry+https://github.com/rust-lang/crates.io-index"
173checksum = "2d072b2ba723f0bada7c515d8b3725224bc4f5052d2a92dcbeb0b118ff37084a" 173checksum = "569014cab9084a6b826fe2507cc6d08f7897ba144fb1bc74e71b593dc8a0b952"
174dependencies = [ 174dependencies = [
175 "proc-macro2", 175 "proc-macro2",
176 "quote", 176 "quote",
@@ -180,9 +180,9 @@ dependencies = [
180 180
181[[package]] 181[[package]]
182name = "chalk-ir" 182name = "chalk-ir"
183version = "0.32.0" 183version = "0.33.0"
184source = "registry+https://github.com/rust-lang/crates.io-index" 184source = "registry+https://github.com/rust-lang/crates.io-index"
185checksum = "f60cdb0e18c5455cb6a85e8464aad3622b70476018edfa8845691df66f7e9a05" 185checksum = "8d9eab2a6590b696419f89c9ca3616fe8e8266ef676e6a6da8818c94963c9541"
186dependencies = [ 186dependencies = [
187 "chalk-derive", 187 "chalk-derive",
188 "lazy_static", 188 "lazy_static",
@@ -190,9 +190,9 @@ dependencies = [
190 190
191[[package]] 191[[package]]
192name = "chalk-recursive" 192name = "chalk-recursive"
193version = "0.32.0" 193version = "0.33.0"
194source = "registry+https://github.com/rust-lang/crates.io-index" 194source = "registry+https://github.com/rust-lang/crates.io-index"
195checksum = "b14f40242102e7c0e2791a2cc86dbbc213a1d7b7acc0e22b7da329f4957d1722" 195checksum = "4a4671bcc70aa2d7e12ff4fe03f91d0c3c9ce387de915915e57fdf0c91dc5abd"
196dependencies = [ 196dependencies = [
197 "chalk-derive", 197 "chalk-derive",
198 "chalk-ir", 198 "chalk-ir",
@@ -203,9 +203,9 @@ dependencies = [
203 203
204[[package]] 204[[package]]
205name = "chalk-solve" 205name = "chalk-solve"
206version = "0.32.0" 206version = "0.33.0"
207source = "registry+https://github.com/rust-lang/crates.io-index" 207source = "registry+https://github.com/rust-lang/crates.io-index"
208checksum = "981534d499a8476ecc0b520be4d3864757f96211826a75360fbf2cb6fae362ab" 208checksum = "45f75cc603f2fd302576c8b2976437f334e159e26d0afbb108a565b96c52184e"
209dependencies = [ 209dependencies = [
210 "chalk-derive", 210 "chalk-derive",
211 "chalk-ir", 211 "chalk-ir",
@@ -737,9 +737,9 @@ checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
737 737
738[[package]] 738[[package]]
739name = "libloading" 739name = "libloading"
740version = "0.6.3" 740version = "0.6.4"
741source = "registry+https://github.com/rust-lang/crates.io-index" 741source = "registry+https://github.com/rust-lang/crates.io-index"
742checksum = "2443d8f0478b16759158b2f66d525991a05491138bc05814ef52a250148ef4f9" 742checksum = "3557c9384f7f757f6d139cd3a4c62ef4e850696c16bf27924a5538c8a09717a1"
743dependencies = [ 743dependencies = [
744 "cfg-if 0.1.10", 744 "cfg-if 0.1.10",
745 "winapi 0.3.9", 745 "winapi 0.3.9",
@@ -747,9 +747,9 @@ dependencies = [
747 747
748[[package]] 748[[package]]
749name = "libmimalloc-sys" 749name = "libmimalloc-sys"
750version = "0.1.17" 750version = "0.1.18"
751source = "registry+https://github.com/rust-lang/crates.io-index" 751source = "registry+https://github.com/rust-lang/crates.io-index"
752checksum = "2bd65748dfb74a807e8379fd49bf4d38964d92b8637b56f173f87e1d70433368" 752checksum = "82151ff13433c4d403cb15d0e6fbda14b24d65bd1a5b33f7d52ec983cc00752d"
753dependencies = [ 753dependencies = [
754 "cmake", 754 "cmake",
755] 755]
@@ -859,9 +859,9 @@ dependencies = [
859 859
860[[package]] 860[[package]]
861name = "mimalloc" 861name = "mimalloc"
862version = "0.1.21" 862version = "0.1.22"
863source = "registry+https://github.com/rust-lang/crates.io-index" 863source = "registry+https://github.com/rust-lang/crates.io-index"
864checksum = "2e18b3d01186c4a7bec0b56cff9a4b9d5a1461aa162c7b8eaf2e5ee10b265b02" 864checksum = "4a5d2c9cb18f9cdc6d88f4aca6d3d8ea89c4c8202d6facfc7e56efdee97b80fa"
865dependencies = [ 865dependencies = [
866 "libmimalloc-sys", 866 "libmimalloc-sys",
867] 867]
@@ -981,9 +981,9 @@ dependencies = [
981 981
982[[package]] 982[[package]]
983name = "object" 983name = "object"
984version = "0.20.0" 984version = "0.21.1"
985source = "registry+https://github.com/rust-lang/crates.io-index" 985source = "registry+https://github.com/rust-lang/crates.io-index"
986checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" 986checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
987 987
988[[package]] 988[[package]]
989name = "once_cell" 989name = "once_cell"
@@ -1383,18 +1383,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1383 1383
1384[[package]] 1384[[package]]
1385name = "scroll" 1385name = "scroll"
1386version = "0.10.1" 1386version = "0.10.2"
1387source = "registry+https://github.com/rust-lang/crates.io-index" 1387source = "registry+https://github.com/rust-lang/crates.io-index"
1388checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" 1388checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec"
1389dependencies = [ 1389dependencies = [
1390 "scroll_derive", 1390 "scroll_derive",
1391] 1391]
1392 1392
1393[[package]] 1393[[package]]
1394name = "scroll_derive" 1394name = "scroll_derive"
1395version = "0.10.2" 1395version = "0.10.3"
1396source = "registry+https://github.com/rust-lang/crates.io-index" 1396source = "registry+https://github.com/rust-lang/crates.io-index"
1397checksum = "e367622f934864ffa1c704ba2b82280aab856e3d8213c84c5720257eb34b15b9" 1397checksum = "6dfde5d1531034db129e95c76ac857e2baecea3443579d493d02224950b0fb6d"
1398dependencies = [ 1398dependencies = [
1399 "proc-macro2", 1399 "proc-macro2",
1400 "quote", 1400 "quote",
@@ -1510,9 +1510,9 @@ version = "0.0.0"
1510 1510
1511[[package]] 1511[[package]]
1512name = "syn" 1512name = "syn"
1513version = "1.0.42" 1513version = "1.0.44"
1514source = "registry+https://github.com/rust-lang/crates.io-index" 1514source = "registry+https://github.com/rust-lang/crates.io-index"
1515checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228" 1515checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
1516dependencies = [ 1516dependencies = [
1517 "proc-macro2", 1517 "proc-macro2",
1518 "quote", 1518 "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 218581d9d..87d33f06c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,18 +19,6 @@ miniz_oxide.opt-level = 3
19incremental = true 19incremental = true
20debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger. 20debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger.
21 21
22# Ideally, we would use `build-override` here, but some crates are also
23# needed at run-time and we end up compiling them twice.
24[profile.release.package]
25chalk-derive.opt-level = 0
26proc-macro2.opt-level = 0
27quote.opt-level = 0
28salsa-macros.opt-level = 0
29serde_derive.opt-level = 0
30syn.opt-level = 0
31tracing-attributes.opt-level = 0
32xtask.opt-level = 0
33
34[patch.'crates-io'] 22[patch.'crates-io']
35# rowan = { path = "../rowan" } 23# rowan = { path = "../rowan" }
36 24
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs
index 7cd76ea06..d505e9444 100644
--- a/crates/assists/src/handlers/fix_visibility.rs
+++ b/crates/assists/src/handlers/fix_visibility.rs
@@ -324,14 +324,14 @@ pub struct Foo { pub bar: () }
324 324
325 #[test] 325 #[test]
326 fn fix_visibility_of_enum_variant_field() { 326 fn fix_visibility_of_enum_variant_field() {
327 check_assist( 327 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
328 // rejects any visibility specifiers on them, so this assist should never fire on them.
329 check_assist_not_applicable(
328 fix_visibility, 330 fix_visibility,
329 r"mod foo { pub enum Foo { Bar { bar: () } } } 331 r"mod foo { pub enum Foo { Bar { bar: () } } }
330 fn main() { foo::Foo::Bar { <|>bar: () }; } ", 332 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
331 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
332 fn main() { foo::Foo::Bar { bar: () }; } ",
333 ); 333 );
334 check_assist( 334 check_assist_not_applicable(
335 fix_visibility, 335 fix_visibility,
336 r" 336 r"
337//- /lib.rs 337//- /lib.rs
@@ -340,8 +340,6 @@ fn main() { foo::Foo::Bar { <|>bar: () }; }
340//- /foo.rs 340//- /foo.rs
341pub enum Foo { Bar { bar: () } } 341pub enum Foo { Bar { bar: () } }
342", 342",
343 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
344",
345 ); 343 );
346 check_assist_not_applicable( 344 check_assist_not_applicable(
347 fix_visibility, 345 fix_visibility,
diff --git a/crates/base_db/src/input.rs b/crates/base_db/src/input.rs
index c330314d4..215ac4b41 100644
--- a/crates/base_db/src/input.rs
+++ b/crates/base_db/src/input.rs
@@ -221,6 +221,34 @@ impl CrateGraph {
221 deps.into_iter() 221 deps.into_iter()
222 } 222 }
223 223
224 /// Returns all crates in the graph, sorted in topological order (ie. dependencies of a crate
225 /// come before the crate itself).
226 pub fn crates_in_topological_order(&self) -> Vec<CrateId> {
227 let mut res = Vec::new();
228 let mut visited = FxHashSet::default();
229
230 for krate in self.arena.keys().copied() {
231 go(self, &mut visited, &mut res, krate);
232 }
233
234 return res;
235
236 fn go(
237 graph: &CrateGraph,
238 visited: &mut FxHashSet<CrateId>,
239 res: &mut Vec<CrateId>,
240 source: CrateId,
241 ) {
242 if !visited.insert(source) {
243 return;
244 }
245 for dep in graph[source].dependencies.iter() {
246 go(graph, visited, res, dep.crate_id)
247 }
248 res.push(source)
249 }
250 }
251
224 // FIXME: this only finds one crate with the given root; we could have multiple 252 // FIXME: this only finds one crate with the given root; we could have multiple
225 pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> { 253 pub fn crate_id_for_crate_root(&self, file_id: FileId) -> Option<CrateId> {
226 let (&crate_id, _) = 254 let (&crate_id, _) =
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 031c91ccf..b65be4fe1 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -186,6 +186,16 @@ impl_from!(
186 for ModuleDef 186 for ModuleDef
187); 187);
188 188
189impl From<VariantDef> for ModuleDef {
190 fn from(var: VariantDef) -> Self {
191 match var {
192 VariantDef::Struct(t) => Adt::from(t).into(),
193 VariantDef::Union(t) => Adt::from(t).into(),
194 VariantDef::EnumVariant(t) => t.into(),
195 }
196 }
197}
198
189impl ModuleDef { 199impl ModuleDef {
190 pub fn module(self, db: &dyn HirDatabase) -> Option<Module> { 200 pub fn module(self, db: &dyn HirDatabase) -> Option<Module> {
191 match self { 201 match self {
@@ -245,6 +255,25 @@ impl ModuleDef {
245 ModuleDef::BuiltinType(it) => Some(it.as_name()), 255 ModuleDef::BuiltinType(it) => Some(it.as_name()),
246 } 256 }
247 } 257 }
258
259 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
260 let id = match self {
261 ModuleDef::Adt(it) => match it {
262 Adt::Struct(it) => it.id.into(),
263 Adt::Enum(it) => it.id.into(),
264 Adt::Union(it) => it.id.into(),
265 },
266 ModuleDef::Trait(it) => it.id.into(),
267 ModuleDef::Function(it) => it.id.into(),
268 ModuleDef::TypeAlias(it) => it.id.into(),
269 ModuleDef::Module(it) => it.id.into(),
270 ModuleDef::Const(it) => it.id.into(),
271 ModuleDef::Static(it) => it.id.into(),
272 _ => return,
273 };
274
275 hir_ty::diagnostics::validate_module_item(db, id, sink)
276 }
248} 277}
249 278
250pub use hir_def::{ 279pub use hir_def::{
@@ -348,6 +377,8 @@ impl Module {
348 let crate_def_map = db.crate_def_map(self.id.krate); 377 let crate_def_map = db.crate_def_map(self.id.krate);
349 crate_def_map.add_diagnostics(db.upcast(), self.id.local_id, sink); 378 crate_def_map.add_diagnostics(db.upcast(), self.id.local_id, sink);
350 for decl in self.declarations(db) { 379 for decl in self.declarations(db) {
380 decl.diagnostics(db, sink);
381
351 match decl { 382 match decl {
352 crate::ModuleDef::Function(f) => f.diagnostics(db, sink), 383 crate::ModuleDef::Function(f) => f.diagnostics(db, sink),
353 crate::ModuleDef::Module(m) => { 384 crate::ModuleDef::Module(m) => {
@@ -750,7 +781,15 @@ impl Function {
750 } 781 }
751 782
752 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 783 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
753 hir_ty::diagnostics::validate_body(db, self.id.into(), sink) 784 hir_ty::diagnostics::validate_module_item(db, self.id.into(), sink);
785 hir_ty::diagnostics::validate_body(db, self.id.into(), sink);
786 }
787
788 /// Whether this function declaration has a definition.
789 ///
790 /// This is false in the case of required (not provided) trait methods.
791 pub fn has_body(self, db: &dyn HirDatabase) -> bool {
792 db.function_data(self.id).has_body
754 } 793 }
755} 794}
756 795
@@ -1372,7 +1411,7 @@ impl Type {
1372 r#trait: Trait, 1411 r#trait: Trait,
1373 args: &[Type], 1412 args: &[Type],
1374 alias: TypeAlias, 1413 alias: TypeAlias,
1375 ) -> Option<Ty> { 1414 ) -> Option<Type> {
1376 let subst = Substs::build_for_def(db, r#trait.id) 1415 let subst = Substs::build_for_def(db, r#trait.id)
1377 .push(self.ty.value.clone()) 1416 .push(self.ty.value.clone())
1378 .fill(args.iter().map(|t| t.ty.value.clone())) 1417 .fill(args.iter().map(|t| t.ty.value.clone()))
@@ -1393,6 +1432,10 @@ impl Type {
1393 Solution::Unique(SolutionVariables(subst)) => subst.value.first().cloned(), 1432 Solution::Unique(SolutionVariables(subst)) => subst.value.first().cloned(),
1394 Solution::Ambig(_) => None, 1433 Solution::Ambig(_) => None,
1395 } 1434 }
1435 .map(|ty| Type {
1436 krate: self.krate,
1437 ty: InEnvironment { value: ty, environment: Arc::clone(&self.ty.environment) },
1438 })
1396 } 1439 }
1397 1440
1398 pub fn is_copy(&self, db: &dyn HirDatabase) -> bool { 1441 pub fn is_copy(&self, db: &dyn HirDatabase) -> bool {
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 363164b9b..da2b40849 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -2,5 +2,6 @@
2pub use hir_def::diagnostics::UnresolvedModule; 2pub use hir_def::diagnostics::UnresolvedModule;
3pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder}; 3pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder};
4pub use hir_ty::diagnostics::{ 4pub use hir_ty::diagnostics::{
5 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField, 5 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr,
6 NoSuchField,
6}; 7};
diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs
index 5918b9541..66fc11611 100644
--- a/crates/hir/src/semantics/source_to_def.rs
+++ b/crates/hir/src/semantics/source_to_def.rs
@@ -189,6 +189,10 @@ impl SourceToDefCtx<'_, '_> {
189 let def = self.type_alias_to_def(container.with_value(it))?; 189 let def = self.type_alias_to_def(container.with_value(it))?;
190 def.into() 190 def.into()
191 }, 191 },
192 ast::Variant(it) => {
193 let def = self.enum_variant_to_def(container.with_value(it))?;
194 VariantId::from(def).into()
195 },
192 _ => continue, 196 _ => continue,
193 } 197 }
194 }; 198 };
diff --git a/crates/hir_def/src/adt.rs b/crates/hir_def/src/adt.rs
index d69ff2fc7..6539959c3 100644
--- a/crates/hir_def/src/adt.rs
+++ b/crates/hir_def/src/adt.rs
@@ -14,7 +14,7 @@ use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};
14use crate::{ 14use crate::{
15 body::{CfgExpander, LowerCtx}, 15 body::{CfgExpander, LowerCtx},
16 db::DefDatabase, 16 db::DefDatabase,
17 item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem}, 17 item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem, RawVisibilityId},
18 src::HasChildSource, 18 src::HasChildSource,
19 src::HasSource, 19 src::HasSource,
20 trace::Trace, 20 trace::Trace,
@@ -91,7 +91,7 @@ impl StructData {
91 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone(); 91 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
92 92
93 let strukt = &item_tree[loc.id.value]; 93 let strukt = &item_tree[loc.id.value];
94 let variant_data = lower_fields(&item_tree, &cfg_options, &strukt.fields); 94 let variant_data = lower_fields(&item_tree, &cfg_options, &strukt.fields, None);
95 Arc::new(StructData { 95 Arc::new(StructData {
96 name: strukt.name.clone(), 96 name: strukt.name.clone(),
97 variant_data: Arc::new(variant_data), 97 variant_data: Arc::new(variant_data),
@@ -105,7 +105,7 @@ impl StructData {
105 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone(); 105 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
106 106
107 let union = &item_tree[loc.id.value]; 107 let union = &item_tree[loc.id.value];
108 let variant_data = lower_fields(&item_tree, &cfg_options, &union.fields); 108 let variant_data = lower_fields(&item_tree, &cfg_options, &union.fields, None);
109 109
110 Arc::new(StructData { 110 Arc::new(StructData {
111 name: union.name.clone(), 111 name: union.name.clone(),
@@ -126,7 +126,8 @@ impl EnumData {
126 for var_id in enum_.variants.clone() { 126 for var_id in enum_.variants.clone() {
127 if item_tree.attrs(var_id.into()).is_cfg_enabled(&cfg_options) { 127 if item_tree.attrs(var_id.into()).is_cfg_enabled(&cfg_options) {
128 let var = &item_tree[var_id]; 128 let var = &item_tree[var_id];
129 let var_data = lower_fields(&item_tree, &cfg_options, &var.fields); 129 let var_data =
130 lower_fields(&item_tree, &cfg_options, &var.fields, Some(enum_.visibility));
130 131
131 variants.alloc(EnumVariantData { 132 variants.alloc(EnumVariantData {
132 name: var.name.clone(), 133 name: var.name.clone(),
@@ -296,13 +297,18 @@ fn lower_struct(
296 } 297 }
297} 298}
298 299
299fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields) -> VariantData { 300fn lower_fields(
301 item_tree: &ItemTree,
302 cfg_options: &CfgOptions,
303 fields: &Fields,
304 override_visibility: Option<RawVisibilityId>,
305) -> VariantData {
300 match fields { 306 match fields {
301 Fields::Record(flds) => { 307 Fields::Record(flds) => {
302 let mut arena = Arena::new(); 308 let mut arena = Arena::new();
303 for field_id in flds.clone() { 309 for field_id in flds.clone() {
304 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) { 310 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
305 arena.alloc(lower_field(item_tree, &item_tree[field_id])); 311 arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
306 } 312 }
307 } 313 }
308 VariantData::Record(arena) 314 VariantData::Record(arena)
@@ -311,7 +317,7 @@ fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields)
311 let mut arena = Arena::new(); 317 let mut arena = Arena::new();
312 for field_id in flds.clone() { 318 for field_id in flds.clone() {
313 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) { 319 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
314 arena.alloc(lower_field(item_tree, &item_tree[field_id])); 320 arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
315 } 321 }
316 } 322 }
317 VariantData::Tuple(arena) 323 VariantData::Tuple(arena)
@@ -320,10 +326,14 @@ fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields)
320 } 326 }
321} 327}
322 328
323fn lower_field(item_tree: &ItemTree, field: &Field) -> FieldData { 329fn lower_field(
330 item_tree: &ItemTree,
331 field: &Field,
332 override_visibility: Option<RawVisibilityId>,
333) -> FieldData {
324 FieldData { 334 FieldData {
325 name: field.name.clone(), 335 name: field.name.clone(),
326 type_ref: field.type_ref.clone(), 336 type_ref: field.type_ref.clone(),
327 visibility: item_tree[field.visibility].clone(), 337 visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(),
328 } 338 }
329} 339}
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 2d91bb21f..01e72690a 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -107,7 +107,10 @@ impl ExprCollector<'_> {
107 let param_pat = self.alloc_pat( 107 let param_pat = self.alloc_pat(
108 Pat::Bind { 108 Pat::Bind {
109 name: name![self], 109 name: name![self],
110 mode: BindingAnnotation::Unannotated, 110 mode: BindingAnnotation::new(
111 self_param.mut_token().is_some() && self_param.amp_token().is_none(),
112 false,
113 ),
111 subpat: None, 114 subpat: None,
112 }, 115 },
113 Either::Right(ptr), 116 Either::Right(ptr),
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index 6190906da..ff1ef0df6 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -25,6 +25,7 @@ pub struct FunctionData {
25 /// True if the first param is `self`. This is relevant to decide whether this 25 /// True if the first param is `self`. This is relevant to decide whether this
26 /// can be called as a method. 26 /// can be called as a method.
27 pub has_self_param: bool, 27 pub has_self_param: bool,
28 pub has_body: bool,
28 pub is_unsafe: bool, 29 pub is_unsafe: bool,
29 pub is_varargs: bool, 30 pub is_varargs: bool,
30 pub visibility: RawVisibility, 31 pub visibility: RawVisibility,
@@ -42,6 +43,7 @@ impl FunctionData {
42 ret_type: func.ret_type.clone(), 43 ret_type: func.ret_type.clone(),
43 attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(), 44 attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(),
44 has_self_param: func.has_self_param, 45 has_self_param: func.has_self_param,
46 has_body: func.has_body,
45 is_unsafe: func.is_unsafe, 47 is_unsafe: func.is_unsafe,
46 is_varargs: func.is_varargs, 48 is_varargs: func.is_varargs,
47 visibility: item_tree[func.visibility].clone(), 49 visibility: item_tree[func.visibility].clone(),
diff --git a/crates/hir_def/src/item_scope.rs b/crates/hir_def/src/item_scope.rs
index 12c24e1ca..a8b3fe844 100644
--- a/crates/hir_def/src/item_scope.rs
+++ b/crates/hir_def/src/item_scope.rs
@@ -95,6 +95,12 @@ impl ItemScope {
95 self.impls.iter().copied() 95 self.impls.iter().copied()
96 } 96 }
97 97
98 pub fn values(
99 &self,
100 ) -> impl Iterator<Item = (ModuleDefId, Visibility)> + ExactSizeIterator + '_ {
101 self.values.values().copied()
102 }
103
98 pub fn visibility_of(&self, def: ModuleDefId) -> Option<Visibility> { 104 pub fn visibility_of(&self, def: ModuleDefId) -> Option<Visibility> {
99 self.name_of(ItemInNs::Types(def)) 105 self.name_of(ItemInNs::Types(def))
100 .or_else(|| self.name_of(ItemInNs::Values(def))) 106 .or_else(|| self.name_of(ItemInNs::Values(def)))
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 0fd91b9d0..8a1121bbd 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -505,6 +505,7 @@ pub struct Function {
505 pub visibility: RawVisibilityId, 505 pub visibility: RawVisibilityId,
506 pub generic_params: GenericParamsId, 506 pub generic_params: GenericParamsId,
507 pub has_self_param: bool, 507 pub has_self_param: bool,
508 pub has_body: bool,
508 pub is_unsafe: bool, 509 pub is_unsafe: bool,
509 pub params: Box<[TypeRef]>, 510 pub params: Box<[TypeRef]>,
510 pub is_varargs: bool, 511 pub is_varargs: bool,
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 54814f141..3328639cf 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -330,12 +330,15 @@ impl Ctx {
330 ret_type 330 ret_type
331 }; 331 };
332 332
333 let has_body = func.body().is_some();
334
333 let ast_id = self.source_ast_id_map.ast_id(func); 335 let ast_id = self.source_ast_id_map.ast_id(func);
334 let mut res = Function { 336 let mut res = Function {
335 name, 337 name,
336 visibility, 338 visibility,
337 generic_params: GenericParamsId::EMPTY, 339 generic_params: GenericParamsId::EMPTY,
338 has_self_param, 340 has_self_param,
341 has_body,
339 is_unsafe: func.unsafe_token().is_some(), 342 is_unsafe: func.unsafe_token().is_some(),
340 params: params.into_boxed_slice(), 343 params: params.into_boxed_slice(),
341 is_varargs, 344 is_varargs,
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs
index 1a806cda5..4b354c4c1 100644
--- a/crates/hir_def/src/item_tree/tests.rs
+++ b/crates/hir_def/src/item_tree/tests.rs
@@ -240,9 +240,9 @@ fn smoke() {
240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }] 240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }]
241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) } 241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) }
242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }] 242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }]
243 > Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(10) } 243 > Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, has_body: false, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(10) }
244 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }] 244 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }]
245 > Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(11) } 245 > Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, has_body: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(11) }
246 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }] 246 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }]
247 Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<syntax::ast::generated::nodes::Struct>(3), kind: Unit } 247 Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<syntax::ast::generated::nodes::Struct>(3), kind: Unit }
248 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }] 248 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }]
@@ -275,12 +275,12 @@ fn simple_inner_items() {
275 275
276 top-level items: 276 top-level items:
277 Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) } 277 Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) }
278 > Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 278 > Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
279 279
280 inner items: 280 inner items:
281 281
282 for AST FileAstId::<syntax::ast::generated::nodes::Item>(2): 282 for AST FileAstId::<syntax::ast::generated::nodes::Item>(2):
283 Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 283 Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
284 284
285 "#]], 285 "#]],
286 ); 286 );
@@ -303,9 +303,9 @@ fn extern_attrs() {
303 303
304 top-level items: 304 top-level items:
305 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] 305 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
306 Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 306 Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
307 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] 307 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
308 Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 308 Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
309 "##]], 309 "##]],
310 ); 310 );
311} 311}
@@ -329,9 +329,9 @@ fn trait_attrs() {
329 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }] 329 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }]
330 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(0) } 330 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(0) }
331 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] 331 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
332 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 332 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
333 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] 333 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
334 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 334 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
335 "##]], 335 "##]],
336 ); 336 );
337} 337}
@@ -355,9 +355,9 @@ fn impl_attrs() {
355 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }] 355 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }]
356 Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) } 356 Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) }
357 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] 357 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
358 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 358 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
359 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] 359 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
360 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 360 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
361 "##]], 361 "##]],
362 ); 362 );
363} 363}
@@ -408,13 +408,13 @@ fn inner_item_attrs() {
408 inner attrs: Attrs { entries: None } 408 inner attrs: Attrs { entries: None }
409 409
410 top-level items: 410 top-level items:
411 Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(0) } 411 Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(0) }
412 412
413 inner items: 413 inner items:
414 414
415 for AST FileAstId::<syntax::ast::generated::nodes::Item>(1): 415 for AST FileAstId::<syntax::ast::generated::nodes::Item>(1):
416 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }] 416 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }]
417 Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 417 Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
418 418
419 "##]], 419 "##]],
420 ); 420 );
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index 0f3c85926..e9c62c6aa 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -17,9 +17,9 @@ ena = "0.14.0"
17log = "0.4.8" 17log = "0.4.8"
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19scoped-tls = "1" 19scoped-tls = "1"
20chalk-solve = "0.32" 20chalk-solve = "0.33"
21chalk-ir = "0.32" 21chalk-ir = "0.33"
22chalk-recursive = "0.32" 22chalk-recursive = "0.33"
23 23
24stdx = { path = "../stdx", version = "0.0.0" } 24stdx = { path = "../stdx", version = "0.0.0" }
25hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_def = { path = "../hir_def", version = "0.0.0" }
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 9ba005fab..dfe98571e 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -2,10 +2,11 @@
2mod expr; 2mod expr;
3mod match_check; 3mod match_check;
4mod unsafe_check; 4mod unsafe_check;
5mod decl_check;
5 6
6use std::any::Any; 7use std::{any::Any, fmt};
7 8
8use hir_def::DefWithBodyId; 9use hir_def::{DefWithBodyId, ModuleDefId};
9use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; 10use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
10use hir_expand::{name::Name, HirFileId, InFile}; 11use hir_expand::{name::Name, HirFileId, InFile};
11use stdx::format_to; 12use stdx::format_to;
@@ -15,6 +16,16 @@ use crate::db::HirDatabase;
15 16
16pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; 17pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields};
17 18
19pub fn validate_module_item(
20 db: &dyn HirDatabase,
21 owner: ModuleDefId,
22 sink: &mut DiagnosticSink<'_>,
23) {
24 let _p = profile::span("validate_module_item");
25 let mut validator = decl_check::DeclValidator::new(owner, sink);
26 validator.validate_item(db);
27}
28
18pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { 29pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) {
19 let _p = profile::span("validate_body"); 30 let _p = profile::span("validate_body");
20 let infer = db.infer(owner); 31 let infer = db.infer(owner);
@@ -231,6 +242,66 @@ impl Diagnostic for MismatchedArgCount {
231 } 242 }
232} 243}
233 244
245#[derive(Debug)]
246pub enum CaseType {
247 // `some_var`
248 LowerSnakeCase,
249 // `SOME_CONST`
250 UpperSnakeCase,
251 // `SomeStruct`
252 UpperCamelCase,
253}
254
255impl fmt::Display for CaseType {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 let repr = match self {
258 CaseType::LowerSnakeCase => "snake_case",
259 CaseType::UpperSnakeCase => "UPPER_SNAKE_CASE",
260 CaseType::UpperCamelCase => "CamelCase",
261 };
262
263 write!(f, "{}", repr)
264 }
265}
266
267#[derive(Debug)]
268pub struct IncorrectCase {
269 pub file: HirFileId,
270 pub ident: AstPtr<ast::Name>,
271 pub expected_case: CaseType,
272 pub ident_type: String,
273 pub ident_text: String,
274 pub suggested_text: String,
275}
276
277impl Diagnostic for IncorrectCase {
278 fn code(&self) -> DiagnosticCode {
279 DiagnosticCode("incorrect-ident-case")
280 }
281
282 fn message(&self) -> String {
283 format!(
284 "{} `{}` should have {} name, e.g. `{}`",
285 self.ident_type,
286 self.ident_text,
287 self.expected_case.to_string(),
288 self.suggested_text
289 )
290 }
291
292 fn display_source(&self) -> InFile<SyntaxNodePtr> {
293 InFile::new(self.file, self.ident.clone().into())
294 }
295
296 fn as_any(&self) -> &(dyn Any + Send + 'static) {
297 self
298 }
299
300 fn is_experimental(&self) -> bool {
301 true
302 }
303}
304
234#[cfg(test)] 305#[cfg(test)]
235mod tests { 306mod tests {
236 use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; 307 use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt};
@@ -242,7 +313,10 @@ mod tests {
242 use rustc_hash::FxHashMap; 313 use rustc_hash::FxHashMap;
243 use syntax::{TextRange, TextSize}; 314 use syntax::{TextRange, TextSize};
244 315
245 use crate::{diagnostics::validate_body, test_db::TestDB}; 316 use crate::{
317 diagnostics::{validate_body, validate_module_item},
318 test_db::TestDB,
319 };
246 320
247 impl TestDB { 321 impl TestDB {
248 fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) { 322 fn diagnostics<F: FnMut(&dyn Diagnostic)>(&self, mut cb: F) {
@@ -253,6 +327,9 @@ mod tests {
253 let mut fns = Vec::new(); 327 let mut fns = Vec::new();
254 for (module_id, _) in crate_def_map.modules.iter() { 328 for (module_id, _) in crate_def_map.modules.iter() {
255 for decl in crate_def_map[module_id].scope.declarations() { 329 for decl in crate_def_map[module_id].scope.declarations() {
330 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
331 validate_module_item(self, decl, &mut sink);
332
256 if let ModuleDefId::FunctionId(f) = decl { 333 if let ModuleDefId::FunctionId(f) = decl {
257 fns.push(f) 334 fns.push(f)
258 } 335 }
@@ -262,6 +339,8 @@ mod tests {
262 let impl_data = self.impl_data(impl_id); 339 let impl_data = self.impl_data(impl_id);
263 for item in impl_data.items.iter() { 340 for item in impl_data.items.iter() {
264 if let AssocItemId::FunctionId(f) = item { 341 if let AssocItemId::FunctionId(f) = item {
342 let mut sink = DiagnosticSinkBuilder::new().build(&mut cb);
343 validate_module_item(self, ModuleDefId::FunctionId(*f), &mut sink);
265 fns.push(*f) 344 fns.push(*f)
266 } 345 }
267 } 346 }
diff --git a/crates/hir_ty/src/diagnostics/decl_check.rs b/crates/hir_ty/src/diagnostics/decl_check.rs
new file mode 100644
index 000000000..f987636fe
--- /dev/null
+++ b/crates/hir_ty/src/diagnostics/decl_check.rs
@@ -0,0 +1,833 @@
1//! Provides validators for the item declarations.
2//!
3//! This includes the following items:
4//!
5//! - variable bindings (e.g. `let x = foo();`)
6//! - struct fields (e.g. `struct Foo { field: u8 }`)
7//! - enum variants (e.g. `enum Foo { Variant { field: u8 } }`)
8//! - function/method arguments (e.g. `fn foo(arg: u8)`)
9//! - constants (e.g. `const FOO: u8 = 10;`)
10//! - static items (e.g. `static FOO: u8 = 10;`)
11//! - match arm bindings (e.g. `foo @ Some(_)`)
12
13mod case_conv;
14
15use hir_def::{
16 adt::VariantData,
17 expr::{Pat, PatId},
18 src::HasSource,
19 AdtId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId,
20};
21use hir_expand::{
22 diagnostics::DiagnosticSink,
23 name::{AsName, Name},
24};
25use syntax::{
26 ast::{self, NameOwner},
27 AstNode, AstPtr,
28};
29
30use crate::{
31 db::HirDatabase,
32 diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase},
33};
34
35pub(super) struct DeclValidator<'a, 'b: 'a> {
36 owner: ModuleDefId,
37 sink: &'a mut DiagnosticSink<'b>,
38}
39
40#[derive(Debug)]
41struct Replacement {
42 current_name: Name,
43 suggested_text: String,
44 expected_case: CaseType,
45}
46
47impl<'a, 'b> DeclValidator<'a, 'b> {
48 pub(super) fn new(
49 owner: ModuleDefId,
50 sink: &'a mut DiagnosticSink<'b>,
51 ) -> DeclValidator<'a, 'b> {
52 DeclValidator { owner, sink }
53 }
54
55 pub(super) fn validate_item(&mut self, db: &dyn HirDatabase) {
56 match self.owner {
57 ModuleDefId::FunctionId(func) => self.validate_func(db, func),
58 ModuleDefId::AdtId(adt) => self.validate_adt(db, adt),
59 ModuleDefId::ConstId(const_id) => self.validate_const(db, const_id),
60 ModuleDefId::StaticId(static_id) => self.validate_static(db, static_id),
61 _ => return,
62 }
63 }
64
65 fn validate_adt(&mut self, db: &dyn HirDatabase, adt: AdtId) {
66 match adt {
67 AdtId::StructId(struct_id) => self.validate_struct(db, struct_id),
68 AdtId::EnumId(enum_id) => self.validate_enum(db, enum_id),
69 AdtId::UnionId(_) => {
70 // Unions aren't yet supported by this validator.
71 }
72 }
73 }
74
75 fn validate_func(&mut self, db: &dyn HirDatabase, func: FunctionId) {
76 let data = db.function_data(func);
77 let body = db.body(func.into());
78
79 // 1. Check the function name.
80 let function_name = data.name.to_string();
81 let fn_name_replacement = if let Some(new_name) = to_lower_snake_case(&function_name) {
82 let replacement = Replacement {
83 current_name: data.name.clone(),
84 suggested_text: new_name,
85 expected_case: CaseType::LowerSnakeCase,
86 };
87 Some(replacement)
88 } else {
89 None
90 };
91
92 // 2. Check the param names.
93 let mut fn_param_replacements = Vec::new();
94
95 for pat_id in body.params.iter().cloned() {
96 let pat = &body[pat_id];
97
98 let param_name = match pat {
99 Pat::Bind { name, .. } => name,
100 _ => continue,
101 };
102
103 let name = param_name.to_string();
104 if let Some(new_name) = to_lower_snake_case(&name) {
105 let replacement = Replacement {
106 current_name: param_name.clone(),
107 suggested_text: new_name,
108 expected_case: CaseType::LowerSnakeCase,
109 };
110 fn_param_replacements.push(replacement);
111 }
112 }
113
114 // 3. Check the patterns inside the function body.
115 let mut pats_replacements = Vec::new();
116
117 for (pat_idx, pat) in body.pats.iter() {
118 if body.params.contains(&pat_idx) {
119 // We aren't interested in function parameters, we've processed them above.
120 continue;
121 }
122
123 let bind_name = match pat {
124 Pat::Bind { name, .. } => name,
125 _ => continue,
126 };
127
128 let name = bind_name.to_string();
129 if let Some(new_name) = to_lower_snake_case(&name) {
130 let replacement = Replacement {
131 current_name: bind_name.clone(),
132 suggested_text: new_name,
133 expected_case: CaseType::LowerSnakeCase,
134 };
135 pats_replacements.push((pat_idx, replacement));
136 }
137 }
138
139 // 4. If there is at least one element to spawn a warning on, go to the source map and generate a warning.
140 self.create_incorrect_case_diagnostic_for_func(
141 func,
142 db,
143 fn_name_replacement,
144 fn_param_replacements,
145 );
146 self.create_incorrect_case_diagnostic_for_variables(func, db, pats_replacements);
147
148 // 5. Recursively validate inner scope items, such as static variables and constants.
149 for (item_id, _) in body.item_scope.values() {
150 let mut validator = DeclValidator::new(item_id, self.sink);
151 validator.validate_item(db);
152 }
153 }
154
155 /// Given the information about incorrect names in the function declaration, looks up into the source code
156 /// for exact locations and adds diagnostics into the sink.
157 fn create_incorrect_case_diagnostic_for_func(
158 &mut self,
159 func: FunctionId,
160 db: &dyn HirDatabase,
161 fn_name_replacement: Option<Replacement>,
162 fn_param_replacements: Vec<Replacement>,
163 ) {
164 // XXX: only look at sources if we do have incorrect names
165 if fn_name_replacement.is_none() && fn_param_replacements.is_empty() {
166 return;
167 }
168
169 let fn_loc = func.lookup(db.upcast());
170 let fn_src = fn_loc.source(db.upcast());
171
172 // 1. Diagnostic for function name.
173 if let Some(replacement) = fn_name_replacement {
174 let ast_ptr = match fn_src.value.name() {
175 Some(name) => name,
176 None => {
177 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
178 log::error!(
179 "Replacement ({:?}) was generated for a function without a name: {:?}",
180 replacement,
181 fn_src
182 );
183 return;
184 }
185 };
186
187 let diagnostic = IncorrectCase {
188 file: fn_src.file_id,
189 ident_type: "Function".to_string(),
190 ident: AstPtr::new(&ast_ptr).into(),
191 expected_case: replacement.expected_case,
192 ident_text: replacement.current_name.to_string(),
193 suggested_text: replacement.suggested_text,
194 };
195
196 self.sink.push(diagnostic);
197 }
198
199 // 2. Diagnostics for function params.
200 let fn_params_list = match fn_src.value.param_list() {
201 Some(params) => params,
202 None => {
203 if !fn_param_replacements.is_empty() {
204 log::error!(
205 "Replacements ({:?}) were generated for a function parameters which had no parameters list: {:?}",
206 fn_param_replacements, fn_src
207 );
208 }
209 return;
210 }
211 };
212 let mut fn_params_iter = fn_params_list.params();
213 for param_to_rename in fn_param_replacements {
214 // We assume that parameters in replacement are in the same order as in the
215 // actual params list, but just some of them (ones that named correctly) are skipped.
216 let ast_ptr: ast::Name = loop {
217 match fn_params_iter.next() {
218 Some(element)
219 if pat_equals_to_name(element.pat(), &param_to_rename.current_name) =>
220 {
221 if let ast::Pat::IdentPat(pat) = element.pat().unwrap() {
222 break pat.name().unwrap();
223 } else {
224 // This is critical. If we consider this parameter the expected one,
225 // it **must** have a name.
226 panic!(
227 "Pattern {:?} equals to expected replacement {:?}, but has no name",
228 element, param_to_rename
229 );
230 }
231 }
232 Some(_) => {}
233 None => {
234 log::error!(
235 "Replacement ({:?}) was generated for a function parameter which was not found: {:?}",
236 param_to_rename, fn_src
237 );
238 return;
239 }
240 }
241 };
242
243 let diagnostic = IncorrectCase {
244 file: fn_src.file_id,
245 ident_type: "Argument".to_string(),
246 ident: AstPtr::new(&ast_ptr).into(),
247 expected_case: param_to_rename.expected_case,
248 ident_text: param_to_rename.current_name.to_string(),
249 suggested_text: param_to_rename.suggested_text,
250 };
251
252 self.sink.push(diagnostic);
253 }
254 }
255
256 /// Given the information about incorrect variable names, looks up into the source code
257 /// for exact locations and adds diagnostics into the sink.
258 fn create_incorrect_case_diagnostic_for_variables(
259 &mut self,
260 func: FunctionId,
261 db: &dyn HirDatabase,
262 pats_replacements: Vec<(PatId, Replacement)>,
263 ) {
264 // XXX: only look at source_map if we do have missing fields
265 if pats_replacements.is_empty() {
266 return;
267 }
268
269 let (_, source_map) = db.body_with_source_map(func.into());
270
271 for (id, replacement) in pats_replacements {
272 if let Ok(source_ptr) = source_map.pat_syntax(id) {
273 if let Some(expr) = source_ptr.value.as_ref().left() {
274 let root = source_ptr.file_syntax(db.upcast());
275 if let ast::Pat::IdentPat(ident_pat) = expr.to_node(&root) {
276 let parent = match ident_pat.syntax().parent() {
277 Some(parent) => parent,
278 None => continue,
279 };
280 let name_ast = match ident_pat.name() {
281 Some(name_ast) => name_ast,
282 None => continue,
283 };
284
285 // We have to check that it's either `let var = ...` or `var @ Variant(_)` statement,
286 // because e.g. match arms are patterns as well.
287 // In other words, we check that it's a named variable binding.
288 let is_binding = ast::LetStmt::cast(parent.clone()).is_some()
289 || (ast::MatchArm::cast(parent).is_some()
290 && ident_pat.at_token().is_some());
291 if !is_binding {
292 // This pattern is not an actual variable declaration, e.g. `Some(val) => {..}` match arm.
293 continue;
294 }
295
296 let diagnostic = IncorrectCase {
297 file: source_ptr.file_id,
298 ident_type: "Variable".to_string(),
299 ident: AstPtr::new(&name_ast).into(),
300 expected_case: replacement.expected_case,
301 ident_text: replacement.current_name.to_string(),
302 suggested_text: replacement.suggested_text,
303 };
304
305 self.sink.push(diagnostic);
306 }
307 }
308 }
309 }
310 }
311
312 fn validate_struct(&mut self, db: &dyn HirDatabase, struct_id: StructId) {
313 let data = db.struct_data(struct_id);
314
315 // 1. Check the structure name.
316 let struct_name = data.name.to_string();
317 let struct_name_replacement = if let Some(new_name) = to_camel_case(&struct_name) {
318 let replacement = Replacement {
319 current_name: data.name.clone(),
320 suggested_text: new_name,
321 expected_case: CaseType::UpperCamelCase,
322 };
323 Some(replacement)
324 } else {
325 None
326 };
327
328 // 2. Check the field names.
329 let mut struct_fields_replacements = Vec::new();
330
331 if let VariantData::Record(fields) = data.variant_data.as_ref() {
332 for (_, field) in fields.iter() {
333 let field_name = field.name.to_string();
334 if let Some(new_name) = to_lower_snake_case(&field_name) {
335 let replacement = Replacement {
336 current_name: field.name.clone(),
337 suggested_text: new_name,
338 expected_case: CaseType::LowerSnakeCase,
339 };
340 struct_fields_replacements.push(replacement);
341 }
342 }
343 }
344
345 // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning.
346 self.create_incorrect_case_diagnostic_for_struct(
347 struct_id,
348 db,
349 struct_name_replacement,
350 struct_fields_replacements,
351 );
352 }
353
354 /// Given the information about incorrect names in the struct declaration, looks up into the source code
355 /// for exact locations and adds diagnostics into the sink.
356 fn create_incorrect_case_diagnostic_for_struct(
357 &mut self,
358 struct_id: StructId,
359 db: &dyn HirDatabase,
360 struct_name_replacement: Option<Replacement>,
361 struct_fields_replacements: Vec<Replacement>,
362 ) {
363 // XXX: only look at sources if we do have incorrect names
364 if struct_name_replacement.is_none() && struct_fields_replacements.is_empty() {
365 return;
366 }
367
368 let struct_loc = struct_id.lookup(db.upcast());
369 let struct_src = struct_loc.source(db.upcast());
370
371 if let Some(replacement) = struct_name_replacement {
372 let ast_ptr = match struct_src.value.name() {
373 Some(name) => name,
374 None => {
375 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
376 log::error!(
377 "Replacement ({:?}) was generated for a structure without a name: {:?}",
378 replacement,
379 struct_src
380 );
381 return;
382 }
383 };
384
385 let diagnostic = IncorrectCase {
386 file: struct_src.file_id,
387 ident_type: "Structure".to_string(),
388 ident: AstPtr::new(&ast_ptr).into(),
389 expected_case: replacement.expected_case,
390 ident_text: replacement.current_name.to_string(),
391 suggested_text: replacement.suggested_text,
392 };
393
394 self.sink.push(diagnostic);
395 }
396
397 let struct_fields_list = match struct_src.value.field_list() {
398 Some(ast::FieldList::RecordFieldList(fields)) => fields,
399 _ => {
400 if !struct_fields_replacements.is_empty() {
401 log::error!(
402 "Replacements ({:?}) were generated for a structure fields which had no fields list: {:?}",
403 struct_fields_replacements, struct_src
404 );
405 }
406 return;
407 }
408 };
409 let mut struct_fields_iter = struct_fields_list.fields();
410 for field_to_rename in struct_fields_replacements {
411 // We assume that parameters in replacement are in the same order as in the
412 // actual params list, but just some of them (ones that named correctly) are skipped.
413 let ast_ptr = loop {
414 match struct_fields_iter.next() {
415 Some(element) if names_equal(element.name(), &field_to_rename.current_name) => {
416 break element.name().unwrap()
417 }
418 Some(_) => {}
419 None => {
420 log::error!(
421 "Replacement ({:?}) was generated for a structure field which was not found: {:?}",
422 field_to_rename, struct_src
423 );
424 return;
425 }
426 }
427 };
428
429 let diagnostic = IncorrectCase {
430 file: struct_src.file_id,
431 ident_type: "Field".to_string(),
432 ident: AstPtr::new(&ast_ptr).into(),
433 expected_case: field_to_rename.expected_case,
434 ident_text: field_to_rename.current_name.to_string(),
435 suggested_text: field_to_rename.suggested_text,
436 };
437
438 self.sink.push(diagnostic);
439 }
440 }
441
442 fn validate_enum(&mut self, db: &dyn HirDatabase, enum_id: EnumId) {
443 let data = db.enum_data(enum_id);
444
445 // 1. Check the enum name.
446 let enum_name = data.name.to_string();
447 let enum_name_replacement = if let Some(new_name) = to_camel_case(&enum_name) {
448 let replacement = Replacement {
449 current_name: data.name.clone(),
450 suggested_text: new_name,
451 expected_case: CaseType::UpperCamelCase,
452 };
453 Some(replacement)
454 } else {
455 None
456 };
457
458 // 2. Check the field names.
459 let mut enum_fields_replacements = Vec::new();
460
461 for (_, variant) in data.variants.iter() {
462 let variant_name = variant.name.to_string();
463 if let Some(new_name) = to_camel_case(&variant_name) {
464 let replacement = Replacement {
465 current_name: variant.name.clone(),
466 suggested_text: new_name,
467 expected_case: CaseType::UpperCamelCase,
468 };
469 enum_fields_replacements.push(replacement);
470 }
471 }
472
473 // 3. If there is at least one element to spawn a warning on, go to the source map and generate a warning.
474 self.create_incorrect_case_diagnostic_for_enum(
475 enum_id,
476 db,
477 enum_name_replacement,
478 enum_fields_replacements,
479 )
480 }
481
482 /// Given the information about incorrect names in the struct declaration, looks up into the source code
483 /// for exact locations and adds diagnostics into the sink.
484 fn create_incorrect_case_diagnostic_for_enum(
485 &mut self,
486 enum_id: EnumId,
487 db: &dyn HirDatabase,
488 enum_name_replacement: Option<Replacement>,
489 enum_variants_replacements: Vec<Replacement>,
490 ) {
491 // XXX: only look at sources if we do have incorrect names
492 if enum_name_replacement.is_none() && enum_variants_replacements.is_empty() {
493 return;
494 }
495
496 let enum_loc = enum_id.lookup(db.upcast());
497 let enum_src = enum_loc.source(db.upcast());
498
499 if let Some(replacement) = enum_name_replacement {
500 let ast_ptr = match enum_src.value.name() {
501 Some(name) => name,
502 None => {
503 // We don't want rust-analyzer to panic over this, but it is definitely some kind of error in the logic.
504 log::error!(
505 "Replacement ({:?}) was generated for a enum without a name: {:?}",
506 replacement,
507 enum_src
508 );
509 return;
510 }
511 };
512
513 let diagnostic = IncorrectCase {
514 file: enum_src.file_id,
515 ident_type: "Enum".to_string(),
516 ident: AstPtr::new(&ast_ptr).into(),
517 expected_case: replacement.expected_case,
518 ident_text: replacement.current_name.to_string(),
519 suggested_text: replacement.suggested_text,
520 };
521
522 self.sink.push(diagnostic);
523 }
524
525 let enum_variants_list = match enum_src.value.variant_list() {
526 Some(variants) => variants,
527 _ => {
528 if !enum_variants_replacements.is_empty() {
529 log::error!(
530 "Replacements ({:?}) were generated for a enum variants which had no fields list: {:?}",
531 enum_variants_replacements, enum_src
532 );
533 }
534 return;
535 }
536 };
537 let mut enum_variants_iter = enum_variants_list.variants();
538 for variant_to_rename in enum_variants_replacements {
539 // We assume that parameters in replacement are in the same order as in the
540 // actual params list, but just some of them (ones that named correctly) are skipped.
541 let ast_ptr = loop {
542 match enum_variants_iter.next() {
543 Some(variant)
544 if names_equal(variant.name(), &variant_to_rename.current_name) =>
545 {
546 break variant.name().unwrap()
547 }
548 Some(_) => {}
549 None => {
550 log::error!(
551 "Replacement ({:?}) was generated for a enum variant which was not found: {:?}",
552 variant_to_rename, enum_src
553 );
554 return;
555 }
556 }
557 };
558
559 let diagnostic = IncorrectCase {
560 file: enum_src.file_id,
561 ident_type: "Variant".to_string(),
562 ident: AstPtr::new(&ast_ptr).into(),
563 expected_case: variant_to_rename.expected_case,
564 ident_text: variant_to_rename.current_name.to_string(),
565 suggested_text: variant_to_rename.suggested_text,
566 };
567
568 self.sink.push(diagnostic);
569 }
570 }
571
572 fn validate_const(&mut self, db: &dyn HirDatabase, const_id: ConstId) {
573 let data = db.const_data(const_id);
574
575 let name = match &data.name {
576 Some(name) => name,
577 None => return,
578 };
579
580 let const_name = name.to_string();
581 let replacement = if let Some(new_name) = to_upper_snake_case(&const_name) {
582 Replacement {
583 current_name: name.clone(),
584 suggested_text: new_name,
585 expected_case: CaseType::UpperSnakeCase,
586 }
587 } else {
588 // Nothing to do here.
589 return;
590 };
591
592 let const_loc = const_id.lookup(db.upcast());
593 let const_src = const_loc.source(db.upcast());
594
595 let ast_ptr = match const_src.value.name() {
596 Some(name) => name,
597 None => return,
598 };
599
600 let diagnostic = IncorrectCase {
601 file: const_src.file_id,
602 ident_type: "Constant".to_string(),
603 ident: AstPtr::new(&ast_ptr).into(),
604 expected_case: replacement.expected_case,
605 ident_text: replacement.current_name.to_string(),
606 suggested_text: replacement.suggested_text,
607 };
608
609 self.sink.push(diagnostic);
610 }
611
612 fn validate_static(&mut self, db: &dyn HirDatabase, static_id: StaticId) {
613 let data = db.static_data(static_id);
614
615 let name = match &data.name {
616 Some(name) => name,
617 None => return,
618 };
619
620 let static_name = name.to_string();
621 let replacement = if let Some(new_name) = to_upper_snake_case(&static_name) {
622 Replacement {
623 current_name: name.clone(),
624 suggested_text: new_name,
625 expected_case: CaseType::UpperSnakeCase,
626 }
627 } else {
628 // Nothing to do here.
629 return;
630 };
631
632 let static_loc = static_id.lookup(db.upcast());
633 let static_src = static_loc.source(db.upcast());
634
635 let ast_ptr = match static_src.value.name() {
636 Some(name) => name,
637 None => return,
638 };
639
640 let diagnostic = IncorrectCase {
641 file: static_src.file_id,
642 ident_type: "Static variable".to_string(),
643 ident: AstPtr::new(&ast_ptr).into(),
644 expected_case: replacement.expected_case,
645 ident_text: replacement.current_name.to_string(),
646 suggested_text: replacement.suggested_text,
647 };
648
649 self.sink.push(diagnostic);
650 }
651}
652
653fn names_equal(left: Option<ast::Name>, right: &Name) -> bool {
654 if let Some(left) = left {
655 &left.as_name() == right
656 } else {
657 false
658 }
659}
660
661fn pat_equals_to_name(pat: Option<ast::Pat>, name: &Name) -> bool {
662 if let Some(ast::Pat::IdentPat(ident)) = pat {
663 ident.to_string() == name.to_string()
664 } else {
665 false
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use crate::diagnostics::tests::check_diagnostics;
672
673 #[test]
674 fn incorrect_function_name() {
675 check_diagnostics(
676 r#"
677fn NonSnakeCaseName() {}
678// ^^^^^^^^^^^^^^^^ Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
679"#,
680 );
681 }
682
683 #[test]
684 fn incorrect_function_params() {
685 check_diagnostics(
686 r#"
687fn foo(SomeParam: u8) {}
688 // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param`
689
690fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
691 // ^^^^^^^^^^ Argument `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
692"#,
693 );
694 }
695
696 #[test]
697 fn incorrect_variable_names() {
698 check_diagnostics(
699 r#"
700fn foo() {
701 let SOME_VALUE = 10;
702 // ^^^^^^^^^^ Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
703 let AnotherValue = 20;
704 // ^^^^^^^^^^^^ Variable `AnotherValue` should have snake_case name, e.g. `another_value`
705}
706"#,
707 );
708 }
709
710 #[test]
711 fn incorrect_struct_name() {
712 check_diagnostics(
713 r#"
714struct non_camel_case_name {}
715 // ^^^^^^^^^^^^^^^^^^^ Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
716"#,
717 );
718 }
719
720 #[test]
721 fn incorrect_struct_field() {
722 check_diagnostics(
723 r#"
724struct SomeStruct { SomeField: u8 }
725 // ^^^^^^^^^ Field `SomeField` should have snake_case name, e.g. `some_field`
726"#,
727 );
728 }
729
730 #[test]
731 fn incorrect_enum_name() {
732 check_diagnostics(
733 r#"
734enum some_enum { Val(u8) }
735 // ^^^^^^^^^ Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
736"#,
737 );
738 }
739
740 #[test]
741 fn incorrect_enum_variant_name() {
742 check_diagnostics(
743 r#"
744enum SomeEnum { SOME_VARIANT(u8) }
745 // ^^^^^^^^^^^^ Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
746"#,
747 );
748 }
749
750 #[test]
751 fn incorrect_const_name() {
752 check_diagnostics(
753 r#"
754const some_weird_const: u8 = 10;
755 // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
756
757fn func() {
758 const someConstInFunc: &str = "hi there";
759 // ^^^^^^^^^^^^^^^ Constant `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
760
761}
762"#,
763 );
764 }
765
766 #[test]
767 fn incorrect_static_name() {
768 check_diagnostics(
769 r#"
770static some_weird_const: u8 = 10;
771 // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
772
773fn func() {
774 static someConstInFunc: &str = "hi there";
775 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
776}
777"#,
778 );
779 }
780
781 #[test]
782 fn fn_inside_impl_struct() {
783 check_diagnostics(
784 r#"
785struct someStruct;
786 // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
787
788impl someStruct {
789 fn SomeFunc(&self) {
790 // ^^^^^^^^ Function `SomeFunc` should have snake_case name, e.g. `some_func`
791 static someConstInFunc: &str = "hi there";
792 // ^^^^^^^^^^^^^^^ Static variable `someConstInFunc` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST_IN_FUNC`
793 let WHY_VAR_IS_CAPS = 10;
794 // ^^^^^^^^^^^^^^^ Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
795 }
796}
797"#,
798 );
799 }
800
801 #[test]
802 fn no_diagnostic_for_enum_varinats() {
803 check_diagnostics(
804 r#"
805enum Option { Some, None }
806
807fn main() {
808 match Option::None {
809 None => (),
810 Some => (),
811 }
812}
813"#,
814 );
815 }
816
817 #[test]
818 fn non_let_bind() {
819 check_diagnostics(
820 r#"
821enum Option { Some, None }
822
823fn main() {
824 match Option::None {
825 SOME_VAR @ None => (),
826 // ^^^^^^^^ Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
827 Some => (),
828 }
829}
830"#,
831 );
832 }
833}
diff --git a/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs
new file mode 100644
index 000000000..3800f2a6b
--- /dev/null
+++ b/crates/hir_ty/src/diagnostics/decl_check/case_conv.rs
@@ -0,0 +1,194 @@
1//! Functions for string case manipulation, such as detecting the identifier case,
2//! and converting it into appropriate form.
3
4#[derive(Debug)]
5enum DetectedCase {
6 LowerCamelCase,
7 UpperCamelCase,
8 LowerSnakeCase,
9 UpperSnakeCase,
10 Unknown,
11}
12
13fn detect_case(ident: &str) -> DetectedCase {
14 let trimmed_ident = ident.trim_matches('_');
15 let first_lowercase = trimmed_ident.starts_with(|chr: char| chr.is_ascii_lowercase());
16 let mut has_lowercase = first_lowercase;
17 let mut has_uppercase = false;
18 let mut has_underscore = false;
19
20 for chr in trimmed_ident.chars() {
21 if chr == '_' {
22 has_underscore = true;
23 } else if chr.is_ascii_uppercase() {
24 has_uppercase = true;
25 } else if chr.is_ascii_lowercase() {
26 has_lowercase = true;
27 }
28 }
29
30 if has_uppercase {
31 if !has_lowercase {
32 DetectedCase::UpperSnakeCase
33 } else if !has_underscore {
34 if first_lowercase {
35 DetectedCase::LowerCamelCase
36 } else {
37 DetectedCase::UpperCamelCase
38 }
39 } else {
40 // It has uppercase, it has lowercase, it has underscore.
41 // No assumptions here
42 DetectedCase::Unknown
43 }
44 } else {
45 DetectedCase::LowerSnakeCase
46 }
47}
48
49/// Converts an identifier to an UpperCamelCase form.
50/// Returns `None` if the string is already is UpperCamelCase.
51pub fn to_camel_case(ident: &str) -> Option<String> {
52 let detected_case = detect_case(ident);
53
54 match detected_case {
55 DetectedCase::UpperCamelCase => return None,
56 DetectedCase::LowerCamelCase => {
57 let mut first_capitalized = false;
58 let output = ident
59 .chars()
60 .map(|chr| {
61 if !first_capitalized && chr.is_ascii_lowercase() {
62 first_capitalized = true;
63 chr.to_ascii_uppercase()
64 } else {
65 chr
66 }
67 })
68 .collect();
69 return Some(output);
70 }
71 _ => {}
72 }
73
74 let mut output = String::with_capacity(ident.len());
75
76 let mut capital_added = false;
77 for chr in ident.chars() {
78 if chr.is_alphabetic() {
79 if !capital_added {
80 output.push(chr.to_ascii_uppercase());
81 capital_added = true;
82 } else {
83 output.push(chr.to_ascii_lowercase());
84 }
85 } else if chr == '_' {
86 // Skip this character and make the next one capital.
87 capital_added = false;
88 } else {
89 // Put the characted as-is.
90 output.push(chr);
91 }
92 }
93
94 if output == ident {
95 // While we didn't detect the correct case at the beginning, there
96 // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE.
97 None
98 } else {
99 Some(output)
100 }
101}
102
103/// Converts an identifier to a lower_snake_case form.
104/// Returns `None` if the string is already in lower_snake_case.
105pub fn to_lower_snake_case(ident: &str) -> Option<String> {
106 // First, assume that it's UPPER_SNAKE_CASE.
107 match detect_case(ident) {
108 DetectedCase::LowerSnakeCase => return None,
109 DetectedCase::UpperSnakeCase => {
110 return Some(ident.chars().map(|chr| chr.to_ascii_lowercase()).collect())
111 }
112 _ => {}
113 }
114
115 // Otherwise, assume that it's CamelCase.
116 let lower_snake_case = stdx::to_lower_snake_case(ident);
117
118 if lower_snake_case == ident {
119 // While we didn't detect the correct case at the beginning, there
120 // may be special cases: e.g. `a` is both valid camelCase and snake_case.
121 None
122 } else {
123 Some(lower_snake_case)
124 }
125}
126
127/// Converts an identifier to an UPPER_SNAKE_CASE form.
128/// Returns `None` if the string is already is UPPER_SNAKE_CASE.
129pub fn to_upper_snake_case(ident: &str) -> Option<String> {
130 match detect_case(ident) {
131 DetectedCase::UpperSnakeCase => return None,
132 DetectedCase::LowerSnakeCase => {
133 return Some(ident.chars().map(|chr| chr.to_ascii_uppercase()).collect())
134 }
135 _ => {}
136 }
137
138 // Normalize the string from whatever form it's in currently, and then just make it uppercase.
139 let upper_snake_case = stdx::to_upper_snake_case(ident);
140
141 if upper_snake_case == ident {
142 // While we didn't detect the correct case at the beginning, there
143 // may be special cases: e.g. `A` is both valid CamelCase and UPPER_SNAKE_CASE.
144 None
145 } else {
146 Some(upper_snake_case)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use expect_test::{expect, Expect};
154
155 fn check<F: Fn(&str) -> Option<String>>(fun: F, input: &str, expect: Expect) {
156 // `None` is translated to empty string, meaning that there is nothing to fix.
157 let output = fun(input).unwrap_or_default();
158
159 expect.assert_eq(&output);
160 }
161
162 #[test]
163 fn test_to_lower_snake_case() {
164 check(to_lower_snake_case, "lower_snake_case", expect![[""]]);
165 check(to_lower_snake_case, "UPPER_SNAKE_CASE", expect![["upper_snake_case"]]);
166 check(to_lower_snake_case, "Weird_Case", expect![["weird_case"]]);
167 check(to_lower_snake_case, "CamelCase", expect![["camel_case"]]);
168 check(to_lower_snake_case, "lowerCamelCase", expect![["lower_camel_case"]]);
169 check(to_lower_snake_case, "a", expect![[""]]);
170 }
171
172 #[test]
173 fn test_to_camel_case() {
174 check(to_camel_case, "CamelCase", expect![[""]]);
175 check(to_camel_case, "CamelCase_", expect![[""]]);
176 check(to_camel_case, "_CamelCase", expect![[""]]);
177 check(to_camel_case, "lowerCamelCase", expect![["LowerCamelCase"]]);
178 check(to_camel_case, "lower_snake_case", expect![["LowerSnakeCase"]]);
179 check(to_camel_case, "UPPER_SNAKE_CASE", expect![["UpperSnakeCase"]]);
180 check(to_camel_case, "Weird_Case", expect![["WeirdCase"]]);
181 check(to_camel_case, "name", expect![["Name"]]);
182 check(to_camel_case, "A", expect![[""]]);
183 }
184
185 #[test]
186 fn test_to_upper_snake_case() {
187 check(to_upper_snake_case, "UPPER_SNAKE_CASE", expect![[""]]);
188 check(to_upper_snake_case, "lower_snake_case", expect![["LOWER_SNAKE_CASE"]]);
189 check(to_upper_snake_case, "Weird_Case", expect![["WEIRD_CASE"]]);
190 check(to_upper_snake_case, "CamelCase", expect![["CAMEL_CASE"]]);
191 check(to_upper_snake_case, "lowerCamelCase", expect![["LOWER_CAMEL_CASE"]]);
192 check(to_upper_snake_case, "A", expect![[""]]);
193 }
194}
diff --git a/crates/hir_ty/src/diagnostics/unsafe_check.rs b/crates/hir_ty/src/diagnostics/unsafe_check.rs
index 61ffbf5d1..21a121aad 100644
--- a/crates/hir_ty/src/diagnostics/unsafe_check.rs
+++ b/crates/hir_ty/src/diagnostics/unsafe_check.rs
@@ -190,13 +190,13 @@ struct Ty {
190 a: u8, 190 a: u8,
191} 191}
192 192
193static mut static_mut: Ty = Ty { a: 0 }; 193static mut STATIC_MUT: Ty = Ty { a: 0 };
194 194
195fn main() { 195fn main() {
196 let x = static_mut.a; 196 let x = STATIC_MUT.a;
197 //^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block 197 //^^^^^^^^^^ This operation is unsafe and requires an unsafe function or block
198 unsafe { 198 unsafe {
199 let x = static_mut.a; 199 let x = STATIC_MUT.a;
200 } 200 }
201} 201}
202"#, 202"#,
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
index 81c33bf3a..50d1e5c81 100644
--- a/crates/ide/src/completion/complete_postfix/format_like.rs
+++ b/crates/ide/src/completion/complete_postfix/format_like.rs
@@ -25,6 +25,7 @@ static KINDS: &[(&str, &str)] = &[
25 ("fmt", "format!"), 25 ("fmt", "format!"),
26 ("panic", "panic!"), 26 ("panic", "panic!"),
27 ("println", "println!"), 27 ("println", "println!"),
28 ("eprintln", "eprintln!"),
28 ("logd", "log::debug!"), 29 ("logd", "log::debug!"),
29 ("logt", "log::trace!"), 30 ("logt", "log::trace!"),
30 ("logi", "log::info!"), 31 ("logi", "log::info!"),
@@ -259,6 +260,7 @@ mod tests {
259 fn test_into_suggestion() { 260 fn test_into_suggestion() {
260 let test_vector = &[ 261 let test_vector = &[
261 ("println!", "{}", r#"println!("{}", $1)"#), 262 ("println!", "{}", r#"println!("{}", $1)"#),
263 ("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
262 ( 264 (
263 "log::info!", 265 "log::info!",
264 "{} {expr} {} {2 + 2}", 266 "{} {expr} {} {2 + 2}",
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index f5d627b6e..b30cdb6ed 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -96,6 +96,9 @@ pub(crate) fn diagnostics(
96 .on::<hir::diagnostics::NoSuchField, _>(|d| { 96 .on::<hir::diagnostics::NoSuchField, _>(|d| {
97 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 97 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
98 }) 98 })
99 .on::<hir::diagnostics::IncorrectCase, _>(|d| {
100 res.borrow_mut().push(warning_with_fix(d, &sema));
101 })
99 // Only collect experimental diagnostics when they're enabled. 102 // Only collect experimental diagnostics when they're enabled.
100 .filter(|diag| !(diag.is_experimental() && config.disable_experimental)) 103 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
101 .filter(|diag| !config.disabled.contains(diag.code().as_str())); 104 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
@@ -130,6 +133,15 @@ fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabas
130 } 133 }
131} 134}
132 135
136fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
137 Diagnostic {
138 range: sema.diagnostics_display_range(d).range,
139 message: d.message(),
140 severity: Severity::WeakWarning,
141 fix: d.fix(&sema),
142 }
143}
144
133fn check_unnecessary_braces_in_use_statement( 145fn check_unnecessary_braces_in_use_statement(
134 acc: &mut Vec<Diagnostic>, 146 acc: &mut Vec<Diagnostic>,
135 file_id: FileId, 147 file_id: FileId,
@@ -245,8 +257,37 @@ mod tests {
245 257
246 assert_eq_text!(&after, &actual); 258 assert_eq_text!(&after, &actual);
247 assert!( 259 assert!(
248 fix.fix_trigger_range.start() <= file_position.offset 260 fix.fix_trigger_range.contains_inclusive(file_position.offset),
249 && fix.fix_trigger_range.end() >= file_position.offset, 261 "diagnostic fix range {:?} does not touch cursor position {:?}",
262 fix.fix_trigger_range,
263 file_position.offset
264 );
265 }
266
267 /// Similar to `check_fix`, but applies all the available fixes.
268 fn check_fixes(ra_fixture_before: &str, ra_fixture_after: &str) {
269 let after = trim_indent(ra_fixture_after);
270
271 let (analysis, file_position) = fixture::position(ra_fixture_before);
272 let diagnostic = analysis
273 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
274 .unwrap()
275 .pop()
276 .unwrap();
277 let fix = diagnostic.fix.unwrap();
278 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
279 let actual = {
280 let mut actual = target_file_contents.to_string();
281 // Go from the last one to the first one, so that ranges won't be affected by previous edits.
282 for edit in fix.source_change.source_file_edits.iter().rev() {
283 edit.edit.apply(&mut actual);
284 }
285 actual
286 };
287
288 assert_eq_text!(&after, &actual);
289 assert!(
290 fix.fix_trigger_range.contains_inclusive(file_position.offset),
250 "diagnostic fix range {:?} does not touch cursor position {:?}", 291 "diagnostic fix range {:?} does not touch cursor position {:?}",
251 fix.fix_trigger_range, 292 fix.fix_trigger_range,
252 file_position.offset 293 file_position.offset
@@ -790,4 +831,100 @@ struct Foo {
790 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 831 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
791 assert!(!diagnostics.is_empty()); 832 assert!(!diagnostics.is_empty());
792 } 833 }
834
835 #[test]
836 fn test_rename_incorrect_case() {
837 check_fixes(
838 r#"
839pub struct test_struct<|> { one: i32 }
840
841pub fn some_fn(val: test_struct) -> test_struct {
842 test_struct { one: val.one + 1 }
843}
844"#,
845 r#"
846pub struct TestStruct { one: i32 }
847
848pub fn some_fn(val: TestStruct) -> TestStruct {
849 TestStruct { one: val.one + 1 }
850}
851"#,
852 );
853
854 check_fixes(
855 r#"
856pub fn some_fn(NonSnakeCase<|>: u8) -> u8 {
857 NonSnakeCase
858}
859"#,
860 r#"
861pub fn some_fn(non_snake_case: u8) -> u8 {
862 non_snake_case
863}
864"#,
865 );
866
867 check_fixes(
868 r#"
869pub fn SomeFn<|>(val: u8) -> u8 {
870 if val != 0 { SomeFn(val - 1) } else { val }
871}
872"#,
873 r#"
874pub fn some_fn(val: u8) -> u8 {
875 if val != 0 { some_fn(val - 1) } else { val }
876}
877"#,
878 );
879
880 check_fixes(
881 r#"
882fn some_fn() {
883 let whatAWeird_Formatting<|> = 10;
884 another_func(whatAWeird_Formatting);
885}
886"#,
887 r#"
888fn some_fn() {
889 let what_a_weird_formatting = 10;
890 another_func(what_a_weird_formatting);
891}
892"#,
893 );
894 }
895
896 #[test]
897 fn test_uppercase_const_no_diagnostics() {
898 check_no_diagnostics(
899 r#"
900fn foo() {
901 const ANOTHER_ITEM<|>: &str = "some_item";
902}
903"#,
904 );
905 }
906
907 #[test]
908 fn test_rename_incorrect_case_struct_method() {
909 check_fixes(
910 r#"
911pub struct TestStruct;
912
913impl TestStruct {
914 pub fn SomeFn<|>() -> TestStruct {
915 TestStruct
916 }
917}
918"#,
919 r#"
920pub struct TestStruct;
921
922impl TestStruct {
923 pub fn some_fn() -> TestStruct {
924 TestStruct
925 }
926}
927"#,
928 );
929 }
793} 930}
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 68ae1c239..0c75e50b0 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -3,7 +3,10 @@
3use base_db::FileId; 3use base_db::FileId;
4use hir::{ 4use hir::{
5 db::AstDatabase, 5 db::AstDatabase,
6 diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, 6 diagnostics::{
7 Diagnostic, IncorrectCase, MissingFields, MissingOkInTailExpr, NoSuchField,
8 UnresolvedModule,
9 },
7 HasSource, HirDisplay, Semantics, VariantDef, 10 HasSource, HirDisplay, Semantics, VariantDef,
8}; 11};
9use ide_db::{ 12use ide_db::{
@@ -17,7 +20,7 @@ use syntax::{
17}; 20};
18use text_edit::TextEdit; 21use text_edit::TextEdit;
19 22
20use crate::diagnostics::Fix; 23use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition};
21 24
22/// A [Diagnostic] that potentially has a fix available. 25/// A [Diagnostic] that potentially has a fix available.
23/// 26///
@@ -99,6 +102,23 @@ impl DiagnosticWithFix for MissingOkInTailExpr {
99 } 102 }
100} 103}
101 104
105impl DiagnosticWithFix for IncorrectCase {
106 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
107 let root = sema.db.parse_or_expand(self.file)?;
108 let name_node = self.ident.to_node(&root);
109
110 let file_id = self.file.original_file(sema.db);
111 let offset = name_node.syntax().text_range().start();
112 let file_position = FilePosition { file_id, offset };
113
114 let rename_changes =
115 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
116
117 let label = format!("Rename to {}", self.suggested_text);
118 Some(Fix::new(&label, rename_changes.info, rename_changes.range))
119 }
120}
121
102fn missing_record_expr_field_fix( 122fn missing_record_expr_field_fix(
103 sema: &Semantics<RootDatabase>, 123 sema: &Semantics<RootDatabase>,
104 usage_file_id: FileId, 124 usage_file_id: FileId,
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/doc_links.rs
index c317a2379..06af36b73 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,13 +1,27 @@
1//! Resolves and rewrites links in markdown documentation. 1//! Resolves and rewrites links in markdown documentation.
2//!
3//! Most of the implementation can be found in [`hir::doc_links`].
4 2
5use hir::{Adt, Crate, HasAttrs, ModuleDef}; 3use std::iter::once;
6use ide_db::{defs::Definition, RootDatabase}; 4
5use itertools::Itertools;
7use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; 6use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag};
8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; 7use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
9use url::Url; 8use url::Url;
10 9
10use hir::{
11 db::{DefDatabase, HirDatabase},
12 Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs,
13 ModuleDef,
14};
15use ide_db::{
16 defs::{classify_name, classify_name_ref, Definition},
17 RootDatabase,
18};
19use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
20
21use crate::{FilePosition, Semantics};
22
23pub type DocumentationLink = String;
24
11/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 25/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
12pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 26pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
13 let doc = Parser::new_with_broken_link_callback( 27 let doc = Parser::new_with_broken_link_callback(
@@ -80,6 +94,70 @@ pub fn remove_links(markdown: &str) -> String {
80 out 94 out
81} 95}
82 96
97// FIXME:
98// BUG: For Option::Some
99// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
100// Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html
101//
102// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
103// https://github.com/rust-lang/rfcs/pull/2988
104fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
105 // Get the outermost definition for the moduledef. This is used to resolve the public path to the type,
106 // then we can join the method, field, etc onto it if required.
107 let target_def: ModuleDef = match definition {
108 Definition::ModuleDef(moddef) => match moddef {
109 ModuleDef::Function(f) => f
110 .as_assoc_item(db)
111 .and_then(|assoc| match assoc.container(db) {
112 AssocItemContainer::Trait(t) => Some(t.into()),
113 AssocItemContainer::ImplDef(impld) => {
114 impld.target_ty(db).as_adt().map(|adt| adt.into())
115 }
116 })
117 .unwrap_or_else(|| f.clone().into()),
118 moddef => moddef,
119 },
120 Definition::Field(f) => f.parent_def(db).into(),
121 // FIXME: Handle macros
122 _ => return None,
123 };
124
125 let ns = ItemInNs::from(target_def.clone());
126
127 let module = definition.module(db)?;
128 let krate = module.krate();
129 let import_map = db.import_map(krate.into());
130 let base = once(krate.declaration_name(db)?.to_string())
131 .chain(import_map.path_of(ns)?.segments.iter().map(|name| name.to_string()))
132 .join("/");
133
134 let filename = get_symbol_filename(db, &target_def);
135 let fragment = match definition {
136 Definition::ModuleDef(moddef) => match moddef {
137 ModuleDef::Function(f) => {
138 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(f)))
139 }
140 ModuleDef::Const(c) => {
141 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(c)))
142 }
143 ModuleDef::TypeAlias(ty) => {
144 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(ty)))
145 }
146 _ => None,
147 },
148 Definition::Field(field) => get_symbol_fragment(db, &FieldOrAssocItem::Field(field)),
149 _ => None,
150 };
151
152 get_doc_url(db, &krate)
153 .and_then(|url| url.join(&base).ok())
154 .and_then(|url| filename.as_deref().and_then(|f| url.join(f).ok()))
155 .and_then(
156 |url| if let Some(fragment) = fragment { url.join(&fragment).ok() } else { Some(url) },
157 )
158 .map(|url| url.into_string())
159}
160
83fn rewrite_intra_doc_link( 161fn rewrite_intra_doc_link(
84 db: &RootDatabase, 162 db: &RootDatabase,
85 def: Definition, 163 def: Definition,
@@ -138,7 +216,29 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
138 .map(|url| url.into_string()) 216 .map(|url| url.into_string())
139} 217}
140 218
141// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. 219/// Retrieve a link to documentation for the given symbol.
220pub(crate) fn external_docs(
221 db: &RootDatabase,
222 position: &FilePosition,
223) -> Option<DocumentationLink> {
224 let sema = Semantics::new(db);
225 let file = sema.parse(position.file_id).syntax().clone();
226 let token = pick_best(file.token_at_offset(position.offset))?;
227 let token = sema.descend_into_macros(token);
228
229 let node = token.parent();
230 let definition = match_ast! {
231 match node {
232 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
233 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
234 _ => None,
235 }
236 };
237
238 get_doc_link(db, definition?)
239}
240
241/// Rewrites a markdown document, applying 'callback' to each link.
142fn map_links<'e>( 242fn map_links<'e>(
143 events: impl Iterator<Item = Event<'e>>, 243 events: impl Iterator<Item = Event<'e>>,
144 callback: impl Fn(&str, &str) -> (String, String), 244 callback: impl Fn(&str, &str) -> (String, String),
@@ -239,6 +339,12 @@ fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> {
239 .next() 339 .next()
240} 340}
241 341
342/// Get the root URL for the documentation of a crate.
343///
344/// ```
345/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
346/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
347/// ```
242fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { 348fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
243 krate 349 krate
244 .get_html_root_url(db) 350 .get_html_root_url(db)
@@ -255,8 +361,11 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
255 361
256/// Get the filename and extension generated for a symbol by rustdoc. 362/// Get the filename and extension generated for a symbol by rustdoc.
257/// 363///
258/// Example: `struct.Shard.html` 364/// ```
259fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<String> { 365/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
366/// ^^^^^^^^^^^^^^^^^^^
367/// ```
368fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> {
260 Some(match definition { 369 Some(match definition {
261 ModuleDef::Adt(adt) => match adt { 370 ModuleDef::Adt(adt) => match adt {
262 Adt::Struct(s) => format!("struct.{}.html", s.name(db)), 371 Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
@@ -266,7 +375,7 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
266 ModuleDef::Module(_) => "index.html".to_string(), 375 ModuleDef::Module(_) => "index.html".to_string(),
267 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), 376 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
268 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), 377 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
269 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t), 378 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
270 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), 379 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
271 ModuleDef::EnumVariant(ev) => { 380 ModuleDef::EnumVariant(ev) => {
272 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) 381 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
@@ -275,3 +384,163 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
275 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), 384 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
276 }) 385 })
277} 386}
387
388enum FieldOrAssocItem {
389 Field(Field),
390 AssocItem(AssocItem),
391}
392
393/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
394///
395/// ```
396/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
397/// ^^^^^^^^^^^^^^
398/// ```
399fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> {
400 Some(match field_or_assoc {
401 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
402 FieldOrAssocItem::AssocItem(assoc) => match assoc {
403 AssocItem::Function(function) => {
404 let is_trait_method = matches!(
405 function.as_assoc_item(db).map(|assoc| assoc.container(db)),
406 Some(AssocItemContainer::Trait(..))
407 );
408 // This distinction may get more complicated when specialisation is available.
409 // Rustdoc makes this decision based on whether a method 'has defaultness'.
410 // Currently this is only the case for provided trait methods.
411 if is_trait_method && !function.has_body(db) {
412 format!("#tymethod.{}", function.name(db))
413 } else {
414 format!("#method.{}", function.name(db))
415 }
416 }
417 AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?),
418 AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)),
419 },
420 })
421}
422
423fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
424 return tokens.max_by_key(priority);
425 fn priority(n: &SyntaxToken) -> usize {
426 match n.kind() {
427 IDENT | INT_NUMBER => 3,
428 T!['('] | T![')'] => 2,
429 kind if kind.is_trivia() => 0,
430 _ => 1,
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use expect_test::{expect, Expect};
438
439 use crate::fixture;
440
441 fn check(ra_fixture: &str, expect: Expect) {
442 let (analysis, position) = fixture::position(ra_fixture);
443 let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
444
445 expect.assert_eq(&url)
446 }
447
448 #[test]
449 fn test_doc_url_struct() {
450 check(
451 r#"
452pub struct Fo<|>o;
453"#,
454 expect![[r#"https://docs.rs/test/*/test/struct.Foo.html"#]],
455 );
456 }
457
458 #[test]
459 fn test_doc_url_fn() {
460 check(
461 r#"
462pub fn fo<|>o() {}
463"#,
464 expect![[r##"https://docs.rs/test/*/test/fn.foo.html#method.foo"##]],
465 );
466 }
467
468 #[test]
469 fn test_doc_url_inherent_method() {
470 check(
471 r#"
472pub struct Foo;
473
474impl Foo {
475 pub fn met<|>hod() {}
476}
477
478"#,
479 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#method.method"##]],
480 );
481 }
482
483 #[test]
484 fn test_doc_url_trait_provided_method() {
485 check(
486 r#"
487pub trait Bar {
488 fn met<|>hod() {}
489}
490
491"#,
492 expect![[r##"https://docs.rs/test/*/test/trait.Bar.html#method.method"##]],
493 );
494 }
495
496 #[test]
497 fn test_doc_url_trait_required_method() {
498 check(
499 r#"
500pub trait Foo {
501 fn met<|>hod();
502}
503
504"#,
505 expect![[r##"https://docs.rs/test/*/test/trait.Foo.html#tymethod.method"##]],
506 );
507 }
508
509 #[test]
510 fn test_doc_url_field() {
511 check(
512 r#"
513pub struct Foo {
514 pub fie<|>ld: ()
515}
516
517"#,
518 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#structfield.field"##]],
519 );
520 }
521
522 // FIXME: ImportMap will return re-export paths instead of public module
523 // paths. The correct path to documentation will never be a re-export.
524 // This problem stops us from resolving stdlib items included in the prelude
525 // such as `Option::Some` correctly.
526 #[ignore = "ImportMap may return re-exports"]
527 #[test]
528 fn test_reexport_order() {
529 check(
530 r#"
531pub mod wrapper {
532 pub use module::Item;
533
534 pub mod module {
535 pub struct Item;
536 }
537}
538
539fn foo() {
540 let bar: wrapper::It<|>em;
541}
542 "#,
543 expect![[r#"https://docs.rs/test/*/test/wrapper/module/struct.Item.html"#]],
544 )
545 }
546}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 53265488e..6290b35bd 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,7 +14,7 @@ use test_utils::mark;
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::{remove_links, rewrite_links}, 17 doc_links::{remove_links, rewrite_links},
18 markdown_remove::remove_markdown, 18 markdown_remove::remove_markdown,
19 markup::Markup, 19 markup::Markup,
20 runnables::runnable, 20 runnables::runnable,
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 7d716577e..e2079bbcf 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -220,8 +220,8 @@ fn hint_iterator(
220 } 220 }
221 let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?; 221 let iter_trait = FamousDefs(sema, krate).core_iter_Iterator()?;
222 let iter_mod = FamousDefs(sema, krate).core_iter()?; 222 let iter_mod = FamousDefs(sema, krate).core_iter()?;
223 // assert this type comes from `core::iter` 223 // assert this struct comes from `core::iter`
224 iter_mod.visibility_of(db, &iter_trait.into()).filter(|&vis| vis == hir::Visibility::Public)?; 224 iter_mod.visibility_of(db, &strukt.into()).filter(|&vis| vis == hir::Visibility::Public)?;
225 if ty.impls_trait(db, iter_trait, &[]) { 225 if ty.impls_trait(db, iter_trait, &[]) {
226 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item { 226 let assoc_type_item = iter_trait.items(db).into_iter().find_map(|item| match item {
227 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias), 227 hir::AssocItem::TypeAlias(alias) if alias.name(db) == known::Item => Some(alias),
@@ -231,12 +231,17 @@ fn hint_iterator(
231 const LABEL_START: &str = "impl Iterator<Item = "; 231 const LABEL_START: &str = "impl Iterator<Item = ";
232 const LABEL_END: &str = ">"; 232 const LABEL_END: &str = ">";
233 233
234 let ty_display = ty.display_truncated( 234 let ty_display = hint_iterator(sema, config, &ty)
235 db, 235 .map(|assoc_type_impl| assoc_type_impl.to_string())
236 config 236 .unwrap_or_else(|| {
237 .max_length 237 ty.display_truncated(
238 .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())), 238 db,
239 ); 239 config
240 .max_length
241 .map(|len| len.saturating_sub(LABEL_START.len() + LABEL_END.len())),
242 )
243 .to_string()
244 });
240 return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END).into()); 245 return Some(format!("{}{}{}", LABEL_START, ty_display, LABEL_END).into());
241 } 246 }
242 } 247 }
@@ -1002,18 +1007,6 @@ fn main() {
1002 1007
1003 println!("Unit expr"); 1008 println!("Unit expr");
1004} 1009}
1005
1006//- /alloc.rs crate:alloc deps:core
1007mod collections {
1008 struct Vec<T> {}
1009 impl<T> Vec<T> {
1010 fn new() -> Self { Vec {} }
1011 fn push(&mut self, t: T) { }
1012 }
1013 impl<T> IntoIterator for Vec<T> {
1014 type Item=T;
1015 }
1016}
1017"#, 1010"#,
1018 ); 1011 );
1019 } 1012 }
@@ -1043,17 +1036,6 @@ fn main() {
1043 //^ &str 1036 //^ &str
1044 } 1037 }
1045} 1038}
1046//- /alloc.rs crate:alloc deps:core
1047mod collections {
1048 struct Vec<T> {}
1049 impl<T> Vec<T> {
1050 fn new() -> Self { Vec {} }
1051 fn push(&mut self, t: T) { }
1052 }
1053 impl<T> IntoIterator for Vec<T> {
1054 type Item=T;
1055 }
1056}
1057"#, 1039"#,
1058 ); 1040 );
1059 } 1041 }
@@ -1183,4 +1165,41 @@ fn main() {
1183 "#]], 1165 "#]],
1184 ); 1166 );
1185 } 1167 }
1168
1169 #[test]
1170 fn shorten_iterators_in_associated_params() {
1171 check_with_config(
1172 InlayHintsConfig {
1173 parameter_hints: false,
1174 type_hints: true,
1175 chaining_hints: false,
1176 max_length: None,
1177 },
1178 r#"
1179use core::iter;
1180
1181pub struct SomeIter<T> {}
1182
1183impl<T> SomeIter<T> {
1184 pub fn new() -> Self { SomeIter {} }
1185 pub fn push(&mut self, t: T) {}
1186}
1187
1188impl<T> Iterator for SomeIter<T> {
1189 type Item = T;
1190 fn next(&mut self) -> Option<Self::Item> {
1191 None
1192 }
1193}
1194
1195fn main() {
1196 let mut some_iter = SomeIter::new();
1197 //^^^^^^^^^^^^^ SomeIter<Take<Repeat<i32>>>
1198 some_iter.push(iter::repeat(2).take(2));
1199 let iter_of_iters = some_iter.take(2);
1200 //^^^^^^^^^^^^^ impl Iterator<Item = impl Iterator<Item = i32>>
1201}
1202"#,
1203 );
1204 }
1186} 1205}
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 57f3581b6..aaf9b3b4b 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -45,8 +45,8 @@ mod status;
45mod syntax_highlighting; 45mod syntax_highlighting;
46mod syntax_tree; 46mod syntax_tree;
47mod typing; 47mod typing;
48mod link_rewrite;
49mod markdown_remove; 48mod markdown_remove;
49mod doc_links;
50 50
51use std::sync::Arc; 51use std::sync::Arc;
52 52
@@ -77,7 +77,10 @@ pub use crate::{
77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, 77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
79 markup::Markup, 79 markup::Markup,
80 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 80 prime_caches::PrimeCachesProgress,
81 references::{
82 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, RenameError,
83 },
81 runnables::{Runnable, RunnableKind, TestId}, 84 runnables::{Runnable, RunnableKind, TestId},
82 syntax_highlighting::{ 85 syntax_highlighting::{
83 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 86 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
@@ -221,8 +224,11 @@ impl Analysis {
221 self.with_db(|db| status::status(&*db, file_id)) 224 self.with_db(|db| status::status(&*db, file_id))
222 } 225 }
223 226
224 pub fn prime_caches(&self, files: Vec<FileId>) -> Cancelable<()> { 227 pub fn prime_caches<F>(&self, cb: F) -> Cancelable<()>
225 self.with_db(|db| prime_caches::prime_caches(db, files)) 228 where
229 F: Fn(PrimeCachesProgress) + Sync + std::panic::UnwindSafe,
230 {
231 self.with_db(move |db| prime_caches::prime_caches(db, &cb))
226 } 232 }
227 233
228 /// Gets the text of the source file. 234 /// Gets the text of the source file.
@@ -382,6 +388,14 @@ impl Analysis {
382 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown)) 388 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown))
383 } 389 }
384 390
391 /// Return URL(s) for the documentation of the symbol under the cursor.
392 pub fn external_docs(
393 &self,
394 position: FilePosition,
395 ) -> Cancelable<Option<doc_links::DocumentationLink>> {
396 self.with_db(|db| doc_links::external_docs(db, &position))
397 }
398
385 /// Computes parameter information for the given call expression. 399 /// Computes parameter information for the given call expression.
386 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { 400 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
387 self.with_db(|db| call_info::call_info(db, position)) 401 self.with_db(|db| call_info::call_info(db, position))
@@ -490,7 +504,7 @@ impl Analysis {
490 &self, 504 &self,
491 position: FilePosition, 505 position: FilePosition,
492 new_name: &str, 506 new_name: &str,
493 ) -> Cancelable<Option<RangeInfo<SourceChange>>> { 507 ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> {
494 self.with_db(|db| references::rename(db, position, new_name)) 508 self.with_db(|db| references::rename(db, position, new_name))
495 } 509 }
496 510
diff --git a/crates/ide/src/prime_caches.rs b/crates/ide/src/prime_caches.rs
index c5ab5a1d8..9687c2734 100644
--- a/crates/ide/src/prime_caches.rs
+++ b/crates/ide/src/prime_caches.rs
@@ -3,10 +3,45 @@
3//! request takes longer to compute. This modules implemented prepopulating of 3//! request takes longer to compute. This modules implemented prepopulating of
4//! various caches, it's not really advanced at the moment. 4//! various caches, it's not really advanced at the moment.
5 5
6use crate::{FileId, RootDatabase}; 6use base_db::SourceDatabase;
7use hir::db::DefDatabase;
7 8
8pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { 9use crate::RootDatabase;
9 for file in files { 10
10 let _ = crate::syntax_highlighting::highlight(db, file, None, false); 11#[derive(Debug)]
12pub enum PrimeCachesProgress {
13 Started,
14 /// We started indexing a crate.
15 StartedOnCrate {
16 on_crate: String,
17 n_done: usize,
18 n_total: usize,
19 },
20 /// We finished indexing all crates.
21 Finished,
22}
23
24pub(crate) fn prime_caches(db: &RootDatabase, cb: &(dyn Fn(PrimeCachesProgress) + Sync)) {
25 let _p = profile::span("prime_caches");
26 let graph = db.crate_graph();
27 let topo = &graph.crates_in_topological_order();
28
29 cb(PrimeCachesProgress::Started);
30
31 // FIXME: This would be easy to parallelize, since it's in the ideal ordering for that.
32 // Unfortunately rayon prevents panics from propagation out of a `scope`, which breaks
33 // cancellation, so we cannot use rayon.
34 for (i, krate) in topo.iter().enumerate() {
35 let crate_name =
36 graph[*krate].declaration_name.as_ref().map(ToString::to_string).unwrap_or_default();
37
38 cb(PrimeCachesProgress::StartedOnCrate {
39 on_crate: crate_name,
40 n_done: i,
41 n_total: topo.len(),
42 });
43 db.crate_def_map(*krate);
11 } 44 }
45
46 cb(PrimeCachesProgress::Finished);
12} 47}
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index e0830eb4f..88e2f2db3 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -9,7 +9,7 @@
9//! at the index that the match starts at and its tree parent is 9//! at the index that the match starts at and its tree parent is
10//! resolved to the search element definition, we get a reference. 10//! resolved to the search element definition, we get a reference.
11 11
12mod rename; 12pub(crate) mod rename;
13 13
14use hir::Semantics; 14use hir::Semantics;
15use ide_db::{ 15use ide_db::{
@@ -26,6 +26,7 @@ use syntax::{
26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
27 27
28pub(crate) use self::rename::rename; 28pub(crate) use self::rename::rename;
29pub use self::rename::RenameError;
29 30
30pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind}; 31pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
31 32
@@ -406,6 +407,23 @@ enum Foo {
406 } 407 }
407 408
408 #[test] 409 #[test]
410 fn test_find_all_refs_enum_var_field() {
411 check(
412 r#"
413enum Foo {
414 A,
415 B { field<|>: u8 },
416 C,
417}
418"#,
419 expect![[r#"
420 field RECORD_FIELD FileId(0) 26..35 26..31 Other
421
422 "#]],
423 );
424 }
425
426 #[test]
409 fn test_find_all_refs_two_modules() { 427 fn test_find_all_refs_two_modules() {
410 check( 428 check(
411 r#" 429 r#"
@@ -669,6 +687,76 @@ fn g() { f(); }
669 ); 687 );
670 } 688 }
671 689
690 #[test]
691 fn test_find_all_refs_struct_pat() {
692 check(
693 r#"
694struct S {
695 field<|>: u8,
696}
697
698fn f(s: S) {
699 match s {
700 S { field } => {}
701 }
702}
703"#,
704 expect![[r#"
705 field RECORD_FIELD FileId(0) 15..24 15..20 Other
706
707 FileId(0) 68..73 FieldShorthandForField Read
708 "#]],
709 );
710 }
711
712 #[test]
713 fn test_find_all_refs_enum_var_pat() {
714 check(
715 r#"
716enum En {
717 Variant {
718 field<|>: u8,
719 }
720}
721
722fn f(e: En) {
723 match e {
724 En::Variant { field } => {}
725 }
726}
727"#,
728 expect![[r#"
729 field RECORD_FIELD FileId(0) 32..41 32..37 Other
730
731 FileId(0) 102..107 FieldShorthandForField Read
732 "#]],
733 );
734 }
735
736 #[test]
737 fn test_find_all_refs_enum_var_privacy() {
738 check(
739 r#"
740mod m {
741 pub enum En {
742 Variant {
743 field<|>: u8,
744 }
745 }
746}
747
748fn f() -> m::En {
749 m::En::Variant { field: 0 }
750}
751"#,
752 expect![[r#"
753 field RECORD_FIELD FileId(0) 56..65 56..61 Other
754
755 FileId(0) 125..130 Other Read
756 "#]],
757 );
758 }
759
672 fn check(ra_fixture: &str, expect: Expect) { 760 fn check(ra_fixture: &str, expect: Expect) {
673 check_with_scope(ra_fixture, None, expect) 761 check_with_scope(ra_fixture, None, expect)
674 } 762 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 8cbe1ae5a..f9a11e43d 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -6,11 +6,16 @@ use ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase, 7 RootDatabase,
8}; 8};
9use std::convert::TryInto; 9
10use std::{
11 convert::TryInto,
12 error::Error,
13 fmt::{self, Display},
14};
10use syntax::{ 15use syntax::{
11 algo::find_node_at_offset, 16 algo::find_node_at_offset,
12 ast::{self, NameOwner}, 17 ast::{self, NameOwner},
13 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 18 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
14}; 19};
15use test_utils::mark; 20use test_utils::mark;
16use text_edit::TextEdit; 21use text_edit::TextEdit;
@@ -20,17 +25,44 @@ use crate::{
20 SourceChange, SourceFileEdit, TextRange, TextSize, 25 SourceChange, SourceFileEdit, TextRange, TextSize,
21}; 26};
22 27
28#[derive(Debug)]
29pub struct RenameError(pub(crate) String);
30
31impl fmt::Display for RenameError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 Display::fmt(&self.0, f)
34 }
35}
36
37impl Error for RenameError {}
38
23pub(crate) fn rename( 39pub(crate) fn rename(
24 db: &RootDatabase, 40 db: &RootDatabase,
25 position: FilePosition, 41 position: FilePosition,
26 new_name: &str, 42 new_name: &str,
27) -> Option<RangeInfo<SourceChange>> { 43) -> Result<RangeInfo<SourceChange>, RenameError> {
28 let sema = Semantics::new(db); 44 let sema = Semantics::new(db);
45 rename_with_semantics(&sema, position, new_name)
46}
29 47
30 match lex_single_valid_syntax_kind(new_name)? { 48pub(crate) fn rename_with_semantics(
31 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 49 sema: &Semantics<RootDatabase>,
32 SyntaxKind::SELF_KW => return rename_to_self(&sema, position), 50 position: FilePosition,
33 _ => return None, 51 new_name: &str,
52) -> Result<RangeInfo<SourceChange>, RenameError> {
53 match lex_single_syntax_kind(new_name) {
54 Some(res) => match res {
55 (SyntaxKind::IDENT, _) => (),
56 (SyntaxKind::UNDERSCORE, _) => (),
57 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
58 (_, Some(syntax_error)) => {
59 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
60 }
61 (_, None) => {
62 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
63 }
64 },
65 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
34 } 66 }
35 67
36 let source_file = sema.parse(position.file_id); 68 let source_file = sema.parse(position.file_id);
@@ -103,7 +135,7 @@ fn rename_mod(
103 position: FilePosition, 135 position: FilePosition,
104 module: Module, 136 module: Module,
105 new_name: &str, 137 new_name: &str,
106) -> Option<RangeInfo<SourceChange>> { 138) -> Result<RangeInfo<SourceChange>, RenameError> {
107 let mut source_file_edits = Vec::new(); 139 let mut source_file_edits = Vec::new();
108 let mut file_system_edits = Vec::new(); 140 let mut file_system_edits = Vec::new();
109 141
@@ -125,7 +157,7 @@ fn rename_mod(
125 157
126 if let Some(src) = module.declaration_source(sema.db) { 158 if let Some(src) = module.declaration_source(sema.db) {
127 let file_id = src.file_id.original_file(sema.db); 159 let file_id = src.file_id.original_file(sema.db);
128 let name = src.value.name()?; 160 let name = src.value.name().unwrap();
129 let edit = SourceFileEdit { 161 let edit = SourceFileEdit {
130 file_id, 162 file_id,
131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), 163 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
@@ -133,35 +165,40 @@ fn rename_mod(
133 source_file_edits.push(edit); 165 source_file_edits.push(edit);
134 } 166 }
135 167
136 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 168 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
169 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
137 let ref_edits = refs 170 let ref_edits = refs
138 .references 171 .references
139 .into_iter() 172 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name)); 173 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits); 174 source_file_edits.extend(ref_edits);
142 175
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 176 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
144} 177}
145 178
146fn rename_to_self( 179fn rename_to_self(
147 sema: &Semantics<RootDatabase>, 180 sema: &Semantics<RootDatabase>,
148 position: FilePosition, 181 position: FilePosition,
149) -> Option<RangeInfo<SourceChange>> { 182) -> Result<RangeInfo<SourceChange>, RenameError> {
150 let source_file = sema.parse(position.file_id); 183 let source_file = sema.parse(position.file_id);
151 let syn = source_file.syntax(); 184 let syn = source_file.syntax();
152 185
153 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 186 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
154 let params = fn_def.param_list()?; 187 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
188 let params =
189 fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
155 if params.self_param().is_some() { 190 if params.self_param().is_some() {
156 return None; // method already has self param 191 return Err(RenameError("Method already has a self parameter".to_string()));
157 } 192 }
158 let first_param = params.params().next()?; 193 let first_param =
194 params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
159 let mutable = match first_param.ty() { 195 let mutable = match first_param.ty() {
160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), 196 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types 197 _ => return Err(RenameError("Not renaming other types".to_string())),
162 }; 198 };
163 199
164 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 200 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
201 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
165 202
166 let param_range = first_param.syntax().text_range(); 203 let param_range = first_param.syntax().text_range();
167 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 204 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
@@ -169,7 +206,7 @@ fn rename_to_self(
169 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 206 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
170 207
171 if param_ref.is_empty() { 208 if param_ref.is_empty() {
172 return None; 209 return Err(RenameError("Parameter to rename not found".to_string()));
173 } 210 }
174 211
175 let mut edits = usages 212 let mut edits = usages
@@ -185,7 +222,7 @@ fn rename_to_self(
185 ), 222 ),
186 }); 223 });
187 224
188 Some(RangeInfo::new(range, SourceChange::from(edits))) 225 Ok(RangeInfo::new(range, SourceChange::from(edits)))
189} 226}
190 227
191fn text_edit_from_self_param( 228fn text_edit_from_self_param(
@@ -216,12 +253,13 @@ fn rename_self_to_param(
216 position: FilePosition, 253 position: FilePosition,
217 self_token: SyntaxToken, 254 self_token: SyntaxToken,
218 new_name: &str, 255 new_name: &str,
219) -> Option<RangeInfo<SourceChange>> { 256) -> Result<RangeInfo<SourceChange>, RenameError> {
220 let source_file = sema.parse(position.file_id); 257 let source_file = sema.parse(position.file_id);
221 let syn = source_file.syntax(); 258 let syn = source_file.syntax();
222 259
223 let text = sema.db.file_text(position.file_id); 260 let text = sema.db.file_text(position.file_id);
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 261 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
262 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
225 let search_range = fn_def.syntax().text_range(); 263 let search_range = fn_def.syntax().text_range();
226 264
227 let mut edits: Vec<SourceFileEdit> = vec![]; 265 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -235,7 +273,8 @@ fn rename_self_to_param(
235 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 273 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
236 { 274 {
237 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 275 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
238 text_edit_from_self_param(syn, self_param, new_name)? 276 text_edit_from_self_param(syn, self_param, new_name)
277 .ok_or_else(|| RenameError("No target type found".to_string()))?
239 } else { 278 } else {
240 TextEdit::replace(usage.text_range(), String::from(new_name)) 279 TextEdit::replace(usage.text_range(), String::from(new_name))
241 }; 280 };
@@ -246,15 +285,18 @@ fn rename_self_to_param(
246 let range = ast::SelfParam::cast(self_token.parent()) 285 let range = ast::SelfParam::cast(self_token.parent())
247 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 286 .map_or(self_token.text_range(), |p| p.syntax().text_range());
248 287
249 Some(RangeInfo::new(range, SourceChange::from(edits))) 288 Ok(RangeInfo::new(range, SourceChange::from(edits)))
250} 289}
251 290
252fn rename_reference( 291fn rename_reference(
253 sema: &Semantics<RootDatabase>, 292 sema: &Semantics<RootDatabase>,
254 position: FilePosition, 293 position: FilePosition,
255 new_name: &str, 294 new_name: &str,
256) -> Option<RangeInfo<SourceChange>> { 295) -> Result<RangeInfo<SourceChange>, RenameError> {
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 296 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
297 Some(range_info) => range_info,
298 None => return Err(RenameError("No references found at position".to_string())),
299 };
258 300
259 let edit = refs 301 let edit = refs
260 .into_iter() 302 .into_iter()
@@ -262,10 +304,10 @@ fn rename_reference(
262 .collect::<Vec<_>>(); 304 .collect::<Vec<_>>();
263 305
264 if edit.is_empty() { 306 if edit.is_empty() {
265 return None; 307 return Err(RenameError("No references found at position".to_string()));
266 } 308 }
267 309
268 Some(RangeInfo::new(range, SourceChange::from(edit))) 310 Ok(RangeInfo::new(range, SourceChange::from(edit)))
269} 311}
270 312
271#[cfg(test)] 313#[cfg(test)]
@@ -280,25 +322,45 @@ mod tests {
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 322 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after); 323 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = fixture::position(ra_fixture_before); 324 let (analysis, position) = fixture::position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap(); 325 let rename_result = analysis
284 let mut text_edit_builder = TextEdit::builder(); 326 .rename(position, new_name)
285 let mut file_id: Option<FileId> = None; 327 .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
286 if let Some(change) = source_change { 328 match rename_result {
287 for edit in change.info.source_file_edits { 329 Ok(source_change) => {
288 file_id = Some(edit.file_id); 330 let mut text_edit_builder = TextEdit::builder();
289 for indel in edit.edit.into_iter() { 331 let mut file_id: Option<FileId> = None;
290 text_edit_builder.replace(indel.delete, indel.insert); 332 for edit in source_change.info.source_file_edits {
333 file_id = Some(edit.file_id);
334 for indel in edit.edit.into_iter() {
335 text_edit_builder.replace(indel.delete, indel.insert);
336 }
291 } 337 }
338 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
339 text_edit_builder.finish().apply(&mut result);
340 assert_eq_text!(ra_fixture_after, &*result);
292 } 341 }
293 } 342 Err(err) => {
294 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 343 if ra_fixture_after.starts_with("error:") {
295 text_edit_builder.finish().apply(&mut result); 344 let error_message = ra_fixture_after
296 assert_eq_text!(ra_fixture_after, &*result); 345 .chars()
346 .into_iter()
347 .skip("error:".len())
348 .collect::<String>();
349 assert_eq!(error_message.trim(), err.to_string());
350 return;
351 } else {
352 panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
353 }
354 }
355 };
297 } 356 }
298 357
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 358 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = fixture::position(ra_fixture); 359 let (analysis, position) = fixture::position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap(); 360 let source_change = analysis
361 .rename(position, new_name)
362 .unwrap()
363 .expect("Expect returned RangeInfo to be Some, but was None");
302 expect.assert_debug_eq(&source_change) 364 expect.assert_debug_eq(&source_change)
303 } 365 }
304 366
@@ -313,11 +375,30 @@ mod tests {
313 } 375 }
314 376
315 #[test] 377 #[test]
316 fn test_rename_to_invalid_identifier() { 378 fn test_rename_to_invalid_identifier1() {
317 let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#); 379 check(
318 let new_name = "invalid!"; 380 "invalid!",
319 let source_change = analysis.rename(position, new_name).unwrap(); 381 r#"fn main() { let i<|> = 1; }"#,
320 assert!(source_change.is_none()); 382 "error: Invalid name `invalid!`: not an identifier",
383 );
384 }
385
386 #[test]
387 fn test_rename_to_invalid_identifier2() {
388 check(
389 "multiple tokens",
390 r#"fn main() { let i<|> = 1; }"#,
391 "error: Invalid name `multiple tokens`: not an identifier",
392 );
393 }
394
395 #[test]
396 fn test_rename_to_invalid_identifier3() {
397 check(
398 "let",
399 r#"fn main() { let i<|> = 1; }"#,
400 "error: Invalid name `let`: not an identifier",