diff options
Diffstat (limited to 'crates')
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 |
341 | pub enum Foo { Bar { bar: () } } | 341 | pub 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 | ||
189 | impl 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 | |||
189 | impl ModuleDef { | 199 | impl 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 | ||
250 | pub use hir_def::{ | 279 | pub 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 @@ | |||
2 | pub use hir_def::diagnostics::UnresolvedModule; | 2 | pub use hir_def::diagnostics::UnresolvedModule; |
3 | pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder}; | 3 | pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder}; |
4 | pub use hir_ty::diagnostics::{ | 4 | pub 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}; | |||
14 | use crate::{ | 14 | use 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 | ||
299 | fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields) -> VariantData { | 300 | fn 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 | ||
323 | fn lower_field(item_tree: &ItemTree, field: &Field) -> FieldData { | 329 | fn 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" | |||
17 | log = "0.4.8" | 17 | log = "0.4.8" |
18 | rustc-hash = "1.1.0" | 18 | rustc-hash = "1.1.0" |
19 | scoped-tls = "1" | 19 | scoped-tls = "1" |
20 | chalk-solve = "0.32" | 20 | chalk-solve = "0.33" |
21 | chalk-ir = "0.32" | 21 | chalk-ir = "0.33" |
22 | chalk-recursive = "0.32" | 22 | chalk-recursive = "0.33" |
23 | 23 | ||
24 | stdx = { path = "../stdx", version = "0.0.0" } | 24 | stdx = { path = "../stdx", version = "0.0.0" } |
25 | hir_def = { path = "../hir_def", version = "0.0.0" } | 25 | hir_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 @@ | |||
2 | mod expr; | 2 | mod expr; |
3 | mod match_check; | 3 | mod match_check; |
4 | mod unsafe_check; | 4 | mod unsafe_check; |
5 | mod decl_check; | ||
5 | 6 | ||
6 | use std::any::Any; | 7 | use std::{any::Any, fmt}; |
7 | 8 | ||
8 | use hir_def::DefWithBodyId; | 9 | use hir_def::{DefWithBodyId, ModuleDefId}; |
9 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; | 10 | use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink}; |
10 | use hir_expand::{name::Name, HirFileId, InFile}; | 11 | use hir_expand::{name::Name, HirFileId, InFile}; |
11 | use stdx::format_to; | 12 | use stdx::format_to; |
@@ -15,6 +16,16 @@ use crate::db::HirDatabase; | |||
15 | 16 | ||
16 | pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; | 17 | pub use crate::diagnostics::expr::{record_literal_missing_fields, record_pattern_missing_fields}; |
17 | 18 | ||
19 | pub 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 | |||
18 | pub fn validate_body(db: &dyn HirDatabase, owner: DefWithBodyId, sink: &mut DiagnosticSink<'_>) { | 29 | pub 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)] | ||
246 | pub enum CaseType { | ||
247 | // `some_var` | ||
248 | LowerSnakeCase, | ||
249 | // `SOME_CONST` | ||
250 | UpperSnakeCase, | ||
251 | // `SomeStruct` | ||
252 | UpperCamelCase, | ||
253 | } | ||
254 | |||
255 | impl 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)] | ||
268 | pub 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 | |||
277 | impl 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)] |
235 | mod tests { | 306 | mod 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 | |||
13 | mod case_conv; | ||
14 | |||
15 | use hir_def::{ | ||
16 | adt::VariantData, | ||
17 | expr::{Pat, PatId}, | ||
18 | src::HasSource, | ||
19 | AdtId, ConstId, EnumId, FunctionId, Lookup, ModuleDefId, StaticId, StructId, | ||
20 | }; | ||
21 | use hir_expand::{ | ||
22 | diagnostics::DiagnosticSink, | ||
23 | name::{AsName, Name}, | ||
24 | }; | ||
25 | use syntax::{ | ||
26 | ast::{self, NameOwner}, | ||
27 | AstNode, AstPtr, | ||
28 | }; | ||
29 | |||
30 | use crate::{ | ||
31 | db::HirDatabase, | ||
32 | diagnostics::{decl_check::case_conv::*, CaseType, IncorrectCase}, | ||
33 | }; | ||
34 | |||
35 | pub(super) struct DeclValidator<'a, 'b: 'a> { | ||
36 | owner: ModuleDefId, | ||
37 | sink: &'a mut DiagnosticSink<'b>, | ||
38 | } | ||
39 | |||
40 | #[derive(Debug)] | ||
41 | struct Replacement { | ||
42 | current_name: Name, | ||
43 | suggested_text: String, | ||
44 | expected_case: CaseType, | ||
45 | } | ||
46 | |||
47 | impl<'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(), ¶m_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 | |||
653 | fn 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 | |||
661 | fn 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)] | ||
670 | mod tests { | ||
671 | use crate::diagnostics::tests::check_diagnostics; | ||
672 | |||
673 | #[test] | ||
674 | fn incorrect_function_name() { | ||
675 | check_diagnostics( | ||
676 | r#" | ||
677 | fn 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#" | ||
687 | fn foo(SomeParam: u8) {} | ||
688 | // ^^^^^^^^^ Argument `SomeParam` should have snake_case name, e.g. `some_param` | ||
689 | |||
690 | fn 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#" | ||
700 | fn 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#" | ||
714 | struct 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#" | ||
724 | struct 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#" | ||
734 | enum 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#" | ||
744 | enum 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#" | ||
754 | const some_weird_const: u8 = 10; | ||
755 | // ^^^^^^^^^^^^^^^^ Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
756 | |||
757 | fn 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#" | ||
770 | static some_weird_const: u8 = 10; | ||
771 | // ^^^^^^^^^^^^^^^^ Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST` | ||
772 | |||
773 | fn 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#" | ||
785 | struct someStruct; | ||
786 | // ^^^^^^^^^^ Structure `someStruct` should have CamelCase name, e.g. `SomeStruct` | ||
787 | |||
788 | impl 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#" | ||
805 | enum Option { Some, None } | ||
806 | |||
807 | fn 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#" | ||
821 | enum Option { Some, None } | ||
822 | |||
823 | fn 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)] | ||
5 | enum DetectedCase { | ||
6 | LowerCamelCase, | ||
7 | UpperCamelCase, | ||
8 | LowerSnakeCase, | ||
9 | UpperSnakeCase, | ||
10 | Unknown, | ||
11 | } | ||
12 | |||
13 | fn 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. | ||
51 | pub 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. | ||
105 | pub 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. | ||
129 | pub 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)] | ||
151 | mod 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 | ||
193 | static mut static_mut: Ty = Ty { a: 0 }; | 193 | static mut STATIC_MUT: Ty = Ty { a: 0 }; |
194 | 194 | ||
195 | fn main() { | 195 | fn 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 | ||
136 | fn 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 | |||
133 | fn check_unnecessary_braces_in_use_statement( | 145 | fn 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#" | ||
839 | pub struct test_struct<|> { one: i32 } | ||
840 | |||
841 | pub fn some_fn(val: test_struct) -> test_struct { | ||
842 | test_struct { one: val.one + 1 } | ||
843 | } | ||
844 | "#, | ||
845 | r#" | ||
846 | pub struct TestStruct { one: i32 } | ||
847 | |||
848 | pub fn some_fn(val: TestStruct) -> TestStruct { | ||
849 | TestStruct { one: val.one + 1 } | ||
850 | } | ||
851 | "#, | ||
852 | ); | ||
853 | |||
854 | check_fixes( | ||
855 | r#" | ||
856 | pub fn some_fn(NonSnakeCase<|>: u8) -> u8 { | ||
857 | NonSnakeCase | ||
858 | } | ||
859 | "#, | ||
860 | r#" | ||
861 | pub fn some_fn(non_snake_case: u8) -> u8 { | ||
862 | non_snake_case | ||
863 | } | ||
864 | "#, | ||
865 | ); | ||
866 | |||
867 | check_fixes( | ||
868 | r#" | ||
869 | pub fn SomeFn<|>(val: u8) -> u8 { | ||
870 | if val != 0 { SomeFn(val - 1) } else { val } | ||
871 | } | ||
872 | "#, | ||
873 | r#" | ||
874 | pub 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#" | ||
882 | fn some_fn() { | ||
883 | let whatAWeird_Formatting<|> = 10; | ||
884 | another_func(whatAWeird_Formatting); | ||
885 | } | ||
886 | "#, | ||
887 | r#" | ||
888 | fn 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#" | ||
900 | fn 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#" | ||
911 | pub struct TestStruct; | ||
912 | |||
913 | impl TestStruct { | ||
914 | pub fn SomeFn<|>() -> TestStruct { | ||
915 | TestStruct | ||
916 | } | ||
917 | } | ||
918 | "#, | ||
919 | r#" | ||
920 | pub struct TestStruct; | ||
921 | |||
922 | impl 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 @@ | |||
3 | use base_db::FileId; | 3 | use base_db::FileId; |
4 | use hir::{ | 4 | use 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 | }; |
9 | use ide_db::{ | 12 | use ide_db::{ |
@@ -17,7 +20,7 @@ use syntax::{ | |||
17 | }; | 20 | }; |
18 | use text_edit::TextEdit; | 21 | use text_edit::TextEdit; |
19 | 22 | ||
20 | use crate::diagnostics::Fix; | 23 | use 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 | ||
105 | impl 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 | |||
102 | fn missing_record_expr_field_fix( | 122 | fn 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 | ||
5 | use hir::{Adt, Crate, HasAttrs, ModuleDef}; | 3 | use std::iter::once; |
6 | use ide_db::{defs::Definition, RootDatabase}; | 4 | |
5 | use itertools::Itertools; | ||
7 | use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; | 6 | use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; |
8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; | 7 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; |
9 | use url::Url; | 8 | use url::Url; |
10 | 9 | ||
10 | use hir::{ | ||
11 | db::{DefDatabase, HirDatabase}, | ||
12 | Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs, | ||
13 | ModuleDef, | ||
14 | }; | ||
15 | use ide_db::{ | ||
16 | defs::{classify_name, classify_name_ref, Definition}, | ||
17 | RootDatabase, | ||
18 | }; | ||
19 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | ||
20 | |||
21 | use crate::{FilePosition, Semantics}; | ||
22 | |||
23 | pub 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) |
12 | pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { | 26 | pub 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 | ||
104 | fn 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 | |||
83 | fn rewrite_intra_doc_link( | 161 | fn 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. |
220 | pub(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. | ||
142 | fn map_links<'e>( | 242 | fn 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 | /// ``` | ||
242 | fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | 348 | fn 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 | /// ``` |
259 | fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<String> { | 365 | /// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next |
366 | /// ^^^^^^^^^^^^^^^^^^^ | ||
367 | /// ``` | ||
368 | fn 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 | |||
388 | enum 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 | /// ``` | ||
399 | fn 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 | |||
423 | fn 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)] | ||
436 | mod 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#" | ||
452 | pub 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#" | ||
462 | pub 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#" | ||
472 | pub struct Foo; | ||
473 | |||
474 | impl 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#" | ||
487 | pub 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#" | ||
500 | pub 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#" | ||
513 | pub 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#" | ||
531 | pub mod wrapper { | ||
532 | pub use module::Item; | ||
533 | |||
534 | pub mod module { | ||
535 | pub struct Item; | ||
536 | } | ||
537 | } | ||
538 | |||
539 | fn 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 | ||
15 | use crate::{ | 15 | use 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 | ||
1007 | mod 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 | ||
1047 | mod 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#" | ||
1179 | use core::iter; | ||
1180 | |||
1181 | pub struct SomeIter<T> {} | ||
1182 | |||
1183 | impl<T> SomeIter<T> { | ||
1184 | pub fn new() -> Self { SomeIter {} } | ||
1185 | pub fn push(&mut self, t: T) {} | ||
1186 | } | ||
1187 | |||
1188 | impl<T> Iterator for SomeIter<T> { | ||
1189 | type Item = T; | ||
1190 | fn next(&mut self) -> Option<Self::Item> { | ||
1191 | None | ||
1192 | } | ||
1193 | } | ||
1194 | |||
1195 | fn 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; | |||
45 | mod syntax_highlighting; | 45 | mod syntax_highlighting; |
46 | mod syntax_tree; | 46 | mod syntax_tree; |
47 | mod typing; | 47 | mod typing; |
48 | mod link_rewrite; | ||
49 | mod markdown_remove; | 48 | mod markdown_remove; |
49 | mod doc_links; | ||
50 | 50 | ||
51 | use std::sync::Arc; | 51 | use 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 | ||
6 | use crate::{FileId, RootDatabase}; | 6 | use base_db::SourceDatabase; |
7 | use hir::db::DefDatabase; | ||
7 | 8 | ||
8 | pub(crate) fn prime_caches(db: &RootDatabase, files: Vec<FileId>) { | 9 | use crate::RootDatabase; |
9 | for file in files { | 10 | |
10 | let _ = crate::syntax_highlighting::highlight(db, file, None, false); | 11 | #[derive(Debug)] |
12 | pub 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 | |||
24 | pub(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 | ||
12 | mod rename; | 12 | pub(crate) mod rename; |
13 | 13 | ||
14 | use hir::Semantics; | 14 | use hir::Semantics; |
15 | use ide_db::{ | 15 | use ide_db::{ |
@@ -26,6 +26,7 @@ use syntax::{ | |||
26 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; | 26 | use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; |
27 | 27 | ||
28 | pub(crate) use self::rename::rename; | 28 | pub(crate) use self::rename::rename; |
29 | pub use self::rename::RenameError; | ||
29 | 30 | ||
30 | pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind}; | 31 | pub 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#" | ||
413 | enum 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#" | ||
694 | struct S { | ||
695 | field<|>: u8, | ||
696 | } | ||
697 | |||
698 | fn 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#" | ||
716 | enum En { | ||
717 | Variant { | ||
718 | field<|>: u8, | ||
719 | } | ||
720 | } | ||
721 | |||
722 | fn 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#" | ||
740 | mod m { | ||
741 | pub enum En { | ||
742 | Variant { | ||
743 | field<|>: u8, | ||
744 | } | ||
745 | } | ||
746 | } | ||
747 | |||
748 | fn 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 | }; |
9 | use std::convert::TryInto; | 9 | |
10 | use std::{ | ||
11 | convert::TryInto, | ||
12 | error::Error, | ||
13 | fmt::{self, Display}, | ||
14 | }; | ||
10 | use syntax::{ | 15 | use 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 | }; |
15 | use test_utils::mark; | 20 | use test_utils::mark; |
16 | use text_edit::TextEdit; | 21 | use 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)] | ||
29 | pub struct RenameError(pub(crate) String); | ||
30 | |||
31 | impl fmt::Display for RenameError { | ||
32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
33 | Display::fmt(&self.0, f) | ||
34 | } | ||
35 | } | ||
36 | |||
37 | impl Error for RenameError {} | ||
38 | |||
23 | pub(crate) fn rename( | 39 | pub(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)? { | 48 | pub(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 | ||
146 | fn rename_to_self( | 179 | fn 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 | ||
191 | fn text_edit_from_self_param( | 228 | fn 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 | ||
252 | fn rename_reference( | 291 | fn 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 | |||
938 | struct MacroMatcherParseState { | ||
939 | /// Opening and corresponding closing bracket of the matcher or expander of the current rule | ||
940 | paren_ty: Option<(SyntaxKind, SyntaxKind)>, | ||
941 | paren_level: usize, | ||
942 | rule_state: RuleState, | ||
943 | /// Whether we are inside the outer `{` `}` macro block that holds the rules | ||
944 | in_invoc_body: bool, | ||
945 | } | ||
946 | |||
947 | impl MacroMatcherParseState { | ||
948 | fn new() -> Self { | ||
949 | MacroMatcherParseState { | ||
950 | paren_ty: None, | ||
951 | paren_level: 0, | ||
952 | in_invoc_body: false, | ||
953 | rule_state: RuleState::None, | ||
954 | } | ||
955 | } | ||
956 | } | ||
957 | |||
958 | #[derive(Copy, Clone, PartialEq)] | ||
959 | enum RuleState { | ||
960 | Matcher, | ||
961 | Expander, | ||
962 | Between, | ||
963 | None, | ||
964 | } | ||
965 | |||
966 | impl RuleState { | ||
967 | fn transition(&mut self) { | ||
968 | *self = match self { | ||
969 | RuleState::Matcher => RuleState::Between, | ||
970 | RuleState::Expander => RuleState::None, | ||
971 | RuleState::Between => RuleState::Expander, | ||
972 | RuleState::None => RuleState::Matcher, | ||
973 | }; | ||
974 | } | ||
975 | } | ||
976 | |||
977 | fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState { | ||
978 | if !state.in_invoc_body { | ||
979 | if tok.kind() == T!['{'] { | ||
980 | state.in_invoc_body = true; | ||
981 | } | ||
982 | return state.rule_state; | ||
983 | } | ||
984 | |||
985 | match state.paren_ty { | ||
986 | Some((open, close)) => { | ||
987 | if tok.kind() == open { | ||
988 | state.paren_level += 1; | ||
989 | } else if tok.kind() == close { | ||
990 | state.paren_level -= 1; | ||
991 | if state.paren_level == 0 { | ||
992 | let res = state.rule_state; | ||
993 | state.rule_state.transition(); | ||
994 | state.paren_ty = None; | ||
995 | return res; | ||
996 | } | ||
997 | } | ||
998 | } | ||
999 | None => { | ||
1000 | match tok.kind() { | ||
1001 | T!['('] => { | ||
1002 | state.paren_ty = Some((T!['('], T![')'])); | ||
1003 | } | ||
1004 | T!['{'] => { | ||
1005 | state.paren_ty = Some((T!['{'], T!['}'])); | ||
1006 | } | ||
1007 | T!['['] => { | ||
1008 | state.paren_ty = Some((T!['['], T![']'])); | ||
1009 | } | ||
1010 | _ => (), | ||
1011 | } | ||
1012 | if state.paren_ty.is_some() { | ||
1013 | state.paren_level = 1; | ||
1014 | state.rule_state.transition(); | ||
1015 | } | ||
1016 | } | ||
1017 | } | ||
1018 | state.rule_state | ||
1019 | } | ||
1020 | |||
1021 | fn skip_metavariables(element: SyntaxElement) -> bool { | ||
1022 | let tok = match element.as_token() { | ||
1023 | Some(tok) => tok, | ||
1024 | None => return false, | ||
1025 | }; | ||
1026 | let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]); | ||
1027 | match tok.kind() { | ||
1028 | IDENT if is_fragment() => true, | ||
1029 | kind if kind.is_keyword() && is_fragment() => true, | ||
1030 | _ => false, | ||
1031 | } | ||
1032 | } | ||
diff --git a/crates/ide/src/syntax_highlighting/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">></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">></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">-></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">-></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">></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 | ||
92 | macro_rules! keyword_frag { | ||
93 | ($type:ty) => ($type) | ||
94 | } | ||
95 | |||
92 | // comment | 96 | // comment |
93 | fn main() { | 97 | fn 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; | |||
12 | use rustc_hash::FxHashMap; | 12 | use rustc_hash::FxHashMap; |
13 | use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; | 13 | use syntax::{ast, match_ast, AstNode, TextRange, TextSize}; |
14 | 14 | ||
15 | use crate::defs::NameClass; | ||
15 | use crate::{ | 16 | use 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 | ||
304 | fn reference_access(def: &Definition, name_ref: &ast::NameRef) -> Option<ReferenceAccess> { | 334 | fn 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 | ||
1304 | pub(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 | |||
1313 | fn implementation_title(count: usize) -> String { | 1316 | fn 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 | |||
351 | pub enum ExternalDocs {} | ||
352 | |||
353 | impl 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 | ||
8 | use base_db::VfsPath; | 8 | use base_db::VfsPath; |
9 | use crossbeam_channel::{select, Receiver}; | 9 | use crossbeam_channel::{select, Receiver}; |
10 | use ide::PrimeCachesProgress; | ||
10 | use ide::{Canceled, FileId}; | 11 | use ide::{Canceled, FileId}; |
11 | use lsp_server::{Connection, Notification, Request, Response}; | 12 | use lsp_server::{Connection, Notification, Request, Response}; |
12 | use lsp_types::notification::Notification as _; | 13 | use 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 | ||
67 | impl fmt::Debug for Event { | 68 | impl 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 | ||
31 | pub fn to_lower_snake_case(s: &str) -> String { | 31 | fn 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 | ||
49 | pub fn to_lower_snake_case(s: &str) -> String { | ||
50 | to_snake_case(s, char::to_ascii_lowercase) | ||
51 | } | ||
52 | |||
53 | pub fn to_upper_snake_case(s: &str) -> String { | ||
54 | to_snake_case(s, char::to_ascii_uppercase) | ||
55 | } | ||
56 | |||
45 | pub fn replace(buf: &mut String, from: char, to: &str) { | 57 | pub 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] |
23 | macro_rules! impl_from { | 29 | macro_rules! impl_from { |
24 | ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => { | 30 | ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => { |