diff options
Diffstat (limited to 'crates/hir_ty/src/diagnostics.rs')
-rw-r--r-- | crates/hir_ty/src/diagnostics.rs | 177 |
1 files changed, 170 insertions, 7 deletions
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs index c67a289f2..6bca7aa0d 100644 --- a/crates/hir_ty/src/diagnostics.rs +++ b/crates/hir_ty/src/diagnostics.rs | |||
@@ -247,7 +247,7 @@ impl Diagnostic for RemoveThisSemicolon { | |||
247 | 247 | ||
248 | // Diagnostic: break-outside-of-loop | 248 | // Diagnostic: break-outside-of-loop |
249 | // | 249 | // |
250 | // This diagnostic is triggered if `break` keyword is used outside of a loop. | 250 | // This diagnostic is triggered if the `break` keyword is used outside of a loop. |
251 | #[derive(Debug)] | 251 | #[derive(Debug)] |
252 | pub struct BreakOutsideOfLoop { | 252 | pub struct BreakOutsideOfLoop { |
253 | pub file: HirFileId, | 253 | pub file: HirFileId, |
@@ -271,7 +271,7 @@ impl Diagnostic for BreakOutsideOfLoop { | |||
271 | 271 | ||
272 | // Diagnostic: missing-unsafe | 272 | // Diagnostic: missing-unsafe |
273 | // | 273 | // |
274 | // This diagnostic is triggered if operation marked as `unsafe` is used outside of `unsafe` function or block. | 274 | // This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block. |
275 | #[derive(Debug)] | 275 | #[derive(Debug)] |
276 | pub struct MissingUnsafe { | 276 | pub struct MissingUnsafe { |
277 | pub file: HirFileId, | 277 | pub file: HirFileId, |
@@ -295,7 +295,7 @@ impl Diagnostic for MissingUnsafe { | |||
295 | 295 | ||
296 | // Diagnostic: mismatched-arg-count | 296 | // Diagnostic: mismatched-arg-count |
297 | // | 297 | // |
298 | // This diagnostic is triggered if function is invoked with an incorrect amount of arguments. | 298 | // This diagnostic is triggered if a function is invoked with an incorrect amount of arguments. |
299 | #[derive(Debug)] | 299 | #[derive(Debug)] |
300 | pub struct MismatchedArgCount { | 300 | pub struct MismatchedArgCount { |
301 | pub file: HirFileId, | 301 | pub file: HirFileId, |
@@ -345,15 +345,46 @@ impl fmt::Display for CaseType { | |||
345 | } | 345 | } |
346 | } | 346 | } |
347 | 347 | ||
348 | #[derive(Debug)] | ||
349 | pub enum IdentType { | ||
350 | Argument, | ||
351 | Constant, | ||
352 | Enum, | ||
353 | Field, | ||
354 | Function, | ||
355 | StaticVariable, | ||
356 | Structure, | ||
357 | Variable, | ||
358 | Variant, | ||
359 | } | ||
360 | |||
361 | impl fmt::Display for IdentType { | ||
362 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
363 | let repr = match self { | ||
364 | IdentType::Argument => "Argument", | ||
365 | IdentType::Constant => "Constant", | ||
366 | IdentType::Enum => "Enum", | ||
367 | IdentType::Field => "Field", | ||
368 | IdentType::Function => "Function", | ||
369 | IdentType::StaticVariable => "Static variable", | ||
370 | IdentType::Structure => "Structure", | ||
371 | IdentType::Variable => "Variable", | ||
372 | IdentType::Variant => "Variant", | ||
373 | }; | ||
374 | |||
375 | write!(f, "{}", repr) | ||
376 | } | ||
377 | } | ||
378 | |||
348 | // Diagnostic: incorrect-ident-case | 379 | // Diagnostic: incorrect-ident-case |
349 | // | 380 | // |
350 | // This diagnostic is triggered if item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention]. | 381 | // This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention]. |
351 | #[derive(Debug)] | 382 | #[derive(Debug)] |
352 | pub struct IncorrectCase { | 383 | pub struct IncorrectCase { |
353 | pub file: HirFileId, | 384 | pub file: HirFileId, |
354 | pub ident: AstPtr<ast::Name>, | 385 | pub ident: AstPtr<ast::Name>, |
355 | pub expected_case: CaseType, | 386 | pub expected_case: CaseType, |
356 | pub ident_type: String, | 387 | pub ident_type: IdentType, |
357 | pub ident_text: String, | 388 | pub ident_text: String, |
358 | pub suggested_text: String, | 389 | pub suggested_text: String, |
359 | } | 390 | } |
@@ -386,6 +417,31 @@ impl Diagnostic for IncorrectCase { | |||
386 | } | 417 | } |
387 | } | 418 | } |
388 | 419 | ||
420 | // Diagnostic: replace-filter-map-next-with-find-map | ||
421 | // | ||
422 | // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. | ||
423 | #[derive(Debug)] | ||
424 | pub struct ReplaceFilterMapNextWithFindMap { | ||
425 | pub file: HirFileId, | ||
426 | /// This expression is the whole method chain up to and including `.filter_map(..).next()`. | ||
427 | pub next_expr: AstPtr<ast::Expr>, | ||
428 | } | ||
429 | |||
430 | impl Diagnostic for ReplaceFilterMapNextWithFindMap { | ||
431 | fn code(&self) -> DiagnosticCode { | ||
432 | DiagnosticCode("replace-filter-map-next-with-find-map") | ||
433 | } | ||
434 | fn message(&self) -> String { | ||
435 | "replace filter_map(..).next() with find_map(..)".to_string() | ||
436 | } | ||
437 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
438 | InFile { file_id: self.file, value: self.next_expr.clone().into() } | ||
439 | } | ||
440 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
441 | self | ||
442 | } | ||
443 | } | ||
444 | |||
389 | #[cfg(test)] | 445 | #[cfg(test)] |
390 | mod tests { | 446 | mod tests { |
391 | use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; | 447 | use base_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; |
@@ -409,7 +465,7 @@ mod tests { | |||
409 | let crate_def_map = self.crate_def_map(krate); | 465 | let crate_def_map = self.crate_def_map(krate); |
410 | 466 | ||
411 | let mut fns = Vec::new(); | 467 | let mut fns = Vec::new(); |
412 | for (module_id, _) in crate_def_map.modules.iter() { | 468 | for (module_id, _) in crate_def_map.modules() { |
413 | for decl in crate_def_map[module_id].scope.declarations() { | 469 | for decl in crate_def_map[module_id].scope.declarations() { |
414 | let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); | 470 | let mut sink = DiagnosticSinkBuilder::new().build(&mut cb); |
415 | validate_module_item(self, krate, decl, &mut sink); | 471 | validate_module_item(self, krate, decl, &mut sink); |
@@ -455,7 +511,7 @@ mod tests { | |||
455 | // FIXME: macros... | 511 | // FIXME: macros... |
456 | let file_id = src.file_id.original_file(&db); | 512 | let file_id = src.file_id.original_file(&db); |
457 | let range = src.value.to_node(&root).text_range(); | 513 | let range = src.value.to_node(&root).text_range(); |
458 | let message = d.message().to_owned(); | 514 | let message = d.message(); |
459 | actual.entry(file_id).or_default().push((range, message)); | 515 | actual.entry(file_id).or_default().push((range, message)); |
460 | }); | 516 | }); |
461 | 517 | ||
@@ -626,6 +682,30 @@ fn baz(s: S) -> i32 { | |||
626 | } | 682 | } |
627 | 683 | ||
628 | #[test] | 684 | #[test] |
685 | fn missing_record_pat_field_box() { | ||
686 | check_diagnostics( | ||
687 | r" | ||
688 | struct S { s: Box<u32> } | ||
689 | fn x(a: S) { | ||
690 | let S { box s } = a; | ||
691 | } | ||
692 | ", | ||
693 | ) | ||
694 | } | ||
695 | |||
696 | #[test] | ||
697 | fn missing_record_pat_field_ref() { | ||
698 | check_diagnostics( | ||
699 | r" | ||
700 | struct S { s: u32 } | ||
701 | fn x(a: S) { | ||
702 | let S { ref s } = a; | ||
703 | } | ||
704 | ", | ||
705 | ) | ||
706 | } | ||
707 | |||
708 | #[test] | ||
629 | fn break_outside_of_loop() { | 709 | fn break_outside_of_loop() { |
630 | check_diagnostics( | 710 | check_diagnostics( |
631 | r#" | 711 | r#" |
@@ -644,4 +724,87 @@ fn foo() { break; } | |||
644 | "#, | 724 | "#, |
645 | ); | 725 | ); |
646 | } | 726 | } |
727 | |||
728 | // Register the required standard library types to make the tests work | ||
729 | fn add_filter_map_with_find_next_boilerplate(body: &str) -> String { | ||
730 | let prefix = r#" | ||
731 | //- /main.rs crate:main deps:core | ||
732 | use core::iter::Iterator; | ||
733 | use core::option::Option::{self, Some, None}; | ||
734 | "#; | ||
735 | let suffix = r#" | ||
736 | //- /core/lib.rs crate:core | ||
737 | pub mod option { | ||
738 | pub enum Option<T> { Some(T), None } | ||
739 | } | ||
740 | pub mod iter { | ||
741 | pub trait Iterator { | ||
742 | type Item; | ||
743 | fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap } | ||
744 | fn next(&mut self) -> Option<Self::Item>; | ||
745 | } | ||
746 | pub struct FilterMap {} | ||
747 | impl Iterator for FilterMap { | ||
748 | type Item = i32; | ||
749 | fn next(&mut self) -> i32 { 7 } | ||
750 | } | ||
751 | } | ||
752 | "#; | ||
753 | format!("{}{}{}", prefix, body, suffix) | ||
754 | } | ||
755 | |||
756 | #[test] | ||
757 | fn replace_filter_map_next_with_find_map2() { | ||
758 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
759 | r#" | ||
760 | fn foo() { | ||
761 | let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next(); | ||
762 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..) | ||
763 | } | ||
764 | "#, | ||
765 | )); | ||
766 | } | ||
767 | |||
768 | #[test] | ||
769 | fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() { | ||
770 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
771 | r#" | ||
772 | fn foo() { | ||
773 | let m = [1, 2, 3] | ||
774 | .iter() | ||
775 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
776 | .len(); | ||
777 | } | ||
778 | "#, | ||
779 | )); | ||
780 | } | ||
781 | |||
782 | #[test] | ||
783 | fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() { | ||
784 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
785 | r#" | ||
786 | fn foo() { | ||
787 | let m = [1, 2, 3] | ||
788 | .iter() | ||
789 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
790 | .map(|x| x + 2) | ||
791 | .len(); | ||
792 | } | ||
793 | "#, | ||
794 | )); | ||
795 | } | ||
796 | |||
797 | #[test] | ||
798 | fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() { | ||
799 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
800 | r#" | ||
801 | fn foo() { | ||
802 | let m = [1, 2, 3] | ||
803 | .iter() | ||
804 | .filter_map(|x| if *x == 2 { Some (4) } else { None }); | ||
805 | let n = m.next(); | ||
806 | } | ||
807 | "#, | ||
808 | )); | ||
809 | } | ||
647 | } | 810 | } |