aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorPhil Ellison <[email protected]>2020-12-30 15:46:05 +0000
committerPhil Ellison <[email protected]>2021-01-23 07:40:25 +0000
commit1ff860b93c972e0f8d3a8ee582c255fa59e9b284 (patch)
tree0533465ecb7329b6185c9259fb0f9c2b5ab0f7b0 /crates
parent1316422a7c2ef26e9da78fa23f170407b1cb39bb (diff)
Implement fix, add tests
Diffstat (limited to 'crates')
-rw-r--r--crates/hir/src/diagnostics.rs9
-rw-r--r--crates/hir_ty/src/diagnostics.rs56
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs27
-rw-r--r--crates/ide/src/diagnostics/fixes.rs29
4 files changed, 66 insertions, 55 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index f7fd3e237..5343a036c 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -7,12 +7,3 @@ pub use hir_ty::diagnostics::{
7 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, 7 IncorrectCase, MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
8 NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, 8 NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
9}; 9};
10
11// PHIL:
12// hir/src/diagnostics.rs - just pub uses the type from hir_ty::diagnostics (DONE)
13// hir_ty/src/diagnostics.rs - defines the type (DONE)
14// hir_ty/src/diagnostics.rs - plus a test (DONE) <--- one example found, need to copy the not-applicable tests from the assist version
15// ide/src/diagnostics.rs - define handler for when this diagnostic is raised (DONE)
16
17// ide/src/diagnostics/fixes.rs - pulls in type from hir, and impls DiagnosticWithFix (TODO)
18// hir_ty/src/diagnostics/expr.rs - do the real work (TODO)
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index b4cf81c10..3d7663f6a 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -392,7 +392,7 @@ impl Diagnostic for IncorrectCase {
392#[derive(Debug)] 392#[derive(Debug)]
393pub struct ReplaceFilterMapNextWithFindMap { 393pub struct ReplaceFilterMapNextWithFindMap {
394 pub file: HirFileId, 394 pub file: HirFileId,
395 pub filter_map_expr: AstPtr<ast::Expr>, 395 /// This expression is the whole method chain up to and including `.filter_map(..).next()`.
396 pub next_expr: AstPtr<ast::Expr>, 396 pub next_expr: AstPtr<ast::Expr>,
397} 397}
398 398
@@ -404,7 +404,7 @@ impl Diagnostic for ReplaceFilterMapNextWithFindMap {
404 "replace filter_map(..).next() with find_map(..)".to_string() 404 "replace filter_map(..).next() with find_map(..)".to_string()
405 } 405 }
406 fn display_source(&self) -> InFile<SyntaxNodePtr> { 406 fn display_source(&self) -> InFile<SyntaxNodePtr> {
407 InFile { file_id: self.file, value: self.filter_map_expr.clone().into() } 407 InFile { file_id: self.file, value: self.next_expr.clone().into() }
408 } 408 }
409 fn as_any(&self) -> &(dyn Any + Send + 'static) { 409 fn as_any(&self) -> &(dyn Any + Send + 'static) {
410 self 410 self
@@ -671,15 +671,55 @@ fn foo() { break; }
671 } 671 }
672 672
673 #[test] 673 #[test]
674 fn replace_missing_filter_next_with_find_map() { 674 fn replace_filter_next_with_find_map() {
675 check_diagnostics( 675 check_diagnostics(
676 r#" 676 r#"
677 fn foo() { 677 fn foo() {
678 let m = [1, 2, 3] 678 let m = [1, 2, 3].iter().filter_map(|x| if *x == 2 { Some (4) } else { None }).next();
679 .iter() 679 //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ replace filter_map(..).next() with find_map(..)
680 .filter_map(|x| if *x == 2 { Some (4) } else { None }) 680 }
681 .next(); 681 "#,
682 //^^^ Replace .filter_map(..).next() with .find_map(..) 682 );
683 }
684
685 #[test]
686 fn replace_filter_next_with_find_map_no_diagnostic_without_next() {
687 check_diagnostics(
688 r#"
689 fn foo() {
690 let m = [1, 2, 3]
691 .iter()
692 .filter_map(|x| if *x == 2 { Some (4) } else { None })
693 .len();
694 }
695 "#,
696 );
697 }
698
699 #[test]
700 fn replace_filter_next_with_find_map_no_diagnostic_with_intervening_methods() {
701 check_diagnostics(
702 r#"
703 fn foo() {
704 let m = [1, 2, 3]
705 .iter()
706 .filter_map(|x| if *x == 2 { Some (4) } else { None })
707 .map(|x| x + 2)
708 .len();
709 }
710 "#,
711 );
712 }
713
714 #[test]
715 fn replace_filter_next_with_find_map_no_diagnostic_if_not_in_chain() {
716 check_diagnostics(
717 r#"
718 fn foo() {
719 let m = [1, 2, 3]
720 .iter()
721 .filter_map(|x| if *x == 2 { Some (4) } else { None });
722 let n = m.next();
683 } 723 }
684 "#, 724 "#,
685 ); 725 );
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index 170d23178..b87557ff5 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -41,16 +41,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
41 ExprValidator { owner, infer, sink } 41 ExprValidator { owner, infer, sink }
42 } 42 }
43 43
44 fn bar() {
45 // LOOK FOR THIS
46 let m = [1, 2, 3]
47 .iter()
48 .filter_map(|x| if *x == 2 { Some(4) } else { None })
49 .next();
50 }
51
52 pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) { 44 pub(super) fn validate_body(&mut self, db: &dyn HirDatabase) {
53 // DO NOT MERGE: just getting something working for now
54 self.check_for_filter_map_next(db); 45 self.check_for_filter_map_next(db);
55 46
56 let body = db.body(self.owner.into()); 47 let body = db.body(self.owner.into());
@@ -169,24 +160,20 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
169 160
170 for (id, expr) in body.exprs.iter() { 161 for (id, expr) in body.exprs.iter() {
171 if let Expr::MethodCall { receiver, method_name, args, .. } = expr { 162 if let Expr::MethodCall { receiver, method_name, args, .. } = expr {
172 let method_name_hack_do_not_merge = format!("{}", method_name); 163 let method_name = format!("{}", method_name);
173 164
174 if method_name_hack_do_not_merge == "filter_map" && args.len() == 1 { 165 if method_name == "filter_map" && args.len() == 1 {
175 prev = Some((id, args[0])); 166 prev = Some(id);
176 continue; 167 continue;
177 } 168 }
178 169
179 if method_name_hack_do_not_merge == "next" { 170 if method_name == "next" {
180 if let Some((filter_map_id, filter_map_args)) = prev { 171 if let Some(filter_map_id) = prev {
181 if *receiver == filter_map_id { 172 if *receiver == filter_map_id {
182 let (_, source_map) = db.body_with_source_map(self.owner.into()); 173 let (_, source_map) = db.body_with_source_map(self.owner.into());
183 if let (Ok(filter_map_source_ptr), Ok(next_source_ptr)) = ( 174 if let Ok(next_source_ptr) = source_map.expr_syntax(id) {
184 source_map.expr_syntax(filter_map_id),
185 source_map.expr_syntax(id),
186 ) {
187 self.sink.push(ReplaceFilterMapNextWithFindMap { 175 self.sink.push(ReplaceFilterMapNextWithFindMap {
188 file: filter_map_source_ptr.file_id, 176 file: next_source_ptr.file_id,
189 filter_map_expr: filter_map_source_ptr.value,
190 next_expr: next_source_ptr.value, 177 next_expr: next_source_ptr.value,
191 }); 178 });
192 } 179 }
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index eafbac43a..7bbf1d8c7 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -1,5 +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.
3use ast::MethodCallExpr;
3use hir::{ 4use hir::{
4 db::AstDatabase, 5 db::AstDatabase,
5 diagnostics::{ 6 diagnostics::{
@@ -13,11 +14,7 @@ use ide_db::{
13 source_change::{FileSystemEdit, SourceChange}, 14 source_change::{FileSystemEdit, SourceChange},
14 RootDatabase, 15 RootDatabase,
15}; 16};
16use syntax::{ 17use syntax::{AstNode, TextRange, algo, ast::{self, ArgList, edit::IndentLevel, make}};
17 algo,
18 ast::{self, edit::IndentLevel, make},
19 AstNode,
20};
21use text_edit::TextEdit; 18use text_edit::TextEdit;
22 19
23use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition}; 20use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition};
@@ -144,25 +141,21 @@ impl DiagnosticWithFix for IncorrectCase {
144 } 141 }
145} 142}
146 143
147// Bugs:
148// * Action is applicable for both iter() and filter_map() rows
149// * Action deletes the entire method chain
150impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { 144impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
151 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 145 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
152 let root = sema.db.parse_or_expand(self.file)?; 146 let root = sema.db.parse_or_expand(self.file)?;
153
154 let next_expr = self.next_expr.to_node(&root); 147 let next_expr = self.next_expr.to_node(&root);
155 let next_expr_range = next_expr.syntax().text_range(); 148 let next_call = MethodCallExpr::cast(next_expr.syntax().clone())?;
156 149
157 let filter_map_expr = self.filter_map_expr.to_node(&root); 150 let filter_map_call = MethodCallExpr::cast(next_call.receiver()?.syntax().clone())?;
158 let filter_map_expr_range = filter_map_expr.syntax().text_range(); 151 let filter_map_name_range = filter_map_call.name_ref()?.ident_token()?.text_range();
152 let filter_map_args = filter_map_call.syntax().children().find_map(ArgList::cast)?;
159 153
160 let edit = TextEdit::delete(next_expr_range); 154 let range_to_replace = TextRange::new(filter_map_name_range.start(), next_expr.syntax().text_range().end());
155 let replacement = format!("find_map{}", filter_map_args.syntax().text());
156 let trigger_range = next_expr.syntax().text_range();
161 157
162 // This is the entire method chain, including the array literal 158 let edit = TextEdit::replace(range_to_replace, replacement);
163 eprintln!("NEXT EXPR: {:#?}", next_expr);
164 // This is the entire method chain except for the final next()
165 eprintln!("FILTER MAP EXPR: {:#?}", filter_map_expr);
166 159
167 let source_change = 160 let source_change =
168 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); 161 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
@@ -170,7 +163,7 @@ impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
170 Some(Fix::new( 163 Some(Fix::new(
171 "Replace filter_map(..).next() with find_map()", 164 "Replace filter_map(..).next() with find_map()",
172 source_change, 165 source_change,
173 filter_map_expr_range, 166 trigger_range,
174 )) 167 ))
175 } 168 }
176} 169}