diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-06-13 18:35:03 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2021-06-13 18:35:03 +0100 |
commit | 2ad78924621420cb323efdeb3d875ca3f47d940f (patch) | |
tree | eb34ec6e46f8a15b015e62dc404773a938e45819 | |
parent | 60ca03e8aa00956d1511969da5f1844a02483bc9 (diff) | |
parent | b66f4bb8d1748b83a6f4c5edc3c77a46b213e1c2 (diff) |
Merge #9253
9253: internal: refactor missing or or some diagnostic r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | crates/hir/src/diagnostics.rs | 72 | ||||
-rw-r--r-- | crates/hir/src/lib.rs | 22 | ||||
-rw-r--r-- | crates/ide/src/diagnostics.rs | 191 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 4 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes/create_field.rs | 1 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes/remove_semicolon.rs | 41 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes/replace_with_find_map.rs | 84 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/missing_fields.rs | 71 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs (renamed from crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs) | 59 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/no_such_field.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/remove_this_semicolon.rs | 64 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs | 182 |
12 files changed, 378 insertions, 415 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index f839616ce..c294a803b 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs | |||
@@ -37,8 +37,11 @@ diagnostics![ | |||
37 | MacroError, | 37 | MacroError, |
38 | MismatchedArgCount, | 38 | MismatchedArgCount, |
39 | MissingFields, | 39 | MissingFields, |
40 | MissingOkOrSomeInTailExpr, | ||
40 | MissingUnsafe, | 41 | MissingUnsafe, |
41 | NoSuchField, | 42 | NoSuchField, |
43 | RemoveThisSemicolon, | ||
44 | ReplaceFilterMapNextWithFindMap, | ||
42 | UnimplementedBuiltinMacro, | 45 | UnimplementedBuiltinMacro, |
43 | UnresolvedExternCrate, | 46 | UnresolvedExternCrate, |
44 | UnresolvedImport, | 47 | UnresolvedImport, |
@@ -119,9 +122,6 @@ pub struct MissingFields { | |||
119 | pub missed_fields: Vec<Name>, | 122 | pub missed_fields: Vec<Name>, |
120 | } | 123 | } |
121 | 124 | ||
122 | // Diagnostic: replace-filter-map-next-with-find-map | ||
123 | // | ||
124 | // This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. | ||
125 | #[derive(Debug)] | 125 | #[derive(Debug)] |
126 | pub struct ReplaceFilterMapNextWithFindMap { | 126 | pub struct ReplaceFilterMapNextWithFindMap { |
127 | pub file: HirFileId, | 127 | pub file: HirFileId, |
@@ -129,21 +129,6 @@ pub struct ReplaceFilterMapNextWithFindMap { | |||
129 | pub next_expr: AstPtr<ast::Expr>, | 129 | pub next_expr: AstPtr<ast::Expr>, |
130 | } | 130 | } |
131 | 131 | ||
132 | impl Diagnostic for ReplaceFilterMapNextWithFindMap { | ||
133 | fn code(&self) -> DiagnosticCode { | ||
134 | DiagnosticCode("replace-filter-map-next-with-find-map") | ||
135 | } | ||
136 | fn message(&self) -> String { | ||
137 | "replace filter_map(..).next() with find_map(..)".to_string() | ||
138 | } | ||
139 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
140 | InFile { file_id: self.file, value: self.next_expr.clone().into() } | ||
141 | } | ||
142 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
143 | self | ||
144 | } | ||
145 | } | ||
146 | |||
147 | #[derive(Debug)] | 132 | #[derive(Debug)] |
148 | pub struct MismatchedArgCount { | 133 | pub struct MismatchedArgCount { |
149 | pub call_expr: InFile<AstPtr<ast::Expr>>, | 134 | pub call_expr: InFile<AstPtr<ast::Expr>>, |
@@ -153,63 +138,16 @@ pub struct MismatchedArgCount { | |||
153 | 138 | ||
154 | #[derive(Debug)] | 139 | #[derive(Debug)] |
155 | pub struct RemoveThisSemicolon { | 140 | pub struct RemoveThisSemicolon { |
156 | pub file: HirFileId, | 141 | pub expr: InFile<AstPtr<ast::Expr>>, |
157 | pub expr: AstPtr<ast::Expr>, | ||
158 | } | ||
159 | |||
160 | impl Diagnostic for RemoveThisSemicolon { | ||
161 | fn code(&self) -> DiagnosticCode { | ||
162 | DiagnosticCode("remove-this-semicolon") | ||
163 | } | ||
164 | |||
165 | fn message(&self) -> String { | ||
166 | "Remove this semicolon".to_string() | ||
167 | } | ||
168 | |||
169 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
170 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
171 | } | ||
172 | |||
173 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
174 | self | ||
175 | } | ||
176 | } | 142 | } |
177 | 143 | ||
178 | // Diagnostic: missing-ok-or-some-in-tail-expr | ||
179 | // | ||
180 | // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`, | ||
181 | // or if a block that should return `Option` returns a value not wrapped in `Some`. | ||
182 | // | ||
183 | // Example: | ||
184 | // | ||
185 | // ```rust | ||
186 | // fn foo() -> Result<u8, ()> { | ||
187 | // 10 | ||
188 | // } | ||
189 | // ``` | ||
190 | #[derive(Debug)] | 144 | #[derive(Debug)] |
191 | pub struct MissingOkOrSomeInTailExpr { | 145 | pub struct MissingOkOrSomeInTailExpr { |
192 | pub file: HirFileId, | 146 | pub expr: InFile<AstPtr<ast::Expr>>, |
193 | pub expr: AstPtr<ast::Expr>, | ||
194 | // `Some` or `Ok` depending on whether the return type is Result or Option | 147 | // `Some` or `Ok` depending on whether the return type is Result or Option |
195 | pub required: String, | 148 | pub required: String, |
196 | } | 149 | } |
197 | 150 | ||
198 | impl Diagnostic for MissingOkOrSomeInTailExpr { | ||
199 | fn code(&self) -> DiagnosticCode { | ||
200 | DiagnosticCode("missing-ok-or-some-in-tail-expr") | ||
201 | } | ||
202 | fn message(&self) -> String { | ||
203 | format!("wrap return expression in {}", self.required) | ||
204 | } | ||
205 | fn display_source(&self) -> InFile<SyntaxNodePtr> { | ||
206 | InFile { file_id: self.file, value: self.expr.clone().into() } | ||
207 | } | ||
208 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
209 | self | ||
210 | } | ||
211 | } | ||
212 | |||
213 | // Diagnostic: missing-match-arm | 151 | // Diagnostic: missing-match-arm |
214 | // | 152 | // |
215 | // This diagnostic is triggered if `match` block is missing one or more match arms. | 153 | // This diagnostic is triggered if `match` block is missing one or more match arms. |
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index c1af5f097..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 } => { |
@@ -1184,20 +1187,13 @@ impl Function { | |||
1184 | } | 1187 | } |
1185 | BodyValidationDiagnostic::RemoveThisSemicolon { expr } => { | 1188 | BodyValidationDiagnostic::RemoveThisSemicolon { expr } => { |
1186 | match source_map.expr_syntax(expr) { | 1189 | match source_map.expr_syntax(expr) { |
1187 | Ok(source_ptr) => sink.push(RemoveThisSemicolon { | 1190 | Ok(expr) => acc.push(RemoveThisSemicolon { expr }.into()), |
1188 | file: source_ptr.file_id, | ||
1189 | expr: source_ptr.value, | ||
1190 | }), | ||
1191 | Err(SyntheticSyntax) => (), | 1191 | Err(SyntheticSyntax) => (), |
1192 | } | 1192 | } |
1193 | } | 1193 | } |
1194 | BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr, required } => { | 1194 | BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr, required } => { |
1195 | match source_map.expr_syntax(expr) { | 1195 | match source_map.expr_syntax(expr) { |
1196 | Ok(source_ptr) => sink.push(MissingOkOrSomeInTailExpr { | 1196 | Ok(expr) => acc.push(MissingOkOrSomeInTailExpr { expr, required }.into()), |
1197 | file: source_ptr.file_id, | ||
1198 | expr: source_ptr.value, | ||
1199 | required, | ||
1200 | }), | ||
1201 | Err(SyntheticSyntax) => (), | 1197 | Err(SyntheticSyntax) => (), |
1202 | } | 1198 | } |
1203 | } | 1199 | } |
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 4c92d0cf4..814e64ae4 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -9,8 +9,11 @@ mod inactive_code; | |||
9 | mod macro_error; | 9 | mod macro_error; |
10 | mod mismatched_arg_count; | 10 | mod mismatched_arg_count; |
11 | mod missing_fields; | 11 | mod missing_fields; |
12 | mod missing_ok_or_some_in_tail_expr; | ||
12 | mod missing_unsafe; | 13 | mod missing_unsafe; |
13 | mod no_such_field; | 14 | mod no_such_field; |
15 | mod remove_this_semicolon; | ||
16 | mod replace_filter_map_next_with_find_map; | ||
14 | mod unimplemented_builtin_macro; | 17 | mod unimplemented_builtin_macro; |
15 | mod unresolved_extern_crate; | 18 | mod unresolved_extern_crate; |
16 | mod unresolved_import; | 19 | mod unresolved_import; |
@@ -162,18 +165,9 @@ pub(crate) fn diagnostics( | |||
162 | } | 165 | } |
163 | let res = RefCell::new(res); | 166 | let res = RefCell::new(res); |
164 | let sink_builder = DiagnosticSinkBuilder::new() | 167 | let sink_builder = DiagnosticSinkBuilder::new() |
165 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { | ||
166 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
167 | }) | ||
168 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { | ||
169 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); | ||
170 | }) | ||
171 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | 168 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { |
172 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); | 169 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); |
173 | }) | 170 | }) |
174 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { | ||
175 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); | ||
176 | }) | ||
177 | .on::<UnlinkedFile, _>(|d| { | 171 | .on::<UnlinkedFile, _>(|d| { |
178 | // 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 |
179 | // 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. |
@@ -223,10 +217,13 @@ pub(crate) fn diagnostics( | |||
223 | let d = match diag { | 217 | let d = match diag { |
224 | AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d), | 218 | AnyDiagnostic::BreakOutsideOfLoop(d) => break_outside_of_loop::break_outside_of_loop(&ctx, &d), |
225 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), | 219 | AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d), |
220 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), | ||
226 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), | 221 | AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d), |
222 | AnyDiagnostic::MissingOkOrSomeInTailExpr(d) => missing_ok_or_some_in_tail_expr::missing_ok_or_some_in_tail_expr(&ctx, &d), | ||
227 | AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), | 223 | AnyDiagnostic::MissingUnsafe(d) => missing_unsafe::missing_unsafe(&ctx, &d), |
228 | AnyDiagnostic::MismatchedArgCount(d) => mismatched_arg_count::mismatched_arg_count(&ctx, &d), | ||
229 | AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d), | 224 | AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&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), | ||
230 | AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), | 227 | AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d), |
231 | AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), | 228 | AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), |
232 | AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), | 229 | AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), |
@@ -253,16 +250,6 @@ pub(crate) fn diagnostics( | |||
253 | res | 250 | res |
254 | } | 251 | } |
255 | 252 | ||
256 | fn diagnostic_with_fix<D: DiagnosticWithFixes>( | ||
257 | d: &D, | ||
258 | sema: &Semantics<RootDatabase>, | ||
259 | resolve: &AssistResolveStrategy, | ||
260 | ) -> Diagnostic { | ||
261 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) | ||
262 | .with_fixes(d.fixes(sema, resolve)) | ||
263 | .with_code(Some(d.code())) | ||
264 | } | ||
265 | |||
266 | fn warning_with_fix<D: DiagnosticWithFixes>( | 253 | fn warning_with_fix<D: DiagnosticWithFixes>( |
267 | d: &D, | 254 | d: &D, |
268 | sema: &Semantics<RootDatabase>, | 255 | sema: &Semantics<RootDatabase>, |
@@ -449,39 +436,6 @@ mod tests { | |||
449 | } | 436 | } |
450 | 437 | ||
451 | #[test] | 438 | #[test] |
452 | fn range_mapping_out_of_macros() { | ||
453 | // FIXME: this is very wrong, but somewhat tricky to fix. | ||
454 | check_fix( | ||
455 | r#" | ||
456 | fn some() {} | ||
457 | fn items() {} | ||
458 | fn here() {} | ||
459 | |||
460 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
461 | |||
462 | fn main() { | ||
463 | let _x = id![Foo { a: $042 }]; | ||
464 | } | ||
465 | |||
466 | pub struct Foo { pub a: i32, pub b: i32 } | ||
467 | "#, | ||
468 | r#" | ||
469 | fn some(, b: () ) {} | ||
470 | fn items() {} | ||
471 | fn here() {} | ||
472 | |||
473 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
474 | |||
475 | fn main() { | ||
476 | let _x = id![Foo { a: 42 }]; | ||
477 | } | ||
478 | |||
479 | pub struct Foo { pub a: i32, pub b: i32 } | ||
480 | "#, | ||
481 | ); | ||
482 | } | ||
483 | |||
484 | #[test] | ||
485 | fn test_check_unnecessary_braces_in_use_statement() { | 439 | fn test_check_unnecessary_braces_in_use_statement() { |
486 | check_diagnostics( | 440 | check_diagnostics( |
487 | r#" | 441 | r#" |
@@ -717,137 +671,6 @@ mod foo; | |||
717 | ); | 671 | ); |
718 | } | 672 | } |
719 | 673 | ||
720 | // Register the required standard library types to make the tests work | ||
721 | fn add_filter_map_with_find_next_boilerplate(body: &str) -> String { | ||
722 | let prefix = r#" | ||
723 | //- /main.rs crate:main deps:core | ||
724 | use core::iter::Iterator; | ||
725 | use core::option::Option::{self, Some, None}; | ||
726 | "#; | ||
727 | let suffix = r#" | ||
728 | //- /core/lib.rs crate:core | ||
729 | pub mod option { | ||
730 | pub enum Option<T> { Some(T), None } | ||
731 | } | ||
732 | pub mod iter { | ||
733 | pub trait Iterator { | ||
734 | type Item; | ||
735 | fn filter_map<B, F>(self, f: F) -> FilterMap where F: FnMut(Self::Item) -> Option<B> { FilterMap } | ||
736 | fn next(&mut self) -> Option<Self::Item>; | ||
737 | } | ||
738 | pub struct FilterMap {} | ||
739 | impl Iterator for FilterMap { | ||
740 | type Item = i32; | ||
741 | fn next(&mut self) -> i32 { 7 } | ||
742 | } | ||
743 | } | ||
744 | "#; | ||
745 | format!("{}{}{}", prefix, body, suffix) | ||
746 | } | ||
747 | |||
748 | #[test] | ||
749 | fn replace_filter_map_next_with_find_map2() { | ||
750 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
751 | r#" | ||
752 | fn foo() { | ||
753 | let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next(); | ||
754 | //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..) | ||
755 | } | ||
756 | "#, | ||
757 | )); | ||
758 | } | ||
759 | |||
760 | #[test] | ||
761 | fn replace_filter_map_next_with_find_map_no_diagnostic_without_next() { | ||
762 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
763 | r#" | ||
764 | fn foo() { | ||
765 | let m = [1, 2, 3] | ||
766 | .iter() | ||
767 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
768 | .len(); | ||
769 | } | ||
770 | "#, | ||
771 | )); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn replace_filter_map_next_with_find_map_no_diagnostic_with_intervening_methods() { | ||
776 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
777 | r#" | ||
778 | fn foo() { | ||
779 | let m = [1, 2, 3] | ||
780 | .iter() | ||
781 | .filter_map(|x| if *x == 2 { Some (4) } else { None }) | ||
782 | .map(|x| x + 2) | ||
783 | .len(); | ||
784 | } | ||
785 | "#, | ||
786 | )); | ||
787 | } | ||
788 | |||
789 | #[test] | ||
790 | fn replace_filter_map_next_with_find_map_no_diagnostic_if_not_in_chain() { | ||
791 | check_diagnostics(&add_filter_map_with_find_next_boilerplate( | ||
792 | r#" | ||
793 | fn foo() { | ||
794 | let m = [1, 2, 3] | ||
795 | .iter() | ||
796 | .filter_map(|x| if *x == 2 { Some (4) } else { None }); | ||
797 | let n = m.next(); | ||
798 | } | ||
799 | "#, | ||
800 | )); | ||
801 | } | ||
802 | |||
803 | #[test] | ||
804 | fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { | ||
805 | check_diagnostics( | ||
806 | r" | ||
807 | struct S { foo: i32, bar: () } | ||
808 | fn baz(s: S) -> i32 { | ||
809 | match s { | ||
810 | S { foo, .. } => foo, | ||
811 | } | ||
812 | } | ||
813 | ", | ||
814 | ) | ||
815 | } | ||
816 | |||
817 | #[test] | ||
818 | fn missing_record_pat_field_box() { | ||
819 | check_diagnostics( | ||
820 | r" | ||
821 | struct S { s: Box<u32> } | ||
822 | fn x(a: S) { | ||
823 | let S { box s } = a; | ||
824 | } | ||
825 | ", | ||
826 | ) | ||
827 | } | ||
828 | |||
829 | #[test] | ||
830 | fn missing_record_pat_field_ref() { | ||
831 | check_diagnostics( | ||
832 | r" | ||
833 | struct S { s: u32 } | ||
834 | fn x(a: S) { | ||
835 | let S { ref s } = a; | ||
836 | } | ||
837 | ", | ||
838 | ) | ||
839 | } | ||
840 | |||
841 | #[test] | ||
842 | fn missing_semicolon() { | ||
843 | check_diagnostics( | ||
844 | r#" | ||
845 | fn test() -> i32 { 123; } | ||
846 | //^^^ Remove this semicolon | ||
847 | "#, | ||
848 | ); | ||
849 | } | ||
850 | |||
851 | #[test] | 674 | #[test] |
852 | fn import_extern_crate_clash_with_inner_item() { | 675 | fn import_extern_crate_clash_with_inner_item() { |
853 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. | 676 | // This is more of a resolver test, but doesn't really work with the hir_def testsuite. |
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index a2e792b3b..e4bd90c3f 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -1,10 +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. |
3 | mod change_case; | 3 | mod change_case; |
4 | mod create_field; | ||
5 | mod remove_semicolon; | ||
6 | mod replace_with_find_map; | ||
7 | mod wrap_tail_expr; | ||
8 | 4 | ||
9 | use hir::{diagnostics::Diagnostic, Semantics}; | 5 | use hir::{diagnostics::Diagnostic, Semantics}; |
10 | use ide_assists::AssistResolveStrategy; | 6 | use ide_assists::AssistResolveStrategy; |
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs deleted file mode 100644 index 8b1378917..000000000 --- a/crates/ide/src/diagnostics/fixes/create_field.rs +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | |||
diff --git a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs b/crates/ide/src/diagnostics/fixes/remove_semicolon.rs deleted file mode 100644 index f1724d479..000000000 --- a/crates/ide/src/diagnostics/fixes/remove_semicolon.rs +++ /dev/null | |||
@@ -1,41 +0,0 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ast, AstNode}; | ||
5 | use text_edit::TextEdit; | ||
6 | |||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
8 | |||
9 | impl DiagnosticWithFixes for RemoveThisSemicolon { | ||
10 | fn fixes( | ||
11 | &self, | ||
12 | sema: &Semantics<RootDatabase>, | ||
13 | _resolve: &AssistResolveStrategy, | ||
14 | ) -> Option<Vec<Assist>> { | ||
15 | let root = sema.db.parse_or_expand(self.file)?; | ||
16 | |||
17 | let semicolon = self | ||
18 | .expr | ||
19 | .to_node(&root) | ||
20 | .syntax() | ||
21 | .parent() | ||
22 | .and_then(ast::ExprStmt::cast) | ||
23 | .and_then(|expr| expr.semicolon_token())? | ||
24 | .text_range(); | ||
25 | |||
26 | let edit = TextEdit::delete(semicolon); | ||
27 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | ||
28 | |||
29 | Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)]) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | #[cfg(test)] | ||
34 | mod tests { | ||
35 | use crate::diagnostics::tests::check_fix; | ||
36 | |||
37 | #[test] | ||
38 | fn remove_semicolon() { | ||
39 | check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); | ||
40 | } | ||
41 | } | ||
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 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::ReplaceFilterMapNextWithFindMap, Semantics}; | ||
2 | use ide_assists::{Assist, AssistResolveStrategy}; | ||
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | ||
4 | use syntax::{ | ||
5 | ast::{self, ArgListOwner}, | ||
6 | AstNode, TextRange, | ||
7 | }; | ||
8 | use text_edit::TextEdit; | ||
9 | |||
10 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | ||
11 | |||
12 | impl 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)] | ||
45 | mod 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 | ||
53 | use core::iter::Iterator; | ||
54 | use core::option::Option::{self, Some, None}; | ||
55 | fn 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 | ||
59 | pub mod option { | ||
60 | pub enum Option<T> { Some(T), None } | ||
61 | } | ||
62 | pub 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#" | ||
76 | use core::iter::Iterator; | ||
77 | use core::option::Option::{self, Some, None}; | ||
78 | fn 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/missing_fields.rs b/crates/ide/src/diagnostics/missing_fields.rs index 95cd64956..d01f05041 100644 --- a/crates/ide/src/diagnostics/missing_fields.rs +++ b/crates/ide/src/diagnostics/missing_fields.rs | |||
@@ -94,6 +94,77 @@ fn baz(s: S) { | |||
94 | } | 94 | } |
95 | 95 | ||
96 | #[test] | 96 | #[test] |
97 | fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { | ||
98 | check_diagnostics( | ||
99 | r" | ||
100 | struct S { foo: i32, bar: () } | ||
101 | fn baz(s: S) -> i32 { | ||
102 | match s { | ||
103 | S { foo, .. } => foo, | ||
104 | } | ||
105 | } | ||
106 | ", | ||
107 | ) | ||
108 | } | ||
109 | |||
110 | #[test] | ||
111 | fn missing_record_pat_field_box() { | ||
112 | check_diagnostics( | ||
113 | r" | ||
114 | struct S { s: Box<u32> } | ||
115 | fn x(a: S) { | ||
116 | let S { box s } = a; | ||
117 | } | ||
118 | ", | ||
119 | ) | ||
120 | } | ||
121 | |||
122 | #[test] | ||
123 | fn missing_record_pat_field_ref() { | ||
124 | check_diagnostics( | ||
125 | r" | ||
126 | struct S { s: u32 } | ||
127 | fn x(a: S) { | ||
128 | let S { ref s } = a; | ||
129 | } | ||
130 | ", | ||
131 | ) | ||
132 | } | ||
133 | |||
134 | #[test] | ||
135 | fn range_mapping_out_of_macros() { | ||
136 | // FIXME: this is very wrong, but somewhat tricky to fix. | ||
137 | check_fix( | ||
138 | r#" | ||
139 | fn some() {} | ||
140 | fn items() {} | ||
141 | fn here() {} | ||
142 | |||
143 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
144 | |||
145 | fn main() { | ||
146 | let _x = id![Foo { a: $042 }]; | ||
147 | } | ||
148 | |||
149 | pub struct Foo { pub a: i32, pub b: i32 } | ||
150 | "#, | ||
151 | r#" | ||
152 | fn some(, b: () ) {} | ||
153 | fn items() {} | ||
154 | fn here() {} | ||
155 | |||
156 | macro_rules! id { ($($tt:tt)*) => { $($tt)*}; } | ||
157 | |||
158 | fn main() { | ||
159 | let _x = id![Foo { a: 42 }]; | ||
160 | } | ||
161 | |||
162 | pub struct Foo { pub a: i32, pub b: i32 } | ||
163 | "#, | ||
164 | ); | ||
165 | } | ||
166 | |||
167 | #[test] | ||
97 | fn test_fill_struct_fields_empty() { | 168 | fn test_fill_struct_fields_empty() { |
98 | check_fix( | 169 | check_fix( |
99 | r#" | 170 | r#" |
diff --git a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs index dcb21e037..06005d156 100644 --- a/crates/ide/src/diagnostics/fixes/wrap_tail_expr.rs +++ b/crates/ide/src/diagnostics/missing_ok_or_some_in_tail_expr.rs | |||
@@ -1,26 +1,45 @@ | |||
1 | use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics}; | 1 | use hir::db::AstDatabase; |
2 | use ide_assists::{Assist, AssistResolveStrategy}; | 2 | use ide_assists::Assist; |
3 | use ide_db::{source_change::SourceChange, RootDatabase}; | 3 | use ide_db::source_change::SourceChange; |
4 | use syntax::AstNode; | 4 | use syntax::AstNode; |
5 | use text_edit::TextEdit; | 5 | use text_edit::TextEdit; |
6 | 6 | ||
7 | use crate::diagnostics::{fix, DiagnosticWithFixes}; | 7 | use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; |
8 | 8 | ||
9 | impl DiagnosticWithFixes for MissingOkOrSomeInTailExpr { | 9 | // Diagnostic: missing-ok-or-some-in-tail-expr |
10 | fn fixes( | 10 | // |
11 | &self, | 11 | // This diagnostic is triggered if a block that should return `Result` returns a value not wrapped in `Ok`, |
12 | sema: &Semantics<RootDatabase>, | 12 | // or if a block that should return `Option` returns a value not wrapped in `Some`. |
13 | _resolve: &AssistResolveStrategy, | 13 | // |
14 | ) -> Option<Vec<Assist>> { | 14 | // Example: |
15 | let root = sema.db.parse_or_expand(self.file)?; | 15 | // |
16 | let tail_expr = self.expr.to_node(&root); | 16 | // ```rust |
17 | let tail_expr_range = tail_expr.syntax().text_range(); | 17 | // fn foo() -> Result<u8, ()> { |
18 | let replacement = format!("{}({})", self.required, tail_expr.syntax()); | 18 | // 10 |
19 | let edit = TextEdit::replace(tail_expr_range, replacement); | 19 | // } |
20 | let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); | 20 | // ``` |
21 | let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; | 21 | pub(super) fn missing_ok_or_some_in_tail_expr( |
22 | Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)]) | 22 | ctx: &DiagnosticsContext<'_>, |
23 | } | 23 | d: &hir::MissingOkOrSomeInTailExpr, |
24 | ) -> Diagnostic { | ||
25 | Diagnostic::new( | ||
26 | "missing-ok-or-some-in-tail-expr", | ||
27 | format!("wrap return expression in {}", d.required), | ||
28 | ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, | ||
29 | ) | ||
30 | .with_fixes(fixes(ctx, d)) | ||
31 | } | ||
32 | |||
33 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingOkOrSomeInTailExpr) -> Option<Vec<Assist>> { | ||
34 | let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; | ||
35 | let tail_expr = d.expr.value.to_node(&root); | ||
36 | let tail_expr_range = tail_expr.syntax().text_range(); | ||
37 | let replacement = format!("{}({})", d.required, tail_expr.syntax()); | ||
38 | let edit = TextEdit::replace(tail_expr_range, replacement); | ||
39 | let source_change = | ||
40 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); | ||
41 | let name = if d.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; | ||
42 | Some(vec![fix("wrap_tail_expr", name, source_change, tail_expr_range)]) | ||
24 | } | 43 | } |
25 | 44 | ||
26 | #[cfg(test)] | 45 | #[cfg(test)] |
diff --git a/crates/ide/src/diagnostics/no_such_field.rs b/crates/ide/src/diagnostics/no_such_field.rs index 61962de28..edc63c246 100644 --- a/crates/ide/src/diagnostics/no_such_field.rs +++ b/crates/ide/src/diagnostics/no_such_field.rs | |||
@@ -17,7 +17,7 @@ use crate::{ | |||
17 | pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { | 17 | pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { |
18 | Diagnostic::new( | 18 | Diagnostic::new( |
19 | "no-such-field", | 19 | "no-such-field", |
20 | "no such field".to_string(), | 20 | "no such field", |
21 | ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range, | 21 | ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range, |
22 | ) | 22 | ) |
23 | .with_fixes(fixes(ctx, d)) | 23 | .with_fixes(fixes(ctx, d)) |
diff --git a/crates/ide/src/diagnostics/remove_this_semicolon.rs b/crates/ide/src/diagnostics/remove_this_semicolon.rs new file mode 100644 index 000000000..814cb0f8c --- /dev/null +++ b/crates/ide/src/diagnostics/remove_this_semicolon.rs | |||
@@ -0,0 +1,64 @@ | |||
1 | use hir::db::AstDatabase; | ||
2 | use ide_db::source_change::SourceChange; | ||
3 | use syntax::{ast, AstNode}; | ||
4 | use text_edit::TextEdit; | ||
5 | |||
6 | use crate::{ | ||
7 | diagnostics::{fix, Diagnostic, DiagnosticsContext}, | ||
8 | Assist, | ||
9 | }; | ||
10 | |||
11 | // Diagnostic: remove-this-semicolon | ||
12 | // | ||
13 | // This diagnostic is triggered when there's an erroneous `;` at the end of the block. | ||
14 | pub(super) fn remove_this_semicolon( | ||
15 | ctx: &DiagnosticsContext<'_>, | ||
16 | d: &hir::RemoveThisSemicolon, | ||
17 | ) -> Diagnostic { | ||
18 | Diagnostic::new( | ||
19 | "remove-this-semicolon", | ||
20 | "remove this semicolon", | ||
21 | ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, | ||
22 | ) | ||
23 | .with_fixes(fixes(ctx, d)) | ||
24 | } | ||
25 | |||
26 | fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::RemoveThisSemicolon) -> Option<Vec<Assist>> { | ||
27 | let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?; | ||
28 | |||
29 | let semicolon = d | ||
30 | .expr | ||
31 | .value | ||
32 | .to_node(&root) | ||
33 | .syntax() | ||
34 | .parent() | ||
35 | .and_then(ast::ExprStmt::cast) | ||
36 | .and_then(|expr| expr.semicolon_token())? | ||
37 | .text_range(); | ||
38 | |||
39 | let edit = TextEdit::delete(semicolon); | ||
40 | let source_change = | ||
41 | SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit); | ||
42 | |||
43 | Some(vec![fix("remove_semicolon", "Remove this semicolon", source_change, semicolon)]) | ||
44 | } | ||
45 | |||
46 | #[cfg(test)] | ||
47 | mod tests { | ||
48 | use crate::diagnostics::tests::{check_diagnostics, check_fix}; | ||
49 | |||
50 | #[test] | ||
51 | fn missing_semicolon() { | ||
52 | check_diagnostics( | ||
53 | r#" | ||
54 | fn test() -> i32 { 123; } | ||
55 | //^^^ remove this semicolon | ||
56 | "#, | ||
57 | ); | ||
58 | } | ||
59 | |||
60 | #[test] | ||
61 | fn remove_semicolon() { | ||
62 | check_fix(r#"fn f() -> i32 { 92$0; }"#, r#"fn f() -> i32 { 92 }"#); | ||
63 | } | ||
64 | } | ||
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 @@ | |||
1 | use hir::{db::AstDatabase, InFile}; | ||
2 | use ide_db::source_change::SourceChange; | ||
3 | use syntax::{ | ||
4 | ast::{self, ArgListOwner}, | ||
5 | AstNode, TextRange, | ||
6 | }; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use 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(..)`. | ||
17 | pub(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 | |||
30 | fn 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)] | ||
60 | mod 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 | ||
68 | use core::iter::Iterator; | ||
69 | use core::option::Option::{self, Some, None}; | ||
70 | "#; | ||
71 | let suffix = r#" | ||
72 | //- /core/lib.rs crate:core | ||
73 | pub mod option { | ||
74 | pub enum Option<T> { Some(T), None } | ||
75 | } | ||
76 | pub 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#" | ||
107 | fn 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#" | ||
121 | fn 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#" | ||
136 | fn 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 | ||
151 | use core::iter::Iterator; | ||
152 | use core::option::Option::{self, Some, None}; | ||
153 | fn 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 | ||
157 | pub mod option { | ||
158 | pub enum Option<T> { Some(T), None } | ||
159 | } | ||
160 | pub 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#" | ||
174 | use core::iter::Iterator; | ||
175 | use core::option::Option::{self, Some, None}; | ||
176 | fn foo() { | ||
177 | let m = [1, 2, 3].iter().find_map(|x| if *x == 2 { Some (4) } else { None }); | ||
178 | } | ||
179 | "#, | ||
180 | ) | ||
181 | } | ||
182 | } | ||