aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/hir/src/diagnostics.rs72
-rw-r--r--crates/hir/src/lib.rs22
-rw-r--r--crates/ide/src/diagnostics.rs191
-rw-r--r--crates/ide/src/diagnostics/fixes.rs4
-rw-r--r--crates/ide/src/diagnostics/fixes/create_field.rs1
-rw-r--r--crates/ide/src/diagnostics/fixes/remove_semicolon.rs41
-rw-r--r--crates/ide/src/diagnostics/fixes/replace_with_find_map.rs84
-rw-r--r--crates/ide/src/diagnostics/missing_fields.rs71
-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.rs2
-rw-r--r--crates/ide/src/diagnostics/remove_this_semicolon.rs64
-rw-r--r--crates/ide/src/diagnostics/replace_filter_map_next_with_find_map.rs182
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)]
126pub struct ReplaceFilterMapNextWithFindMap { 126pub 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
132impl 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)]
148pub struct MismatchedArgCount { 133pub 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)]
155pub struct RemoveThisSemicolon { 140pub struct RemoveThisSemicolon {
156 pub file: HirFileId, 141 pub expr: InFile<AstPtr<ast::Expr>>,
157 pub expr: AstPtr<ast::Expr>,
158}
159
160impl 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)]
191pub struct MissingOkOrSomeInTailExpr { 145pub 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
198impl 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;
9mod macro_error; 9mod macro_error;
10mod mismatched_arg_count; 10mod mismatched_arg_count;
11mod missing_fields; 11mod missing_fields;
12mod missing_ok_or_some_in_tail_expr;
12mod missing_unsafe; 13mod missing_unsafe;
13mod no_such_field; 14mod no_such_field;
15mod remove_this_semicolon;
16mod replace_filter_map_next_with_find_map;
14mod unimplemented_builtin_macro; 17mod unimplemented_builtin_macro;
15mod unresolved_extern_crate; 18mod unresolved_extern_crate;
16mod unresolved_import; 19mod 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
256fn 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
266fn warning_with_fix<D: DiagnosticWithFixes>( 253fn 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#"
456fn some() {}
457fn items() {}
458fn here() {}
459
460macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
461
462fn main() {
463 let _x = id![Foo { a: $042 }];
464}
465
466pub struct Foo { pub a: i32, pub b: i32 }
467"#,
468 r#"
469fn some(, b: () ) {}
470fn items() {}
471fn here() {}
472
473macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
474
475fn main() {
476 let _x = id![Foo { a: 42 }];
477}
478
479pub 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"
807struct S { foo: i32, bar: () }
808fn 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"
821struct S { s: Box<u32> }
822fn 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"
833struct S { s: u32 }
834fn 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.
3mod change_case; 3mod change_case;
4mod create_field;
5mod remove_semicolon;
6mod replace_with_find_map;
7mod wrap_tail_expr;
8 4
9use hir::{diagnostics::Diagnostic, Semantics}; 5use hir::{diagnostics::Diagnostic, Semantics};
10use ide_assists::AssistResolveStrategy; 6use 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 @@
1use hir::{db::AstDatabase, diagnostics::RemoveThisSemicolon, Semantics};
2use ide_assists::{Assist, AssistResolveStrategy};
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{ast, AstNode};
5use text_edit::TextEdit;
6
7use crate::diagnostics::{fix, DiagnosticWithFixes};
8
9impl 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)]
34mod 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 @@
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/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"
100struct S { foo: i32, bar: () }
101fn 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"
114struct S { s: Box<u32> }
115fn 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"
126struct S { s: u32 }
127fn 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#"
139fn some() {}
140fn items() {}
141fn here() {}
142
143macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
144
145fn main() {
146 let _x = id![Foo { a: $042 }];
147}
148
149pub struct Foo { pub a: i32, pub b: i32 }
150"#,
151 r#"
152fn some(, b: () ) {}
153fn items() {}
154fn here() {}
155
156macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }
157
158fn main() {
159 let _x = id![Foo { a: 42 }];
160}
161
162pub 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 @@
1use hir::{db::AstDatabase, diagnostics::MissingOkOrSomeInTailExpr, Semantics}; 1use hir::db::AstDatabase;
2use ide_assists::{Assist, AssistResolveStrategy}; 2use ide_assists::Assist;
3use ide_db::{source_change::SourceChange, RootDatabase}; 3use ide_db::source_change::SourceChange;
4use syntax::AstNode; 4use syntax::AstNode;
5use text_edit::TextEdit; 5use text_edit::TextEdit;
6 6
7use crate::diagnostics::{fix, DiagnosticWithFixes}; 7use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
8 8
9impl 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" }; 21pub(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
33fn 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::{
17pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic { 17pub(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 @@
1use hir::db::AstDatabase;
2use ide_db::source_change::SourceChange;
3use syntax::{ast, AstNode};
4use text_edit::TextEdit;
5
6use 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.
14pub(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
26fn 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)]
47mod tests {
48 use crate::diagnostics::tests::{check_diagnostics, check_fix};
49
50 #[test]
51 fn missing_semicolon() {
52 check_diagnostics(
53 r#"
54fn 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 @@
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}