aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-06-13 18:32:54 +0100
committerAleksey Kladov <[email protected]>2021-06-13 18:32:54 +0100
commitde1fc70ccd3bf7a0850e036a12cf866a80d46458 (patch)
treed93cc6be7678b8be01a5162e80bb3f8df84e8c50
parent24262f9ff6ae9ea326fa35d238704d18e99d52a1 (diff)
internal: refactor find_map diagnostic
-rw-r--r--crates/hir/src/diagnostics.rs19
-rw-r--r--crates/hir/src/lib.rs11
-rw-r--r--crates/ide/src/diagnostics.rs88
-rw-r--r--crates/ide/src/diagnostics/fixes.rs1
-rw-r--r--crates/ide/src/diagnostics/fixes/replace_with_find_map.rs84
-rw-r--r--crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs182
6 files changed, 192 insertions, 193 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 9afee0b90..c294a803b 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -41,6 +41,7 @@ diagnostics![
41 MissingUnsafe, 41 MissingUnsafe,
42 NoSuchField, 42 NoSuchField,
43 RemoveThisSemicolon, 43 RemoveThisSemicolon,
44 ReplaceFilterMapNextWithFindMap,
44 UnimplementedBuiltinMacro, 45 UnimplementedBuiltinMacro,
45 UnresolvedExternCrate, 46 UnresolvedExternCrate,
46 UnresolvedImport, 47 UnresolvedImport,
@@ -121,9 +122,6 @@ pub struct MissingFields {
121 pub missed_fields: Vec<Name>, 122 pub missed_fields: Vec<Name>,
122} 123}
123 124
124// Diagnostic: replace-filter-map-next-with-find-map
125//
126// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
127#[derive(Debug)] 125#[derive(Debug)]
128pub struct ReplaceFilterMapNextWithFindMap { 126pub struct ReplaceFilterMapNextWithFindMap {
129 pub file: HirFileId, 127 pub file: HirFileId,
@@ -131,21 +129,6 @@ pub struct ReplaceFilterMapNextWithFindMap {
131 pub next_expr: AstPtr<ast::Expr>, 129 pub next_expr: AstPtr<ast::Expr>,
132} 130}
133 131
134impl Diagnostic for ReplaceFilterMapNextWithFindMap {
135 fn code(&self) -> DiagnosticCode {
136 DiagnosticCode("replace-filter-map-next-with-find-map")
137 }
138 fn message(&self) -> String {
139 "replace filter_map(..).next() with find_map(..)".to_string()
140 }
141 fn display_source(&self) -> InFile<SyntaxNodePtr> {
142 InFile { file_id: self.file, value: self.next_expr.clone().into() }
143 }
144 fn as_any(&self) -> &(dyn Any + Send + 'static) {
145 self
146 }
147}
148
149#[derive(Debug)] 132#[derive(Debug)]
150pub struct MismatchedArgCount { 133pub struct MismatchedArgCount {
151 pub call_expr: InFile<AstPtr<ast::Expr>>, 134 pub call_expr: InFile<AstPtr<ast::Expr>>,
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index aaab5336a..b2731b62f 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1168,10 +1168,13 @@ impl Function {
1168 } 1168 }
1169 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => { 1169 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
1170 if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) { 1170 if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) {
1171 sink.push(ReplaceFilterMapNextWithFindMap { 1171 acc.push(
1172 file: next_source_ptr.file_id, 1172 ReplaceFilterMapNextWithFindMap {
1173 next_expr: next_source_ptr.value, 1173 file: next_source_ptr.file_id,
1174 }); 1174 next_expr: next_source_ptr.value,
1175 }
1176 .into(),
1177 );
1175 } 1178 }
1176 } 1179 }
1177 BodyValidationDiagnostic::MismatchedArgCount { call_expr, expected, found } => { 1180 BodyValidationDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index e3974e1ae..ec5318594 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -13,6 +13,7 @@ mod missing_ok_or_some_in_tail_expr;
13mod missing_unsafe; 13mod missing_unsafe;
14mod no_such_field; 14mod no_such_field;
15mod remove_this_semicolon; 15mod remove_this_semicolon;
16mod replace_filter_map_next_with_find_map;
16mod unimplemented_builtin_macro; 17mod unimplemented_builtin_macro;
17mod unresolved_extern_crate; 18mod unresolved_extern_crate;
18mod unresolved_import; 19mod unresolved_import;
@@ -167,9 +168,6 @@ pub(crate) fn diagnostics(
167 .on::<hir::diagnostics::IncorrectCase, _>(|d| { 168 .on::<hir::diagnostics::IncorrectCase, _>(|d| {
168 res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); 169 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
169 }) 170 })
170 .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| {
171 res.borrow_mut().push(warning_with_fix(d, &sema, resolve));
172 })
173 .on::<UnlinkedFile, _>(|d| { 171 .on::<UnlinkedFile, _>(|d| {
174 // Limit diagnostic to the first few characters in the file. This matches how VS Code 172 // Limit diagnostic to the first few characters in the file. This matches how VS Code
175 // renders it with the full span, but on other editors, and is less invasive. 173 // renders it with the full span, but on other editors, and is less invasive.
@@ -225,6 +223,7 @@ pub(crate) fn diagnostics(
225 AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), 223 AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d),
226 AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d), 224 AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d),
227 AnyDiagnostic::RemoveThisSemicolon(d) => remove_this_semicolon::remove_this_semicolon(&ctx, &d), 225 AnyDiagnostic::RemoveThisSemicolon(d) => remove_this_semicolon::remove_this_semicolon(&ctx, &d),
226 AnyDiagnostic::ReplaceFilterMapNextWithFindMap(d) => replace_filter_map_next_with_find_map::replace_filter_map_next_with_find_map(&ctx, &d),
228 AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), 227 AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
229 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), 228 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
230 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), 229 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
@@ -672,89 +671,6 @@ mod foo;
672 ); 671 );
673 } 672 }
674 673
675 // Register the required standard library types to make the tests work
676 fn add_filter_map_with_find_next_boilerplate(body: &str) -> String {
677 let prefix = r#"
678 //- /main.rs crate:main deps:core
679 use core::iter::Iterator;
680 use core::option::Option::{self, Some, None};
681 "#;
682 let suffix = r#"
683 //- /core/lib.rs crate:core
684 pub mod option {
685 pub enum Option<T> { Some(T), None }
686 }
687 pub mod iter {
688 pub trait Iterator {
689 type Item;
690 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
691 fn next(&mut self) -> Option<Self::Item>;
692 }
693 pub struct FilterMap {}
694 impl Iterator for FilterMap {
695 type Item = i32;
696 fn next(&mut self) -> i32 { 7 }
697 }
698 }
699 "#;
700 format!("{}{}{}", prefix, body, suffix)
701 }
702
703 #[test]
704 fn replace_filter_map_next_with_find_map2() {
705 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
706 r#"
707 fn foo() {
708 let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
709 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..)
710 }
711 "#,
712 ));
713 }
714
715 #[test]
716 fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
717 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
718 r#"
719 fn foo() {
720 let m = [1, 2, 3]
721 .iter()
722 .filter_map(|x| if *x == 2 { Some (4) } else { None })
723 .len();
724 }
725 "#,
726 ));
727 }
728
729 #[test]
730 fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
731 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
732 r#"
733 fn foo() {
734 let m = [1, 2, 3]
735 .iter()
736 .filter_map(|x| if *x == 2 { Some (4) } else { None })
737 .map(|x| x + 2)
738 .len();
739 }
740 "#,
741 ));
742 }
743
744 #[test]
745 fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
746 check_diagnostics(&add_filter_map_with_find_next_boilerplate(
747 r#"
748 fn foo() {
749 let m = [1, 2, 3]
750 .iter()
751 .filter_map(|x| if *x == 2 { Some (4) } else { None });
752 let n = m.next();
753 }
754 "#,
755 ));
756 }
757
758 #[test] 674 #[test]
759 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { 675 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
760 check_diagnostics( 676 check_diagnostics(
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 350575b3a..e4bd90c3f 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -1,7 +1,6 @@
1//! Provides a way to attach fixes to the diagnostics. 1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented. 2//! The same module also has all curret custom fixes for the diagnostics implemented.
3mod change_case; 3mod change_case;
4mod replace_with_find_map;
5 4
6use hir::{diagnostics::Diagnostic, Semantics}; 5use hir::{diagnostics::Diagnostic, Semantics};
7use ide_assists::AssistResolveStrategy; 6use ide_assists::AssistResolveStrategy;
diff --git a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs b/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
deleted file mode 100644
index 444bf563b..000000000
--- a/crates/ide/src/diagnostics/fixes/replace_with_find_map.rs
+++ /dev/null
@@ -1,84 +0,0 @@
1use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{
5 ast::{self, ArgListOwner},
6 AstNode, TextRange,
7};
8use text_edit::TextEdit;
9
10use crate::diagnostics::{fix, DiagnosticWithFixes};
11
12impl DiagnosticWithFixes for ReplaceFilterMapNextWithFindMap {
13 fn fixes(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Vec<Assist>> {
18 let root = sema.db.parse_or_expand(self.file)?;
19 let next_expr = self.next_expr.to_node(&root);
20 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
21
22 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
23 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
24 let filter_map_args = filter_map_call.arg_list()?;
25
26 let range_to_replace =
27 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
28 let replacement = format!("find_map{}", filter_map_args.syntax().text());
29 let trigger_range = next_expr.syntax().text_range();
30
31 let edit = TextEdit::replace(range_to_replace, replacement);
32
33 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
34
35 Some(vec![fix(
36 "replace_with_find_map",
37 "Replace filter_map(..).next() with find_map()",
38 source_change,
39 trigger_range,
40 )])
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use crate::diagnostics::tests::check_fix;
47
48 #[test]
49 fn replace_with_wind_map() {
50 check_fix(
51 r#"
52//- /main.rs crate:main deps:core
53use core::iter::Iterator;
54use core::option::Option::{self, Some, None};
55fn foo() {
56 let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
57}
58//- /core/lib.rs crate:core
59pub mod option {
60 pub enum Option<T> { Some(T), None }
61}
62pub mod iter {
63 pub trait Iterator {
64 type Item;
65 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
66 fn next(&mut self) -> Option<Self::Item>;
67 }
68 pub struct FilterMap {}
69 impl Iterator for FilterMap {
70 type Item = i32;
71 fn next(&mut self) -> i32 { 7 }
72 }
73}
74"#,
75 r#"
76use core::iter::Iterator;
77use core::option::Option::{self, Some, None};
78fn foo() {
79 let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });
80}
81"#,
82 )
83 }
84}
diff --git a/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs b/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs
new file mode 100644
index 000000000..f3b011495
--- /dev/null
+++ b/crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs
@@ -0,0 +1,182 @@
1use hir::{db::AstDatabase, InFile};
2use ide_db::source_change::SourceChange;
3use syntax::{
4 ast::{self, ArgListOwner},
5 AstNode, TextRange,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, Diagnostic, DiagnosticsContext},
11 Assist, Severity,
12};
13
14// Diagnostic: replace-filter-map-next-with-find-map
15//
16// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
17pub(super) fn replace_filter_map_next_with_find_map(
18 ctx: &DiagnosticsContext<'_>,
19 d: &hir::ReplaceFilterMapNextWithFindMap,
20) -> Diagnostic {
21 Diagnostic::new(
22 "replace-filter-map-next-with-find-map",
23 "replace filter_map(..).next() with find_map(..)",
24 ctx.sema.diagnostics_display_range(InFile::new(d.file, d.next_expr.clone().into())).range,
25 )
26 .severity(Severity::WeakWarning)
27 .with_fixes(fixes(ctx, d))
28}
29
30fn fixes(
31 ctx: &DiagnosticsContext<'_>,
32 d: &hir::ReplaceFilterMapNextWithFindMap,
33) -> Option<Vec<Assist>> {
34 let root = ctx.sema.db.parse_or_expand(d.file)?;
35 let next_expr = d.next_expr.to_node(&root);
36 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
37
38 let filter_map_call = ast::MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
39 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
40 let filter_map_args = filter_map_call.arg_list()?;
41
42 let range_to_replace =
43 TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
44 let replacement = format!("find_map{}", filter_map_args.syntax().text());
45 let trigger_range = next_expr.syntax().text_range();
46
47 let edit = TextEdit::replace(range_to_replace, replacement);
48
49 let source_change = SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit);
50
51 Some(vec![fix(
52 "replace_with_find_map",
53 "Replace filter_map(..).next() with find_map()",
54 source_change,
55 trigger_range,
56 )])
57}
58
59#[cfg(test)]
60mod tests {
61 use crate::diagnostics::tests::check_fix;
62
63 // Register the required standard library types to make the tests work
64 #[track_caller]
65 fn check_diagnostics(ra_fixture: &str) {
66 let prefix = r#"
67//- /main.rs crate:main deps:core
68use core::iter::Iterator;
69use core::option::Option::{self, Some, None};
70"#;
71 let suffix = r#"
72//- /core/lib.rs crate:core
73pub mod option {
74 pub enum Option<T> { Some(T), None }
75}
76pub mod iter {
77 pub trait Iterator {
78 type Item;
79 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
80 fn next(&mut self) -> Option<Self::Item>;
81 }
82 pub struct FilterMap {}
83 impl Iterator for FilterMap {
84 type Item = i32;
85 fn next(&mut self) -> i32 { 7 }
86 }
87}
88"#;
89 crate::diagnostics::tests::check_diagnostics(&format!("{}{}{}", prefix, ra_fixture, suffix))
90 }
91
92 #[test]
93 fn replace_filter_map_next_with_find_map2() {
94 check_diagnostics(
95 r#"
96 fn foo() {
97 let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
98 } //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..)
99"#,
100 );
101 }
102
103 #[test]
104 fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() {
105 check_diagnostics(
106 r#"
107fn foo() {
108 let m = [1, 2, 3]
109 .iter()
110 .filter_map(|x| if *x == 2 { Some (4) } else { None })
111 .len();
112}
113"#,
114 );
115 }
116
117 #[test]
118 fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() {
119 check_diagnostics(
120 r#"
121fn foo() {
122 let m = [1, 2, 3]
123 .iter()
124 .filter_map(|x| if *x == 2 { Some (4) } else { None })
125 .map(|x| x + 2)
126 .len();
127}
128"#,
129 );
130 }
131
132 #[test]
133 fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() {
134 check_diagnostics(
135 r#"
136fn foo() {
137 let m = [1, 2, 3]
138 .iter()
139 .filter_map(|x| if *x == 2 { Some (4) } else { None });
140 let n = m.next();
141}
142"#,
143 );
144 }
145
146 #[test]
147 fn replace_with_wind_map() {
148 check_fix(
149 r#"
150//- /main.rs crate:main deps:core
151use core::iter::Iterator;
152use core::option::Option::{self, Some, None};
153fn foo() {
154 let m = [1, 2, 3].iter().$0filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
155}
156//- /core/lib.rs crate:core
157pub mod option {
158 pub enum Option<T> { Some(T), None }
159}
160pub mod iter {
161 pub trait Iterator {
162 type Item;
163 fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap }
164 fn next(&mut self) -> Option<Self::Item>;
165 }
166 pub struct FilterMap {}
167 impl Iterator for FilterMap {
168 type Item = i32;
169 fn next(&mut self) -> i32 { 7 }
170 }
171}
172"#,
173 r#"
174use core::iter::Iterator;
175use core::option::Option::{self, Some, None};
176fn foo() {
177 let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None });
178}
179"#,
180 )
181 }
182}