aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-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
39 files changed, 2318 insertions, 214 deletions
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",
401 );
321 } 402 }
322 403
323 #[test] 404 #[test]
@@ -350,6 +431,15 @@ fn main() {
350 } 431 }
351 432
352 #[test] 433 #[test]
434 fn test_rename_unresolved_reference() {
435 check(
436 "new_name",
437 r#"fn main() { let _ = unresolved_ref<|>; }"#,
438 "error: No references found at position",
439 );
440 }
441
442 #[test]
353 fn test_rename_for_macro_args() { 443 fn test_rename_for_macro_args() {
354 check( 444 check(
355 "b", 445 "b",
diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs
index d9fc25d88..6aafd6fd5 100644
--- a/crates/ide/src/syntax_highlighting.rs
+++ b/crates/ide/src/syntax_highlighting.rs
@@ -68,7 +68,7 @@ pub(crate) fn highlight(
68 // When we leave a node, the we use it to flatten the highlighted ranges. 68 // When we leave a node, the we use it to flatten the highlighted ranges.
69 let mut stack = HighlightedRangeStack::new(); 69 let mut stack = HighlightedRangeStack::new();
70 70
71 let mut current_macro_call: Option<ast::MacroCall> = None; 71 let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None;
72 let mut format_string: Option<SyntaxElement> = None; 72 let mut format_string: Option<SyntaxElement> = None;
73 73
74 // Walk all nodes, keeping track of whether we are inside a macro or not. 74 // Walk all nodes, keeping track of whether we are inside a macro or not.
@@ -92,7 +92,6 @@ pub(crate) fn highlight(
92 // Track "inside macro" state 92 // Track "inside macro" state
93 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) { 93 match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
94 WalkEvent::Enter(Some(mc)) => { 94 WalkEvent::Enter(Some(mc)) => {
95 current_macro_call = Some(mc.clone());
96 if let Some(range) = macro_call_range(&mc) { 95 if let Some(range) = macro_call_range(&mc) {
97 stack.add(HighlightedRange { 96 stack.add(HighlightedRange {
98 range, 97 range,
@@ -100,7 +99,9 @@ pub(crate) fn highlight(
100 binding_hash: None, 99 binding_hash: None,
101 }); 100 });
102 } 101 }
102 let mut is_macro_rules = None;
103 if let Some(name) = mc.is_macro_rules() { 103 if let Some(name) = mc.is_macro_rules() {
104 is_macro_rules = Some(MacroMatcherParseState::new());
104 if let Some((highlight, binding_hash)) = highlight_element( 105 if let Some((highlight, binding_hash)) = highlight_element(
105 &sema, 106 &sema,
106 &mut bindings_shadow_count, 107 &mut bindings_shadow_count,
@@ -114,10 +115,11 @@ pub(crate) fn highlight(
114 }); 115 });
115 } 116 }
116 } 117 }
118 current_macro_call = Some((mc.clone(), is_macro_rules));
117 continue; 119 continue;
118 } 120 }
119 WalkEvent::Leave(Some(mc)) => { 121 WalkEvent::Leave(Some(mc)) => {
120 assert!(current_macro_call == Some(mc)); 122 assert!(current_macro_call.map(|it| it.0) == Some(mc));
121 current_macro_call = None; 123 current_macro_call = None;
122 format_string = None; 124 format_string = None;
123 } 125 }
@@ -146,6 +148,20 @@ pub(crate) fn highlight(
146 WalkEvent::Leave(_) => continue, 148 WalkEvent::Leave(_) => continue,
147 }; 149 };
148 150
151 // check if in matcher part of a macro_rules rule
152 if let Some((_, Some(ref mut state))) = current_macro_call {
153 if let Some(tok) = element.as_token() {
154 if matches!(
155 update_macro_rules_state(tok, state),
156 RuleState::Matcher | RuleState::Expander
157 ) {
158 if skip_metavariables(element.clone()) {
159 continue;
160 }
161 }
162 }
163 }
164
149 let range = element.text_range(); 165 let range = element.text_range();
150 166
151 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { 167 let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
@@ -918,3 +934,99 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
918 _ => default.into(), 934 _ => default.into(),
919 } 935 }
920} 936}
937
938struct MacroMatcherParseState {
939 /// Opening and corresponding closing bracket of the matcher or expander of the current rule
940 paren_ty: Option<(SyntaxKind, SyntaxKind)>,
941 paren_level: usize,
942 rule_state: RuleState,
943 /// Whether we are inside the outer `{` `}` macro block that holds the rules
944 in_invoc_body: bool,
945}
946
947impl MacroMatcherParseState {
948 fn new() -> Self {
949 MacroMatcherParseState {
950 paren_ty: None,
951 paren_level: 0,
952 in_invoc_body: false,
953 rule_state: RuleState::None,
954 }
955 }
956}
957
958#[derive(Copy, Clone, PartialEq)]
959enum RuleState {
960 Matcher,
961 Expander,
962 Between,
963 None,
964}
965
966impl RuleState {
967 fn transition(&mut self) {
968 *self = match self {
969 RuleState::Matcher => RuleState::Between,
970 RuleState::Expander => RuleState::None,
971 RuleState::Between => RuleState::Expander,
972 RuleState::None => RuleState::Matcher,
973 };
974 }
975}
976
977fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState {
978 if !state.in_invoc_body {
979 if tok.kind() == T!['{'] {
980 state.in_invoc_body = true;
981 }
982 return state.rule_state;
983 }
984
985 match state.paren_ty {
986 Some((open, close)) => {
987 if tok.kind() == open {
988 state.paren_level += 1;
989 } else if tok.kind() == close {
990 state.paren_level -= 1;
991 if state.paren_level == 0 {
992 let res = state.rule_state;
993 state.rule_state.transition();
994 state.paren_ty = None;
995 return res;
996 }
997 }
998 }
999 None => {
1000 match tok.kind() {
1001 T!['('] => {
1002 state.paren_ty = Some((T!['('], T![')']));
1003 }
1004 T!['{'] => {
1005 state.paren_ty = Some((T!['{'], T!['}']));
1006 }
1007 T!['['] => {
1008 state.paren_ty = Some((T!['['], T![']']));
1009 }
1010 _ => (),
1011 }
1012 if state.paren_ty.is_some() {
1013 state.paren_level = 1;
1014 state.rule_state.transition();
1015 }
1016 }
1017 }
1018 state.rule_state
1019}
1020
1021fn skip_metavariables(element: SyntaxElement) -> bool {
1022 let tok = match element.as_token() {
1023 Some(tok) => tok,
1024 None => return false,
1025 };
1026 let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]);
1027 match tok.kind() {
1028 IDENT if is_fragment() => true,
1029 kind if kind.is_keyword() && is_fragment() => true,
1030 _ => false,
1031 }
1032}
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
index 1b681b2c6..43f1b32fd 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlight_strings.html
@@ -37,7 +37,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
37</style> 37</style>
38<pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> <span class="punctuation">{</span> 38<pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> <span class="punctuation">{</span>
39 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">(</span><span class="punctuation">{</span> 39 <span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">(</span><span class="punctuation">{</span>
40 <span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>io<span class="punctuation">:</span><span class="punctuation">:</span>_print<span class="punctuation">(</span><span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>format_args_nl<span class="punctuation">!</span><span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span> 40 <span class="punctuation">$</span>crate<span class="punctuation">:</span><span class="punctuation">:</span>io<span class="punctuation">:</span><span class="punctuation">:</span>_print<span class="punctuation">(</span><span class="punctuation">$</span>crate<span class="punctuation">:</span><span class="punctuation">:</span>format_args_nl<span class="punctuation">!</span><span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span>
41 <span class="punctuation">}</span><span class="punctuation">)</span> 41 <span class="punctuation">}</span><span class="punctuation">)</span>
42<span class="punctuation">}</span> 42<span class="punctuation">}</span>
43#[rustc_builtin_macro] 43#[rustc_builtin_macro]
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 1d8a3c404..0bb0928e4 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -62,7 +62,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62 62
63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> 64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
65 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword consuming">self</span><span class="punctuation">)</span> 65 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword mutable consuming">self</span><span class="punctuation">)</span>
66 <span class="punctuation">}</span> 66 <span class="punctuation">}</span>
67 67
68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> 68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
@@ -115,6 +115,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
115 <span class="punctuation">}</span> 115 <span class="punctuation">}</span>
116<span class="punctuation">}</span> 116<span class="punctuation">}</span>
117 117
118<span class="macro">macro_rules!</span> <span class="macro declaration">keyword_frag</span> <span class="punctuation">{</span>
119 <span class="punctuation">(</span><span class="punctuation">$</span>type<span class="punctuation">:</span>ty<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">&gt;</span> <span class="punctuation">(</span><span class="punctuation">$</span>type<span class="punctuation">)</span>
120<span class="punctuation">}</span>
121
118<span class="comment">// comment</span> 122<span class="comment">// comment</span>
119<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span> 123<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
120 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello, {}!"</span><span class="punctuation">,</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span> 124 <span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello, {}!"</span><span class="punctuation">,</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs
index 694c4b7fa..126363b8b 100644
--- a/crates/ide/src/syntax_highlighting/tests.rs
+++ b/crates/ide/src/syntax_highlighting/tests.rs
@@ -89,6 +89,10 @@ macro_rules! noop {
89 } 89 }
90} 90}
91 91
92macro_rules! keyword_frag {
93 ($type:ty) => ($type)
94}
95
92// comment 96// comment
93fn main() { 97fn main() {
94 println!("Hello, {}!", 92); 98 println!("Hello, {}!", 92);
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index edab1d644..8e3dcd99c 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -12,8 +12,9 @@ use once_cell::unsync::Lazy;
12use rustc_hash::FxHashMap; 12use rustc_hash::FxHashMap;
13use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; 13use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
14 14
15use crate::defs::NameClass;
15use crate::{ 16use crate::{
16 defs::{classify_name_ref, Definition, NameRefClass}, 17 defs::{classify_name, classify_name_ref, Definition, NameRefClass},
17 RootDatabase, 18 RootDatabase,
18}; 19};
19 20
@@ -226,9 +227,9 @@ impl<'a> FindUsages<'a> {
226 227
227 let search_scope = { 228 let search_scope = {
228 let base = self.def.search_scope(sema.db); 229 let base = self.def.search_scope(sema.db);
229 match self.scope { 230 match &self.scope {
230 None => base, 231 None => base,
231 Some(scope) => base.intersection(&scope), 232 Some(scope) => base.intersection(scope),
232 } 233 }
233 }; 234 };
234 235
@@ -251,54 +252,83 @@ impl<'a> FindUsages<'a> {
251 continue; 252 continue;
252 } 253 }
253 254
254 let name_ref: ast::NameRef = 255 match sema.find_node_at_offset_with_descend(&tree, offset) {
255 match sema.find_node_at_offset_with_descend(&tree, offset) { 256 Some(name_ref) => {
256 Some(it) => it, 257 if self.found_name_ref(&name_ref, sink) {
257 None => continue,
258 };
259
260 match classify_name_ref(&sema, &name_ref) {
261 Some(NameRefClass::Definition(def)) if &def == self.def => {
262 let kind = if is_record_lit_name_ref(&name_ref)
263 || is_call_expr_name_ref(&name_ref)
264 {
265 ReferenceKind::StructLiteral
266 } else {
267 ReferenceKind::Other
268 };
269
270 let reference = Reference {
271 file_range: sema.original_range(name_ref.syntax()),
272 kind,
273 access: reference_access(&def, &name_ref),
274 };
275 if sink(reference) {
276 return; 258 return;
277 } 259 }
278 } 260 }
279 Some(NameRefClass::FieldShorthand { local, field }) => { 261 None => match sema.find_node_at_offset_with_descend(&tree, offset) {
280 let reference = match self.def { 262 Some(name) => {
281 Definition::Field(_) if &field == self.def => Reference { 263 if self.found_name(&name, sink) {
282 file_range: self.sema.original_range(name_ref.syntax()), 264 return;
283 kind: ReferenceKind::FieldShorthandForField, 265 }
284 access: reference_access(&field, &name_ref),
285 },
286 Definition::Local(l) if &local == l => Reference {
287 file_range: self.sema.original_range(name_ref.syntax()),
288 kind: ReferenceKind::FieldShorthandForLocal,
289 access: reference_access(&Definition::Local(local), &name_ref),
290 },
291 _ => continue, // not a usage
292 };
293 if sink(reference) {
294 return;
295 } 266 }
296 } 267 None => {}
297 _ => {} // not a usage 268 },
298 } 269 }
299 } 270 }
300 } 271 }
301 } 272 }
273
274 fn found_name_ref(
275 &self,
276 name_ref: &ast::NameRef,
277 sink: &mut dyn FnMut(Reference) -> bool,
278 ) -> bool {
279 match classify_name_ref(self.sema, &name_ref) {
280 Some(NameRefClass::Definition(def)) if &def == self.def => {
281 let kind = if is_record_lit_name_ref(&name_ref) || is_call_expr_name_ref(&name_ref)
282 {
283 ReferenceKind::StructLiteral
284 } else {
285 ReferenceKind::Other
286 };
287
288 let reference = Reference {
289 file_range: self.sema.original_range(name_ref.syntax()),
290 kind,
291 access: reference_access(&def, &name_ref),
292 };
293 sink(reference)
294 }
295 Some(NameRefClass::FieldShorthand { local, field }) => {
296 let reference = match self.def {
297 Definition::Field(_) if &field == self.def => Reference {
298 file_range: self.sema.original_range(name_ref.syntax()),
299 kind: ReferenceKind::FieldShorthandForField,
300 access: reference_access(&field, &name_ref),
301 },
302 Definition::Local(l) if &local == l => Reference {
303 file_range: self.sema.original_range(name_ref.syntax()),
304 kind: ReferenceKind::FieldShorthandForLocal,
305 access: reference_access(&Definition::Local(local), &name_ref),
306 },
307 _ => return false, // not a usage
308 };
309 sink(reference)
310 }
311 _ => false, // not a usage
312 }
313 }
314
315 fn found_name(&self, name: &ast::Name, sink: &mut dyn FnMut(Reference) -> bool) -> bool {
316 match classify_name(self.sema, name) {
317 Some(NameClass::FieldShorthand { local: _, field }) => {
318 let reference = match self.def {
319 Definition::Field(_) if &field == self.def => Reference {
320 file_range: self.sema.original_range(name.syntax()),
321 kind: ReferenceKind::FieldShorthandForField,
322 // FIXME: mutable patterns should have `Write` access
323 access: Some(ReferenceAccess::Read),
324 },
325 _ => return false, // not a usage
326 };
327 sink(reference)
328 }
329 _ => false, // not a usage
330 }
331 }
302} 332}
303 333
304fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> { 334fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> {
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs
index 871808d89..e529e07b0 100644
--- a/crates/project_model/src/sysroot.rs
+++ b/crates/project_model/src/sysroot.rs
@@ -90,9 +90,15 @@ impl Sysroot {
90 } 90 }
91 91
92 if sysroot.by_name("core").is_none() { 92 if sysroot.by_name("core").is_none() {
93 let var_note = if env::var_os("RUST_SRC_PATH").is_some() {
94 " (`RUST_SRC_PATH` might be incorrect, try unsetting it)"
95 } else {
96 ""
97 };
93 anyhow::bail!( 98 anyhow::bail!(
94 "could not find libcore in sysroot path `{}`", 99 "could not find libcore in sysroot path `{}`{}",
95 sysroot_src_dir.as_ref().display() 100 sysroot_src_dir.as_ref().display(),
101 var_note,
96 ); 102 );
97 } 103 }
98 104
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 468655f9c..215be850f 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -646,14 +646,9 @@ pub(crate) fn handle_prepare_rename(
646 let _p = profile::span("handle_prepare_rename"); 646 let _p = profile::span("handle_prepare_rename");
647 let position = from_proto::file_position(&snap, params)?; 647 let position = from_proto::file_position(&snap, params)?;
648 648
649 let optional_change = snap.analysis.rename(position, "dummy")?; 649 let change = snap.analysis.rename(position, "dummy")??;
650 let range = match optional_change {
651 None => return Ok(None),
652 Some(it) => it.range,
653 };
654
655 let line_index = snap.analysis.file_line_index(position.file_id)?; 650 let line_index = snap.analysis.file_line_index(position.file_id)?;
656 let range = to_proto::range(&line_index, range); 651 let range = to_proto::range(&line_index, change.range);
657 Ok(Some(PrepareRenameResponse::Range(range))) 652 Ok(Some(PrepareRenameResponse::Range(range)))
658} 653}
659 654
@@ -672,12 +667,8 @@ pub(crate) fn handle_rename(
672 .into()); 667 .into());
673 } 668 }
674 669
675 let optional_change = snap.analysis.rename(position, &*params.new_name)?; 670 let change = snap.analysis.rename(position, &*params.new_name)??;
676 let source_change = match optional_change { 671 let workspace_edit = to_proto::workspace_edit(&snap, change.info)?;
677 None => return Ok(None),
678 Some(it) => it.info,
679 };
680 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
681 Ok(Some(workspace_edit)) 672 Ok(Some(workspace_edit))
682} 673}
683 674
@@ -1310,6 +1301,18 @@ pub(crate) fn handle_semantic_tokens_range(
1310 Ok(Some(semantic_tokens.into())) 1301 Ok(Some(semantic_tokens.into()))
1311} 1302}
1312 1303
1304pub(crate) fn handle_open_docs(
1305 snap: GlobalStateSnapshot,
1306 params: lsp_types::TextDocumentPositionParams,
1307) -> Result<Option<lsp_types::Url>> {
1308 let _p = profile::span("handle_open_docs");
1309 let position = from_proto::file_position(&snap, params)?;
1310
1311 let remote = snap.analysis.external_docs(position)?;
1312
1313 Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
1314}
1315
1313fn implementation_title(count: usize) -> String { 1316fn implementation_title(count: usize) -> String {
1314 if count == 1 { 1317 if count == 1 {
1315 "1 implementation".into() 1318 "1 implementation".into()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index fee0bb69c..f31f8d900 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -347,3 +347,11 @@ pub struct CommandLink {
347 #[serde(skip_serializing_if = "Option::is_none")] 347 #[serde(skip_serializing_if = "Option::is_none")]
348 pub tooltip: Option<String>, 348 pub tooltip: Option<String>,
349} 349}
350
351pub enum ExternalDocs {}
352
353impl Request for ExternalDocs {
354 type Params = lsp_types::TextDocumentPositionParams;
355 type Result = Option<lsp_types::Url>;
356 const METHOD: &'static str = "experimental/externalDocs";
357}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4b7ac8224..fb18f9014 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -7,6 +7,7 @@ use std::{
7 7
8use base_db::VfsPath; 8use base_db::VfsPath;
9use crossbeam_channel::{select, Receiver}; 9use crossbeam_channel::{select, Receiver};
10use ide::PrimeCachesProgress;
10use ide::{Canceled, FileId}; 11use ide::{Canceled, FileId};
11use lsp_server::{Connection, Notification, Request, Response}; 12use lsp_server::{Connection, Notification, Request, Response};
12use lsp_types::notification::Notification as _; 13use lsp_types::notification::Notification as _;
@@ -61,7 +62,7 @@ pub(crate) enum Task {
61 Response(Response), 62 Response(Response),
62 Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>), 63 Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>),
63 Workspaces(Vec<anyhow::Result<ProjectWorkspace>>), 64 Workspaces(Vec<anyhow::Result<ProjectWorkspace>>),
64 Unit, 65 PrimeCaches(PrimeCachesProgress),
65} 66}
66 67
67impl fmt::Debug for Event { 68impl fmt::Debug for Event {
@@ -197,7 +198,28 @@ impl GlobalState {
197 } 198 }
198 } 199 }
199 Task::Workspaces(workspaces) => self.switch_workspaces(workspaces), 200 Task::Workspaces(workspaces) => self.switch_workspaces(workspaces),
200 Task::Unit => (), 201 Task::PrimeCaches(progress) => {
202 let (state, message, fraction);
203 match progress {
204 PrimeCachesProgress::Started => {
205 state = Progress::Begin;
206 message = None;
207 fraction = 0.0;
208 }
209 PrimeCachesProgress::StartedOnCrate { on_crate, n_done, n_total } => {
210 state = Progress::Report;
211 message = Some(format!("{}/{} ({})", n_done, n_total, on_crate));
212 fraction = Progress::fraction(n_done, n_total);
213 }
214 PrimeCachesProgress::Finished => {
215 state = Progress::End;
216 message = None;
217 fraction = 1.0;
218 }
219 };
220
221 self.report_progress("indexing", state, message, Some(fraction));
222 }
201 }, 223 },
202 Event::Vfs(mut task) => { 224 Event::Vfs(mut task) => {
203 let _p = profile::span("GlobalState::handle_event/vfs"); 225 let _p = profile::span("GlobalState::handle_event/vfs");
@@ -384,6 +406,7 @@ impl GlobalState {
384 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)? 406 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
385 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)? 407 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
386 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)? 408 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
409 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)?
387 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? 410 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
388 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? 411 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
389 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? 412 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
@@ -572,12 +595,18 @@ impl GlobalState {
572 Task::Diagnostics(diagnostics) 595 Task::Diagnostics(diagnostics)
573 }) 596 })
574 } 597 }
575 self.task_pool.handle.spawn({ 598 self.task_pool.handle.spawn_with_sender({
576 let subs = subscriptions;
577 let snap = self.snapshot(); 599 let snap = self.snapshot();
578 move || { 600 move |sender| {
579 snap.analysis.prime_caches(subs).unwrap_or_else(|_: Canceled| ()); 601 snap.analysis
580 Task::Unit 602 .prime_caches(|progress| {
603 sender.send(Task::PrimeCaches(progress)).unwrap();
604 })
605 .unwrap_or_else(|_: Canceled| {
606 // Pretend that we're done, so that the progress bar is removed. Otherwise
607 // the editor may complain about it already existing.
608 sender.send(Task::PrimeCaches(PrimeCachesProgress::Finished)).unwrap()
609 });
581 } 610 }
582 }); 611 });
583 } 612 }
diff --git a/crates/rust-analyzer/src/thread_pool.rs b/crates/rust-analyzer/src/thread_pool.rs
index 4fa502925..833893739 100644
--- a/crates/rust-analyzer/src/thread_pool.rs
+++ b/crates/rust-analyzer/src/thread_pool.rs
@@ -23,6 +23,17 @@ impl<T> TaskPool<T> {
23 }) 23 })
24 } 24 }
25 25
26 pub(crate) fn spawn_with_sender<F>(&mut self, task: F)
27 where
28 F: FnOnce(Sender<T>) + Send + 'static,
29 T: Send + 'static,
30 {
31 self.inner.execute({
32 let sender = self.sender.clone();
33 move || task(sender)
34 })
35 }
36
26 pub(crate) fn len(&self) -> usize { 37 pub(crate) fn len(&self) -> usize {
27 self.inner.queued_count() 38 self.inner.queued_count()
28 } 39 }
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 011935cad..59d89f47d 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -28,20 +28,32 @@ pub fn timeit(label: &'static str) -> impl Drop {
28 Guard { label, start: Instant::now() } 28 Guard { label, start: Instant::now() }
29} 29}
30 30
31pub fn to_lower_snake_case(s: &str) -> String { 31fn to_snake_case<F: Fn(&char) -> char>(s: &str, change_case: F) -> String {
32 let mut buf = String::with_capacity(s.len()); 32 let mut buf = String::with_capacity(s.len());
33 let mut prev = false; 33 let mut prev = false;
34 for c in s.chars() { 34 for c in s.chars() {
35 // `&& prev` is required to not insert `_` before the first symbol.
35 if c.is_ascii_uppercase() && prev { 36 if c.is_ascii_uppercase() && prev {
36 buf.push('_') 37 // This check is required to not translate `Weird_Case` into `weird__case`.
38 if !buf.ends_with('_') {
39 buf.push('_')
40 }
37 } 41 }
38 prev = true; 42 prev = true;
39 43
40 buf.push(c.to_ascii_lowercase()); 44 buf.push(change_case(&c));
41 } 45 }
42 buf 46 buf
43} 47}
44 48
49pub fn to_lower_snake_case(s: &str) -> String {
50 to_snake_case(s, char::to_ascii_lowercase)
51}
52
53pub fn to_upper_snake_case(s: &str) -> String {
54 to_snake_case(s, char::to_ascii_uppercase)
55}
56
45pub fn replace(buf: &mut String, from: char, to: &str) { 57pub fn replace(buf: &mut String, from: char, to: &str) {
46 if !buf.contains(from) { 58 if !buf.contains(from) {
47 return; 59 return;
diff --git a/crates/stdx/src/macros.rs b/crates/stdx/src/macros.rs
index bf298460f..f5ee3484b 100644
--- a/crates/stdx/src/macros.rs
+++ b/crates/stdx/src/macros.rs
@@ -18,7 +18,13 @@ macro_rules! format_to {
18 }; 18 };
19} 19}
20 20
21// Generates `From` impls for `Enum E { Foo(Foo), Bar(Bar) }` enums 21/// Generates `From` impls for `Enum E { Foo(Foo), Bar(Bar) }` enums
22///
23/// # Example
24///
25/// ```rust
26/// impl_from!(Struct, Union, Enum for Adt);
27/// ```
22#[macro_export] 28#[macro_export]
23macro_rules! impl_from { 29macro_rules! impl_from {
24 ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => { 30 ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => {