aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2020-12-05 14:41:36 +0000
committerLukas Wirth <[email protected]>2020-12-05 14:41:36 +0000
commit44c76d6550081552c3c5106b0535a7e5bf265aec (patch)
tree362d0555ac4b8a4b41a06b2ffada92a17b758ae7 /crates/assists
parenta3043cf53feffef3f69f25c2617801d2fc66ce75 (diff)
Add replace_match_with_if_let assist
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/handlers/early_return.rs2
-rw-r--r--crates/assists/src/handlers/move_guard.rs1
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs277
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs2
-rw-r--r--crates/assists/src/lib.rs1
-rw-r--r--crates/assists/src/tests/generated.rs28
6 files changed, 308 insertions, 3 deletions
diff --git a/crates/assists/src/handlers/early_return.rs b/crates/assists/src/handlers/early_return.rs
index 7fd78e9d4..7bcc318a9 100644
--- a/crates/assists/src/handlers/early_return.rs
+++ b/crates/assists/src/handlers/early_return.rs
@@ -112,7 +112,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext)
112 let then_branch = 112 let then_branch =
113 make::block_expr(once(make::expr_stmt(early_expression).into()), None); 113 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114 let cond = invert_boolean_expression(cond_expr); 114 let cond = invert_boolean_expression(cond_expr);
115 make::expr_if(make::condition(cond, None), then_branch) 115 make::expr_if(make::condition(cond, None), then_branch, None)
116 .indent(if_indent_level) 116 .indent(if_indent_level)
117 }; 117 };
118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr) 118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
diff --git a/crates/assists/src/handlers/move_guard.rs b/crates/assists/src/handlers/move_guard.rs
index e1855b63d..eaffd80ce 100644
--- a/crates/assists/src/handlers/move_guard.rs
+++ b/crates/assists/src/handlers/move_guard.rs
@@ -42,6 +42,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) ->
42 let if_expr = make::expr_if( 42 let if_expr = make::expr_if(
43 make::condition(guard_condition, None), 43 make::condition(guard_condition, None),
44 make::block_expr(None, Some(arm_expr.clone())), 44 make::block_expr(None, Some(arm_expr.clone())),
45 None,
45 ) 46 )
46 .indent(arm_expr.indent_level()); 47 .indent(arm_expr.indent_level());
47 48
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs
index 9a49c48c1..4a355c66f 100644
--- a/crates/assists/src/handlers/replace_if_let_with_match.rs
+++ b/crates/assists/src/handlers/replace_if_let_with_match.rs
@@ -1,3 +1,6 @@
1use std::iter;
2
3use ide_db::{ty_filter::TryEnum, RootDatabase};
1use syntax::{ 4use syntax::{
2 ast::{ 5 ast::{
3 self, 6 self,
@@ -8,7 +11,6 @@ use syntax::{
8}; 11};
9 12
10use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; 13use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
11use ide_db::ty_filter::TryEnum;
12 14
13// Assist: replace_if_let_with_match 15// Assist: replace_if_let_with_match
14// 16//
@@ -79,6 +81,91 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext)
79 ) 81 )
80} 82}
81 83
84// Assist: replace_match_with_if_let
85//
86// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
87//
88// ```
89// enum Action { Move { distance: u32 }, Stop }
90//
91// fn handle(action: Action) {
92// <|>match action {
93// Action::Move { distance } => foo(distance),
94// _ => bar(),
95// }
96// }
97// ```
98// ->
99// ```
100// enum Action { Move { distance: u32 }, Stop }
101//
102// fn handle(action: Action) {
103// if let Action::Move { distance } = action {
104// foo(distance)
105// } else {
106// bar()
107// }
108// }
109// ```
110pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
111 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
112 let mut arms = match_expr.match_arm_list()?.arms();
113 let first_arm = arms.next()?;
114 let second_arm = arms.next()?;
115 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
116 return None;
117 }
118 let condition_expr = match_expr.expr()?;
119 let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
120 {
121 (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
122 } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
123 (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
124 } else {
125 return None;
126 };
127
128 let target = match_expr.syntax().text_range();
129 acc.add(
130 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
131 "Replace with if let",
132 target,
133 move |edit| {
134 let condition = make::condition(condition_expr, Some(if_let_pat));
135 let then_block = match then_expr.reset_indent() {
136 ast::Expr::BlockExpr(block) => block,
137 expr => make::block_expr(iter::empty(), Some(expr)),
138 };
139 let else_expr = match else_expr {
140 ast::Expr::BlockExpr(block)
141 if block.statements().count() == 0 && block.expr().is_none() =>
142 {
143 None
144 }
145 ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
146 expr => Some(expr),
147 };
148 let if_let_expr = make::expr_if(
149 condition,
150 then_block,
151 else_expr.map(|else_expr| {
152 ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
153 }),
154 )
155 .indent(IndentLevel::from_node(match_expr.syntax()));
156
157 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
158 },
159 )
160}
161
162fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
163 sema.type_of_pat(&pat)
164 .and_then(|ty| TryEnum::from_ty(sema, &ty))
165 .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
166 .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
167}
168
82#[cfg(test)] 169#[cfg(test)]
83mod tests { 170mod tests {
84 use super::*; 171 use super::*;
@@ -252,4 +339,192 @@ fn main() {
252"#, 339"#,
253 ) 340 )
254 } 341 }
342
343 #[test]
344 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
345 check_assist(
346 replace_match_with_if_let,
347 r#"
348impl VariantData {
349 pub fn is_struct(&self) -> bool {
350 <|>match *self {
351 VariantData::Struct(..) => true,
352 _ => false,
353 }
354 }
355} "#,
356 r#"
357impl VariantData {
358 pub fn is_struct(&self) -> bool {
359 if let VariantData::Struct(..) = *self {
360 true
361 } else {
362 false
363 }
364 }
365} "#,
366 )
367 }
368
369 #[test]
370 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
371 check_assist(
372 replace_match_with_if_let,
373 r#"
374fn foo() {
375 <|>match a {
376 VariantData::Struct(..) => {
377 bar(
378 123
379 )
380 }
381 _ => false,
382 }
383} "#,
384 r#"
385fn foo() {
386 if let VariantData::Struct(..) = a {
387 bar(
388 123
389 )
390 } else {
391 false
392 }
393} "#,
394 )
395 }
396
397 #[test]
398 fn replace_match_with_if_let_target() {
399 check_assist_target(
400 replace_match_with_if_let,
401 r#"
402impl VariantData {
403 pub fn is_struct(&self) -> bool {
404 <|>match *self {
405 VariantData::Struct(..) => true,
406 _ => false,
407 }
408 }
409} "#,
410 r#"match *self {
411 VariantData::Struct(..) => true,
412 _ => false,
413 }"#,
414 );
415 }
416
417 #[test]
418 fn special_case_option_match_to_if_let() {
419 check_assist(
420 replace_match_with_if_let,
421 r#"
422enum Option<T> { Some(T), None }
423use Option::*;
424
425fn foo(x: Option<i32>) {
426 <|>match x {
427 Some(x) => println!("{}", x),
428 None => println!("none"),
429 }
430}
431 "#,
432 r#"
433enum Option<T> { Some(T), None }
434use Option::*;
435
436fn foo(x: Option<i32>) {
437 if let Some(x) = x {
438 println!("{}", x)
439 } else {
440 println!("none")
441 }
442}
443 "#,
444 );
445 }
446
447 #[test]
448 fn special_case_result_match_to_if_let() {
449 check_assist(
450 replace_match_with_if_let,
451 r#"
452enum Result<T, E> { Ok(T), Err(E) }
453use Result::*;
454
455fn foo(x: Result<i32, ()>) {
456 <|>match x {
457 Ok(x) => println!("{}", x),
458 Err(_) => println!("none"),
459 }
460}
461 "#,
462 r#"
463enum Result<T, E> { Ok(T), Err(E) }
464use Result::*;
465
466fn foo(x: Result<i32, ()>) {
467 if let Ok(x) = x {
468 println!("{}", x)
469 } else {
470 println!("none")
471 }
472}
473 "#,
474 );
475 }
476
477 #[test]
478 fn nested_indent_match_to_if_let() {
479 check_assist(
480 replace_match_with_if_let,
481 r#"
482fn main() {
483 if true {
484 <|>match path.strip_prefix(root_path) {
485 Ok(rel_path) => {
486 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
487 Some((*id, rel_path))
488 }
489 _ => None,
490 }
491 }
492}
493"#,
494 r#"
495fn main() {
496 if true {
497 if let Ok(rel_path) = path.strip_prefix(root_path) {
498 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
499 Some((*id, rel_path))
500 } else {
501 None
502 }
503 }
504}
505"#,
506 )
507 }
508
509 #[test]
510 fn replace_match_with_if_let_empty_wildcard_expr() {
511 check_assist(
512 replace_match_with_if_let,
513 r#"
514fn main() {
515 <|>match path.strip_prefix(root_path) {
516 Ok(rel_path) => println!("{}", rel_path),
517 _ => (),
518 }
519}
520"#,
521 r#"
522fn main() {
523 if let Ok(rel_path) = path.strip_prefix(root_path) {
524 println!("{}", rel_path)
525 }
526}
527"#,
528 )
529 }
255} 530}
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
index 69d3b08d3..5970e283c 100644
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ b/crates/assists/src/handlers/replace_let_with_if_let.rs
@@ -60,7 +60,7 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) ->
60 }; 60 };
61 let block = 61 let block =
62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax())); 62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block); 63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None);
64 let stmt = make::expr_stmt(if_); 64 let stmt = make::expr_stmt(if_);
65 65
66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap(); 66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index dfe6c2729..b8ce7418d 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -209,6 +209,7 @@ mod handlers {
209 reorder_fields::reorder_fields, 209 reorder_fields::reorder_fields,
210 replace_derive_with_manual_impl::replace_derive_with_manual_impl, 210 replace_derive_with_manual_impl::replace_derive_with_manual_impl,
211 replace_if_let_with_match::replace_if_let_with_match, 211 replace_if_let_with_match::replace_if_let_with_match,
212 replace_if_let_with_match::replace_match_with_if_let,
212 replace_impl_trait_with_generic::replace_impl_trait_with_generic, 213 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
213 replace_let_with_if_let::replace_let_with_if_let, 214 replace_let_with_if_let::replace_let_with_if_let,
214 replace_qualified_name_with_use::replace_qualified_name_with_use, 215 replace_qualified_name_with_use::replace_qualified_name_with_use,
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 8d50c8791..853bde09c 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -890,6 +890,34 @@ fn compute() -> Option<i32> { None }
890} 890}
891 891
892#[test] 892#[test]
893fn doctest_replace_match_with_if_let() {
894 check_doc_test(
895 "replace_match_with_if_let",
896 r#####"
897enum Action { Move { distance: u32 }, Stop }
898
899fn handle(action: Action) {
900 <|>match action {
901 Action::Move { distance } => foo(distance),
902 _ => bar(),
903 }
904}
905"#####,
906 r#####"
907enum Action { Move { distance: u32 }, Stop }
908
909fn handle(action: Action) {
910 if let Action::Move { distance } = action {
911 foo(distance)
912 } else {
913 bar()
914 }
915}
916"#####,
917 )
918}
919
920#[test]
893fn doctest_replace_qualified_name_with_use() { 921fn doctest_replace_qualified_name_with_use() {
894 check_doc_test( 922 check_doc_test(
895 "replace_qualified_name_with_use", 923 "replace_qualified_name_with_use",